mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `8.7`: - [[Controls] Fix sorting of numeric keyword fields (#155207)](https://github.com/elastic/kibana/pull/155207) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Hannah Mudge","email":"Heenawter@users.noreply.github.com"},"sourceCommit":{"committedDate":"2023-04-26T18:19:46Z","message":"[Controls] Fix sorting of numeric keyword fields (#155207)\n\nCloses https://github.com/elastic/kibana/issues/155073\r\n\r\n## Summary\r\n\r\n### Before\r\n\r\nPreviously, the options list suggestions were stored as a dictionary\r\n(i.e. an object of key+value pairs) - while this worked for most fields,\r\nunbeknownst to us, Javascript tries to sort numeric keys (regardless of\r\nif they are of type `string` or `number`) based on their value.\r\n\r\nThis meant that, as part of the parsing process when using an options\r\nlist control for a numeric `keyword` field, the results returned by the\r\nES query were **always** sorted in ascending numeric order regardless of\r\nthe sorting method that was picked (note that this is especially obvious\r\nonce you \"load more\", which is what I did for the following\r\nscreenshots):\r\n\r\n\r\n| | Ascending | Descending |\r\n|--------------|-----------|------------|\r\n| Alphabetical | <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234391308-6d3a23ee-3495-4eff-810f-216f758b3a58.png\"/>\r\n| <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234391213-117163e2-ee97-4f9d-87fa-a63c8cc5459e.png\"/>\r\n|\r\n| Doc count | <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234391375-0ccdf72f-83c0-4a87-951e-c2e1e3223006.png\"/>\r\n| <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234392997-fea42ffe-5d9d-4a11-968f-e1503f2c0e4f.png\"/>\r\n|\r\n\r\n\r\n### After\r\n\r\nThis PR converts the options list suggestions to be stored as an\r\n**array** of key/value pairs in order to preserve the order returned\r\nfrom Elasticsearch - now, you get the expected string-sorted ordering\r\nwhen using numeric `keyword` fields in an options list control:\r\n\r\n| | Ascending | Descending |\r\n|--------------|-----------|------------|\r\n| Alphabetical | <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234394182-aa2bfdf4-fe41-441d-bdbf-917173c17627.png\"/>\r\n| <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234393421-24cca3e3-0249-4607-9e16-daa274399bdd.png\"/>\r\n|\r\n| Doc count | <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234394599-dda01056-5446-497e-abe4-f3839aeb4dd0.png\"/>\r\n| <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234394693-42544ef1-eb2b-4d52-8a78-c2ca7d2d7cfa.png\"/>\r\n|\r\n\r\n\r\nNotice in the above that we are now using **string sorting** for the\r\nnumeric values when alphabetical sorting is selected, which means you\r\naren't getting the expected \"numeric\" sorting - so for example, when\r\nsorted ascending, `\"6\" > \"52\"` because it is only comparing the first\r\ncharacter and `\"6\" > \"5\"`. This will be handled much better once\r\n[numeric field support](https://github.com/elastic/kibana/issues/126795)\r\nis added to options lists.\r\n\r\n\r\n### Checklist\r\n\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [x] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n### For maintainers\r\n\r\n- [ ] This was checked for breaking API changes and was [labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"b3f65f79e5017b70fe26e5aa1c2ee1085e68c138","branchLabelMapping":{"^v8.8.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:fix","Feature:Input Control","Team:Presentation","loe:days","impact:medium","backport:prev-minor","v8.8.0","v8.9.0"],"number":155207,"url":"https://github.com/elastic/kibana/pull/155207","mergeCommit":{"message":"[Controls] Fix sorting of numeric keyword fields (#155207)\n\nCloses https://github.com/elastic/kibana/issues/155073\r\n\r\n## Summary\r\n\r\n### Before\r\n\r\nPreviously, the options list suggestions were stored as a dictionary\r\n(i.e. an object of key+value pairs) - while this worked for most fields,\r\nunbeknownst to us, Javascript tries to sort numeric keys (regardless of\r\nif they are of type `string` or `number`) based on their value.\r\n\r\nThis meant that, as part of the parsing process when using an options\r\nlist control for a numeric `keyword` field, the results returned by the\r\nES query were **always** sorted in ascending numeric order regardless of\r\nthe sorting method that was picked (note that this is especially obvious\r\nonce you \"load more\", which is what I did for the following\r\nscreenshots):\r\n\r\n\r\n| | Ascending | Descending |\r\n|--------------|-----------|------------|\r\n| Alphabetical | <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234391308-6d3a23ee-3495-4eff-810f-216f758b3a58.png\"/>\r\n| <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234391213-117163e2-ee97-4f9d-87fa-a63c8cc5459e.png\"/>\r\n|\r\n| Doc count | <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234391375-0ccdf72f-83c0-4a87-951e-c2e1e3223006.png\"/>\r\n| <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234392997-fea42ffe-5d9d-4a11-968f-e1503f2c0e4f.png\"/>\r\n|\r\n\r\n\r\n### After\r\n\r\nThis PR converts the options list suggestions to be stored as an\r\n**array** of key/value pairs in order to preserve the order returned\r\nfrom Elasticsearch - now, you get the expected string-sorted ordering\r\nwhen using numeric `keyword` fields in an options list control:\r\n\r\n| | Ascending | Descending |\r\n|--------------|-----------|------------|\r\n| Alphabetical | <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234394182-aa2bfdf4-fe41-441d-bdbf-917173c17627.png\"/>\r\n| <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234393421-24cca3e3-0249-4607-9e16-daa274399bdd.png\"/>\r\n|\r\n| Doc count | <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234394599-dda01056-5446-497e-abe4-f3839aeb4dd0.png\"/>\r\n| <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234394693-42544ef1-eb2b-4d52-8a78-c2ca7d2d7cfa.png\"/>\r\n|\r\n\r\n\r\nNotice in the above that we are now using **string sorting** for the\r\nnumeric values when alphabetical sorting is selected, which means you\r\naren't getting the expected \"numeric\" sorting - so for example, when\r\nsorted ascending, `\"6\" > \"52\"` because it is only comparing the first\r\ncharacter and `\"6\" > \"5\"`. This will be handled much better once\r\n[numeric field support](https://github.com/elastic/kibana/issues/126795)\r\nis added to options lists.\r\n\r\n\r\n### Checklist\r\n\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [x] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n### For maintainers\r\n\r\n- [ ] This was checked for breaking API changes and was [labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"b3f65f79e5017b70fe26e5aa1c2ee1085e68c138"}},"sourceBranch":"main","suggestedTargetBranches":["8.9"],"targetPullRequestStates":[{"branch":"main","label":"v8.8.0","labelRegex":"^v8.8.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/155207","number":155207,"mergeCommit":{"message":"[Controls] Fix sorting of numeric keyword fields (#155207)\n\nCloses https://github.com/elastic/kibana/issues/155073\r\n\r\n## Summary\r\n\r\n### Before\r\n\r\nPreviously, the options list suggestions were stored as a dictionary\r\n(i.e. an object of key+value pairs) - while this worked for most fields,\r\nunbeknownst to us, Javascript tries to sort numeric keys (regardless of\r\nif they are of type `string` or `number`) based on their value.\r\n\r\nThis meant that, as part of the parsing process when using an options\r\nlist control for a numeric `keyword` field, the results returned by the\r\nES query were **always** sorted in ascending numeric order regardless of\r\nthe sorting method that was picked (note that this is especially obvious\r\nonce you \"load more\", which is what I did for the following\r\nscreenshots):\r\n\r\n\r\n| | Ascending | Descending |\r\n|--------------|-----------|------------|\r\n| Alphabetical | <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234391308-6d3a23ee-3495-4eff-810f-216f758b3a58.png\"/>\r\n| <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234391213-117163e2-ee97-4f9d-87fa-a63c8cc5459e.png\"/>\r\n|\r\n| Doc count | <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234391375-0ccdf72f-83c0-4a87-951e-c2e1e3223006.png\"/>\r\n| <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234392997-fea42ffe-5d9d-4a11-968f-e1503f2c0e4f.png\"/>\r\n|\r\n\r\n\r\n### After\r\n\r\nThis PR converts the options list suggestions to be stored as an\r\n**array** of key/value pairs in order to preserve the order returned\r\nfrom Elasticsearch - now, you get the expected string-sorted ordering\r\nwhen using numeric `keyword` fields in an options list control:\r\n\r\n| | Ascending | Descending |\r\n|--------------|-----------|------------|\r\n| Alphabetical | <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234394182-aa2bfdf4-fe41-441d-bdbf-917173c17627.png\"/>\r\n| <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234393421-24cca3e3-0249-4607-9e16-daa274399bdd.png\"/>\r\n|\r\n| Doc count | <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234394599-dda01056-5446-497e-abe4-f3839aeb4dd0.png\"/>\r\n| <img width=\"320px\"\r\nsrc=\"https://user-images.githubusercontent.com/8698078/234394693-42544ef1-eb2b-4d52-8a78-c2ca7d2d7cfa.png\"/>\r\n|\r\n\r\n\r\nNotice in the above that we are now using **string sorting** for the\r\nnumeric values when alphabetical sorting is selected, which means you\r\naren't getting the expected \"numeric\" sorting - so for example, when\r\nsorted ascending, `\"6\" > \"52\"` because it is only comparing the first\r\ncharacter and `\"6\" > \"5\"`. This will be handled much better once\r\n[numeric field support](https://github.com/elastic/kibana/issues/126795)\r\nis added to options lists.\r\n\r\n\r\n### Checklist\r\n\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [x] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n### For maintainers\r\n\r\n- [ ] This was checked for breaking API changes and was [labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"b3f65f79e5017b70fe26e5aa1c2ee1085e68c138"}},{"branch":"8.9","label":"v8.9.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT-->
This commit is contained in:
parent
5d80eb581c
commit
167c3285d8
11 changed files with 196 additions and 191 deletions
|
@ -21,13 +21,13 @@ const mockOptionsListComponentState = {
|
|||
...getDefaultComponentState(),
|
||||
field: undefined,
|
||||
totalCardinality: 0,
|
||||
availableOptions: {
|
||||
woof: { doc_count: 100 },
|
||||
bark: { doc_count: 75 },
|
||||
meow: { doc_count: 50 },
|
||||
quack: { doc_count: 25 },
|
||||
moo: { doc_count: 5 },
|
||||
},
|
||||
availableOptions: [
|
||||
{ value: 'woof', docCount: 100 },
|
||||
{ value: 'bark', docCount: 75 },
|
||||
{ value: 'meow', docCount: 50 },
|
||||
{ value: 'quack', docCount: 25 },
|
||||
{ value: 'moo', docCount: 5 },
|
||||
],
|
||||
invalidSelections: [],
|
||||
validSelections: [],
|
||||
} as OptionsListComponentState;
|
||||
|
|
|
@ -28,9 +28,7 @@ export interface OptionsListEmbeddableInput extends DataControlInput {
|
|||
placeholder?: string;
|
||||
}
|
||||
|
||||
export interface OptionsListSuggestions {
|
||||
[key: string]: { doc_count: number };
|
||||
}
|
||||
export type OptionsListSuggestions = Array<{ value: string; docCount?: number }>;
|
||||
|
||||
/**
|
||||
* The Options list response is returned from the serverside Options List route.
|
||||
|
|
|
@ -35,7 +35,11 @@ import { injectStorybookDataView } from '../services/data_views/data_views.story
|
|||
import { replaceOptionsListMethod } from '../services/options_list/options_list.story';
|
||||
import { populateStorybookControlFactories } from './storybook_control_factories';
|
||||
import { replaceValueSuggestionMethod } from '../services/unified_search/unified_search.story';
|
||||
import { OptionsListResponse, OptionsListRequest } from '../../common/options_list/types';
|
||||
import {
|
||||
OptionsListResponse,
|
||||
OptionsListRequest,
|
||||
OptionsListSuggestions,
|
||||
} from '../../common/options_list/types';
|
||||
|
||||
export default {
|
||||
title: 'Controls',
|
||||
|
@ -56,9 +60,9 @@ const storybookStubOptionsListRequest = async (
|
|||
r({
|
||||
suggestions: getFlightSearchOptions(request.field.name, request.searchString).reduce(
|
||||
(o, current, index) => {
|
||||
return { ...o, [current]: { doc_count: index } };
|
||||
return [...o, { value: current, docCount: index }];
|
||||
},
|
||||
{}
|
||||
[] as OptionsListSuggestions
|
||||
),
|
||||
totalCardinality: 100,
|
||||
}),
|
||||
|
|
|
@ -68,7 +68,7 @@ describe('Options list popover', () => {
|
|||
});
|
||||
|
||||
test('no available options', async () => {
|
||||
const popover = await mountComponent({ componentState: { availableOptions: {} } });
|
||||
const popover = await mountComponent({ componentState: { availableOptions: [] } });
|
||||
const availableOptionsDiv = findTestSubject(popover, 'optionsList-control-available-options');
|
||||
const noOptionsDiv = findTestSubject(
|
||||
availableOptionsDiv,
|
||||
|
@ -125,9 +125,7 @@ describe('Options list popover', () => {
|
|||
selectedOptions: ['bark', 'woof'],
|
||||
},
|
||||
componentState: {
|
||||
availableOptions: {
|
||||
bark: { doc_count: 75 },
|
||||
},
|
||||
availableOptions: [{ value: 'bark', docCount: 75 }],
|
||||
validSelections: ['bark'],
|
||||
invalidSelections: ['woof'],
|
||||
},
|
||||
|
@ -152,9 +150,7 @@ describe('Options list popover', () => {
|
|||
const popover = await mountComponent({
|
||||
explicitInput: { selectedOptions: ['bark', 'woof', 'meow'] },
|
||||
componentState: {
|
||||
availableOptions: {
|
||||
bark: { doc_count: 75 },
|
||||
},
|
||||
availableOptions: [{ value: 'bark', docCount: 75 }],
|
||||
validSelections: ['bark'],
|
||||
invalidSelections: ['woof', 'meow'],
|
||||
},
|
||||
|
@ -217,7 +213,7 @@ describe('Options list popover', () => {
|
|||
|
||||
test('if existsSelected = false and no suggestions, then "Exists" does not show up', async () => {
|
||||
const popover = await mountComponent({
|
||||
componentState: { availableOptions: {} },
|
||||
componentState: { availableOptions: [] },
|
||||
explicitInput: { existsSelected: false },
|
||||
});
|
||||
const existsOption = findTestSubject(popover, 'optionsList-control-selection-exists');
|
||||
|
|
|
@ -55,7 +55,7 @@ export const OptionsListPopoverSuggestions = ({
|
|||
const canLoadMoreSuggestions = useMemo(
|
||||
() =>
|
||||
totalCardinality
|
||||
? Object.keys(availableOptions ?? {}).length <
|
||||
? (availableOptions ?? []).length <
|
||||
Math.min(totalCardinality, MAX_OPTIONS_LIST_REQUEST_SIZE)
|
||||
: false,
|
||||
[availableOptions, totalCardinality]
|
||||
|
@ -68,7 +68,7 @@ export const OptionsListPopoverSuggestions = ({
|
|||
[invalidSelections]
|
||||
);
|
||||
const suggestions = useMemo(() => {
|
||||
return showOnlySelected ? selectedOptions : Object.keys(availableOptions ?? {});
|
||||
return showOnlySelected ? selectedOptions : availableOptions ?? [];
|
||||
}, [availableOptions, selectedOptions, showOnlySelected]);
|
||||
|
||||
const existsSelectableOption = useMemo<EuiSelectableOption | undefined>(() => {
|
||||
|
@ -86,19 +86,23 @@ export const OptionsListPopoverSuggestions = ({
|
|||
const [selectableOptions, setSelectableOptions] = useState<EuiSelectableOption[]>([]); // will be set in following useEffect
|
||||
useEffect(() => {
|
||||
/* This useEffect makes selectableOptions responsive to search, show only selected, and clear selections */
|
||||
const options: EuiSelectableOption[] = (suggestions ?? []).map((key) => {
|
||||
const options: EuiSelectableOption[] = (suggestions ?? []).map((suggestion) => {
|
||||
if (typeof suggestion === 'string') {
|
||||
// this means that `showOnlySelected` is true, and doc count is not known when this is the case
|
||||
suggestion = { value: suggestion };
|
||||
}
|
||||
return {
|
||||
key,
|
||||
label: key,
|
||||
checked: selectedOptionsSet?.has(key) ? 'on' : undefined,
|
||||
'data-test-subj': `optionsList-control-selection-${key}`,
|
||||
key: suggestion.value,
|
||||
label: suggestion.value,
|
||||
checked: selectedOptionsSet?.has(suggestion.value) ? 'on' : undefined,
|
||||
'data-test-subj': `optionsList-control-selection-${suggestion.value}`,
|
||||
className:
|
||||
showOnlySelected && invalidSelectionsSet.has(key)
|
||||
showOnlySelected && invalidSelectionsSet.has(suggestion.value)
|
||||
? 'optionsList__selectionInvalid'
|
||||
: 'optionsList__validSuggestion',
|
||||
append:
|
||||
!showOnlySelected && availableOptions?.[key] ? (
|
||||
<OptionsListPopoverSuggestionBadge documentCount={availableOptions[key].doc_count} />
|
||||
!showOnlySelected && suggestion?.docCount ? (
|
||||
<OptionsListPopoverSuggestionBadge documentCount={suggestion.docCount} />
|
||||
) : undefined,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -377,7 +377,7 @@ export class OptionsListEmbeddable extends Embeddable<OptionsListEmbeddableInput
|
|||
batch(() => {
|
||||
dispatch(
|
||||
updateQueryResults({
|
||||
availableOptions: {},
|
||||
availableOptions: [],
|
||||
})
|
||||
);
|
||||
dispatch(setLoading(false));
|
||||
|
|
|
@ -18,7 +18,7 @@ let optionsListRequestMethod = async (request: OptionsListRequest, abortSignal:
|
|||
setTimeout(
|
||||
() =>
|
||||
r({
|
||||
suggestions: {},
|
||||
suggestions: [],
|
||||
totalCardinality: 100,
|
||||
}),
|
||||
120
|
||||
|
|
|
@ -388,17 +388,20 @@ describe('options list cheap queries', () => {
|
|||
expect(
|
||||
suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock).suggestions
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"cool1": Object {
|
||||
"doc_count": 5,
|
||||
Array [
|
||||
Object {
|
||||
"docCount": 5,
|
||||
"value": "cool1",
|
||||
},
|
||||
"cool2": Object {
|
||||
"doc_count": 15,
|
||||
Object {
|
||||
"docCount": 15,
|
||||
"value": "cool2",
|
||||
},
|
||||
"cool3": Object {
|
||||
"doc_count": 10,
|
||||
Object {
|
||||
"docCount": 10,
|
||||
"value": "cool3",
|
||||
},
|
||||
}
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
|
@ -421,14 +424,16 @@ describe('options list cheap queries', () => {
|
|||
expect(
|
||||
suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock).suggestions
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"false": Object {
|
||||
"doc_count": 55,
|
||||
Array [
|
||||
Object {
|
||||
"docCount": 55,
|
||||
"value": "false",
|
||||
},
|
||||
"true": Object {
|
||||
"doc_count": 155,
|
||||
Object {
|
||||
"docCount": 155,
|
||||
"value": "true",
|
||||
},
|
||||
}
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
|
@ -455,17 +460,20 @@ describe('options list cheap queries', () => {
|
|||
expect(
|
||||
suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock).suggestions
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"cool1": Object {
|
||||
"doc_count": 5,
|
||||
Array [
|
||||
Object {
|
||||
"docCount": 5,
|
||||
"value": "cool1",
|
||||
},
|
||||
"cool2": Object {
|
||||
"doc_count": 15,
|
||||
Object {
|
||||
"docCount": 15,
|
||||
"value": "cool2",
|
||||
},
|
||||
"cool3": Object {
|
||||
"doc_count": 10,
|
||||
Object {
|
||||
"docCount": 10,
|
||||
"value": "cool3",
|
||||
},
|
||||
}
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
|
@ -490,17 +498,20 @@ describe('options list cheap queries', () => {
|
|||
expect(
|
||||
suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock).suggestions
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"cool1": Object {
|
||||
"doc_count": 5,
|
||||
Array [
|
||||
Object {
|
||||
"docCount": 5,
|
||||
"value": "cool1",
|
||||
},
|
||||
"cool2": Object {
|
||||
"doc_count": 15,
|
||||
Object {
|
||||
"docCount": 15,
|
||||
"value": "cool2",
|
||||
},
|
||||
"cool3": Object {
|
||||
"doc_count": 10,
|
||||
Object {
|
||||
"docCount": 10,
|
||||
"value": "cool3",
|
||||
},
|
||||
}
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
@ -552,55 +563,50 @@ describe('options list cheap queries', () => {
|
|||
rawSearchResponseMock,
|
||||
optionsListRequestBodyMock
|
||||
).suggestions;
|
||||
/** first, verify that the sorting worked as expected */
|
||||
expect(Object.keys(parsed)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"52:ae76:5947:5e2a:551:fe6a:712a:c72",
|
||||
"111.52.174.2",
|
||||
"196.162.13.39",
|
||||
"f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63",
|
||||
"23.216.241.120",
|
||||
"28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172",
|
||||
"21.35.91.62",
|
||||
"21.35.91.61",
|
||||
"203.88.33.151",
|
||||
"1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8",
|
||||
]
|
||||
`);
|
||||
/** then, make sure the object is structured properly */
|
||||
|
||||
expect(parsed).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"111.52.174.2": Object {
|
||||
"doc_count": 11,
|
||||
Array [
|
||||
Object {
|
||||
"docCount": 12,
|
||||
"value": "52:ae76:5947:5e2a:551:fe6a:712a:c72",
|
||||
},
|
||||
"196.162.13.39": Object {
|
||||
"doc_count": 10,
|
||||
Object {
|
||||
"docCount": 11,
|
||||
"value": "111.52.174.2",
|
||||
},
|
||||
"1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8": Object {
|
||||
"doc_count": 6,
|
||||
Object {
|
||||
"docCount": 10,
|
||||
"value": "196.162.13.39",
|
||||
},
|
||||
"203.88.33.151": Object {
|
||||
"doc_count": 7,
|
||||
Object {
|
||||
"docCount": 10,
|
||||
"value": "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63",
|
||||
},
|
||||
"21.35.91.61": Object {
|
||||
"doc_count": 8,
|
||||
Object {
|
||||
"docCount": 9,
|
||||
"value": "23.216.241.120",
|
||||
},
|
||||
"21.35.91.62": Object {
|
||||
"doc_count": 8,
|
||||
Object {
|
||||
"docCount": 9,
|
||||
"value": "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172",
|
||||
},
|
||||
"23.216.241.120": Object {
|
||||
"doc_count": 9,
|
||||
Object {
|
||||
"docCount": 8,
|
||||
"value": "21.35.91.62",
|
||||
},
|
||||
"28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172": Object {
|
||||
"doc_count": 9,
|
||||
Object {
|
||||
"docCount": 8,
|
||||
"value": "21.35.91.61",
|
||||
},
|
||||
"52:ae76:5947:5e2a:551:fe6a:712a:c72": Object {
|
||||
"doc_count": 12,
|
||||
Object {
|
||||
"docCount": 7,
|
||||
"value": "203.88.33.151",
|
||||
},
|
||||
"f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63": Object {
|
||||
"doc_count": 10,
|
||||
Object {
|
||||
"docCount": 6,
|
||||
"value": "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8",
|
||||
},
|
||||
}
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -51,11 +51,11 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat
|
|||
},
|
||||
}),
|
||||
parse: (rawEsResult) => ({
|
||||
suggestions: get(rawEsResult, 'aggregations.suggestions.buckets').reduce(
|
||||
(suggestions: OptionsListSuggestions, suggestion: EsBucket) => {
|
||||
return { ...suggestions, [suggestion.key]: { doc_count: suggestion.doc_count } };
|
||||
suggestions: get(rawEsResult, 'aggregations.suggestions.buckets')?.reduce(
|
||||
(acc: OptionsListSuggestions, suggestion: EsBucket) => {
|
||||
return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }];
|
||||
},
|
||||
{}
|
||||
[]
|
||||
),
|
||||
}),
|
||||
},
|
||||
|
@ -75,13 +75,10 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat
|
|||
}),
|
||||
parse: (rawEsResult) => ({
|
||||
suggestions: get(rawEsResult, 'aggregations.suggestions.buckets')?.reduce(
|
||||
(suggestions: OptionsListSuggestions, suggestion: EsBucket & { key_as_string: string }) => {
|
||||
return {
|
||||
...suggestions,
|
||||
[suggestion.key_as_string]: { doc_count: suggestion.doc_count },
|
||||
};
|
||||
(acc: OptionsListSuggestions, suggestion: EsBucket & { key_as_string: string }) => {
|
||||
return [...acc, { value: suggestion.key_as_string, docCount: suggestion.doc_count }];
|
||||
},
|
||||
{}
|
||||
[]
|
||||
),
|
||||
}),
|
||||
},
|
||||
|
@ -134,7 +131,7 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat
|
|||
if (!Boolean(rawEsResult.aggregations?.suggestions)) {
|
||||
// if this is happens, that means there is an invalid search that snuck through to the server side code;
|
||||
// so, might as well early return with no suggestions
|
||||
return { suggestions: {} };
|
||||
return { suggestions: [] };
|
||||
}
|
||||
|
||||
const buckets: EsBucket[] = [];
|
||||
|
@ -153,9 +150,9 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat
|
|||
return {
|
||||
suggestions: sortedSuggestions
|
||||
.slice(0, 10) // only return top 10 results
|
||||
.reduce((suggestions, suggestion: EsBucket) => {
|
||||
return { ...suggestions, [suggestion.key]: { doc_count: suggestion.doc_count } };
|
||||
}, {}),
|
||||
.reduce((acc: OptionsListSuggestions, suggestion: EsBucket) => {
|
||||
return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }];
|
||||
}, []),
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -190,11 +187,11 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat
|
|||
};
|
||||
},
|
||||
parse: (rawEsResult) => ({
|
||||
suggestions: get(rawEsResult, 'aggregations.nestedSuggestions.suggestions.buckets').reduce(
|
||||
(suggestions: OptionsListSuggestions, suggestion: EsBucket) => {
|
||||
return { ...suggestions, [suggestion.key]: { doc_count: suggestion.doc_count } };
|
||||
suggestions: get(rawEsResult, 'aggregations.nestedSuggestions.suggestions.buckets')?.reduce(
|
||||
(acc: OptionsListSuggestions, suggestion: EsBucket) => {
|
||||
return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }];
|
||||
},
|
||||
{}
|
||||
[]
|
||||
),
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -466,17 +466,20 @@ describe('options list expensive queries', () => {
|
|||
expect(suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"suggestions": Object {
|
||||
"cool1": Object {
|
||||
"doc_count": 5,
|
||||
"suggestions": Array [
|
||||
Object {
|
||||
"docCount": 5,
|
||||
"value": "cool1",
|
||||
},
|
||||
"cool2": Object {
|
||||
"doc_count": 15,
|
||||
Object {
|
||||
"docCount": 15,
|
||||
"value": "cool2",
|
||||
},
|
||||
"cool3": Object {
|
||||
"doc_count": 10,
|
||||
Object {
|
||||
"docCount": 10,
|
||||
"value": "cool3",
|
||||
},
|
||||
},
|
||||
],
|
||||
"totalCardinality": 3,
|
||||
}
|
||||
`);
|
||||
|
@ -503,14 +506,16 @@ describe('options list expensive queries', () => {
|
|||
expect(suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"suggestions": Object {
|
||||
"false": Object {
|
||||
"doc_count": 55,
|
||||
"suggestions": Array [
|
||||
Object {
|
||||
"docCount": 55,
|
||||
"value": "false",
|
||||
},
|
||||
"true": Object {
|
||||
"doc_count": 155,
|
||||
Object {
|
||||
"docCount": 155,
|
||||
"value": "true",
|
||||
},
|
||||
},
|
||||
],
|
||||
"totalCardinality": 2,
|
||||
}
|
||||
`);
|
||||
|
@ -546,17 +551,20 @@ describe('options list expensive queries', () => {
|
|||
expect(suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"suggestions": Object {
|
||||
"cool1": Object {
|
||||
"doc_count": 5,
|
||||
"suggestions": Array [
|
||||
Object {
|
||||
"docCount": 5,
|
||||
"value": "cool1",
|
||||
},
|
||||
"cool2": Object {
|
||||
"doc_count": 15,
|
||||
Object {
|
||||
"docCount": 15,
|
||||
"value": "cool2",
|
||||
},
|
||||
"cool3": Object {
|
||||
"doc_count": 10,
|
||||
Object {
|
||||
"docCount": 10,
|
||||
"value": "cool3",
|
||||
},
|
||||
},
|
||||
],
|
||||
"totalCardinality": 3,
|
||||
}
|
||||
`);
|
||||
|
@ -621,55 +629,50 @@ describe('options list expensive queries', () => {
|
|||
rawSearchResponseMock,
|
||||
optionsListRequestBodyMock
|
||||
).suggestions;
|
||||
/** first, verify that the sorting worked as expected */
|
||||
expect(Object.keys(parsed)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"52:ae76:5947:5e2a:551:fe6a:712a:c72",
|
||||
"111.52.174.2",
|
||||
"196.162.13.39",
|
||||
"f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63",
|
||||
"23.216.241.120",
|
||||
"28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172",
|
||||
"21.35.91.62",
|
||||
"21.35.91.61",
|
||||
"203.88.33.151",
|
||||
"1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8",
|
||||
]
|
||||
`);
|
||||
/** then, make sure the object is structured properly */
|
||||
|
||||
expect(parsed).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"111.52.174.2": Object {
|
||||
"doc_count": 11,
|
||||
Array [
|
||||
Object {
|
||||
"docCount": 12,
|
||||
"value": "52:ae76:5947:5e2a:551:fe6a:712a:c72",
|
||||
},
|
||||
"196.162.13.39": Object {
|
||||
"doc_count": 10,
|
||||
Object {
|
||||
"docCount": 11,
|
||||
"value": "111.52.174.2",
|
||||
},
|
||||
"1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8": Object {
|
||||
"doc_count": 6,
|
||||
Object {
|
||||
"docCount": 10,
|
||||
"value": "196.162.13.39",
|
||||
},
|
||||
"203.88.33.151": Object {
|
||||
"doc_count": 7,
|
||||
Object {
|
||||
"docCount": 10,
|
||||
"value": "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63",
|
||||
},
|
||||
"21.35.91.61": Object {
|
||||
"doc_count": 8,
|
||||
Object {
|
||||
"docCount": 9,
|
||||
"value": "23.216.241.120",
|
||||
},
|
||||
"21.35.91.62": Object {
|
||||
"doc_count": 8,
|
||||
Object {
|
||||
"docCount": 9,
|
||||
"value": "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172",
|
||||
},
|
||||
"23.216.241.120": Object {
|
||||
"doc_count": 9,
|
||||
Object {
|
||||
"docCount": 8,
|
||||
"value": "21.35.91.62",
|
||||
},
|
||||
"28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172": Object {
|
||||
"doc_count": 9,
|
||||
Object {
|
||||
"docCount": 8,
|
||||
"value": "21.35.91.61",
|
||||
},
|
||||
"52:ae76:5947:5e2a:551:fe6a:712a:c72": Object {
|
||||
"doc_count": 12,
|
||||
Object {
|
||||
"docCount": 7,
|
||||
"value": "203.88.33.151",
|
||||
},
|
||||
"f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63": Object {
|
||||
"doc_count": 10,
|
||||
Object {
|
||||
"docCount": 6,
|
||||
"value": "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8",
|
||||
},
|
||||
}
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -93,9 +93,9 @@ const expensiveSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggr
|
|||
|
||||
const suggestions = get(rawEsResult, `${basePath}.suggestions.buckets`)?.reduce(
|
||||
(acc: OptionsListSuggestions, suggestion: EsBucket) => {
|
||||
return { ...acc, [suggestion.key]: { doc_count: suggestion.doc_count } };
|
||||
return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }];
|
||||
},
|
||||
{}
|
||||
[]
|
||||
);
|
||||
return {
|
||||
suggestions,
|
||||
|
@ -120,14 +120,11 @@ const expensiveSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggr
|
|||
parse: (rawEsResult) => {
|
||||
const suggestions = get(rawEsResult, 'aggregations.suggestions.buckets')?.reduce(
|
||||
(acc: OptionsListSuggestions, suggestion: EsBucket & { key_as_string: string }) => {
|
||||
return {
|
||||
...acc,
|
||||
[suggestion.key_as_string]: { doc_count: suggestion.doc_count },
|
||||
};
|
||||
return [...acc, { value: suggestion.key_as_string, docCount: suggestion.doc_count }];
|
||||
},
|
||||
{}
|
||||
[]
|
||||
);
|
||||
return { suggestions, totalCardinality: Object.keys(suggestions).length }; // cardinality is only ever 0, 1, or 2 so safe to use length here
|
||||
return { suggestions, totalCardinality: suggestions.length }; // cardinality is only ever 0, 1, or 2 so safe to use length here
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -185,7 +182,7 @@ const expensiveSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggr
|
|||
if (!Boolean(rawEsResult.aggregations?.suggestions)) {
|
||||
// if this is happens, that means there is an invalid search that snuck through to the server side code;
|
||||
// so, might as well early return with no suggestions
|
||||
return { suggestions: {}, totalCardinality: 0 };
|
||||
return { suggestions: [], totalCardinality: 0 };
|
||||
}
|
||||
const buckets: EsBucket[] = [];
|
||||
getIpBuckets(rawEsResult, buckets, 'ipv4'); // modifies buckets array directly, i.e. "by reference"
|
||||
|
@ -200,11 +197,11 @@ const expensiveSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggr
|
|||
(bucketA: EsBucket, bucketB: EsBucket) => bucketB.doc_count - bucketA.doc_count
|
||||
);
|
||||
|
||||
const suggestions: OptionsListSuggestions = sortedSuggestions
|
||||
const suggestions = sortedSuggestions
|
||||
.slice(0, request.size)
|
||||
.reduce((acc: OptionsListSuggestions, suggestion: EsBucket) => {
|
||||
return { ...acc, [suggestion.key]: { doc_count: suggestion.doc_count } };
|
||||
}, {});
|
||||
return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }];
|
||||
}, []);
|
||||
const totalCardinality =
|
||||
(get(rawEsResult, `aggregations.suggestions.buckets.ipv4.unique_terms.value`) ?? 0) +
|
||||
(get(rawEsResult, `aggregations.suggestions.buckets.ipv6.unique_terms.value`) ?? 0);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue