[Dashboard] [Controls] Remove options list "Allow <x>" toggles (#147216)

Closes https://github.com/elastic/kibana/issues/147140

## Summary

After discussion surrounding the "author" versus "analyst" experience
for the controls, we have come to the conclusion that a simpler create
experience is necessary to streamline our current process. This means
removing **all** options that are not absolutely necessary -
specifically, this PR removes the UX for the following "Additional
settings" toggles from the creating/editing flyout:

- Allow selections to be excluded (`hideExclude`)
- Allow exists query (`hideExists`)
- Allow dynamic sorting of suggestions (`hideSort`)

This will leave only two additional settings: 
- Allow multiple selections in dropdown
- Ignore timeout for results

While the UX for the removed toggles will be removed, the logic is kept
- this means that, for the Control Group API that solutions will be
using, this functionality could still be exposed to the consumers.


### Migration 
Consider a scenario where, in 8.6, a user creates an options list
control with `hideExists` set to `true`- that is, the `Exists` option is
not displayed in the options list suggestions. After upgrading to 8.7,
they realize that they want to turn the `Exists` query back on - but
they can't, because the toggle has been removed so `hideExists` is stuck
as `true`! Their only choice is to remove the control and start over.

In order to avoid this scenario, I added an 8.7 migration that goes
through all existing control group panels and, if a control is an
options list, it removes the `hideExclude` and `hideExists` keys from
the explicit input. This is effectively the same was setting these to
`false` since they are optional.

> **Note**
> Because the `hideExclude` and `hideExists` toggles were added back in
8.6.0 but `hideSort` was only added in 8.7.0 (which has not yet reached
feature freeze), that is why only the `hideExclude` and `hideExists`
keys are removed. There is no need to add a migration for `hideSort`
because there will be no customer impact from removing the "Allow
dynamic sorting of suggestions" toggle.


## How to test

For the sake of convenience, I have exported various dashboard saved
objects from 8.6 (all of which used the demo "Kibana Sample Data
eCommerce" data view) in order to test the migration to 8.7:
- `8.6_OneControlNoMigrations.ndjson`
This dashboard had a single options list control that should require
**no** migration because it was created with the default settings:


![image](https://user-images.githubusercontent.com/8698078/206319266-84377dd8-8e53-4680-81f3-60e7b33ba15e.png)


- `8.6_OneControlTogglesOff.ndjson`
This dashboard had a single options list control with **every single**
toggle turned off:


![image](https://user-images.githubusercontent.com/8698078/206319041-bdb8d28f-4f70-4ac9-a2d2-87600b0db66b.png)


- `8.6_TwoOptionsListControls.ndjson`
This dashboard had two options list controls with the following
settings:
    

![TwoOptionsListControls](https://user-images.githubusercontent.com/8698078/206320381-4aad9c78-0bb0-4d30-b7f7-3dc3e6b7b2a2.png)
 
The first control also had the `exists` query selected with `exclude`
set to `true`, while the second control had non-default size settings,
and I made some selections and saved them as part of the dashboard.

- `8.6_MultipleControlTypes.ndjson`
This dashboard had four controls: two options list controls with the
following settings, a range slider control, and a time slider control:


![MultipleControlTypes](https://user-images.githubusercontent.com/8698078/206321542-8a9943ce-bf87-4de7-94fe-7ee370b92a23.png)


These can each be found in the following ZIP file:
> **Warning**
>
[DashboardSavedObjectsForTestingMigration.zip](10180945/DashboardSavedObjectsForTestingMigration.zip)


In order to test this PR, you should (at minimum) download the previous
ZIP, individually import each dashboard into Kibana 8.7, and ensure
that:
1. the resulting JSON looks as expected, with no `hideExists` or
`hideExclude` keys present, and
2. the dashboard loads correctly and each options list control has the
ability to include/exclude, sort, and create an exists query regardless
of the previous state of `hideExists` or `hideExclude`


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)


### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Hannah Mudge 2022-12-14 15:03:21 -07:00 committed by GitHub
parent 182c05e22e
commit 6a1b37e5fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 181 additions and 216 deletions

View file

@ -66,9 +66,9 @@ describe('checking migration metadata changes on all registered SO types', () =>
"app_search_telemetry": "7fc4fc08852bf0924ee29942bb394fda9aa8954d",
"application_usage_daily": "6e645e0b60ef3af2e8fde80963c2a4f09a190d61",
"application_usage_totals": "b2af3577dcd50bfae492b166a7804f69e2cc41dc",
"canvas-element": "c27505dcf2970760bea8a0fe1d000253f0c40f08",
"canvas-workpad": "eb7b28a3b1c24af615edbf29becddf2e750a4bb5",
"canvas-workpad-template": "34454b811e32993eaa55c6ec85a7aecca00c4cfc",
"canvas-element": "e2e312fc499c1a81e628b88baba492fb24f4e82d",
"canvas-workpad": "4b05f7829bc805bbaa07eb9fc0d2a2bbbd6bbf39",
"canvas-workpad-template": "d4bb65aa9c4a2b25029d3272fd9c715d8e4247d7",
"cases": "17af08c8b3550b3e57ba1a7f3b89d85f271712c0",
"cases-comments": "d7c4c1d24e97620cd415e27e5eb7d5b5f2c5b461",
"cases-configure": "1afc414f5563a36e4612fa269193d3ed7277c7bd",
@ -81,7 +81,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"core-usage-stats": "f40a213da2c597b0de94e364a4326a5a1baa4ca9",
"csp-rule-template": "3679c5f2431da8153878db79c78a4e695357fb61",
"csp_rule": "d2bb53ea5d2bdfba1a835ad8956dfcd2b2c32e19",
"dashboard": "742f2d9f110709fd8599b14f7766f38efc30de61",
"dashboard": "7e37790f802b39c852f905c010e13674e893105a",
"endpoint:user-artifact": "f94c250a52b30d0a2d32635f8b4c5bdabd1e25c0",
"endpoint:user-artifact-manifest": "8c14d49a385d5d1307d956aa743ec78de0b2be88",
"enterprise_search_telemetry": "fafcc8318528d34f721c42d1270787c52565bad5",

View file

@ -0,0 +1,119 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import {
ControlWidth,
ControlPanelState,
OPTIONS_LIST_CONTROL,
RANGE_SLIDER_CONTROL,
OptionsListEmbeddableInput,
RangeSliderEmbeddableInput,
getDefaultControlGroupInput,
ControlGroupInput,
} from '..';
import { mockOptionsListEmbeddableInput, mockRangeSliderEmbeddableInput } from '../mocks';
import { removeHideExcludeAndHideExists } from './control_group_migrations';
describe('migrate control group', () => {
const getOptionsListControl = (order: number, input?: Partial<OptionsListEmbeddableInput>) => {
return {
type: OPTIONS_LIST_CONTROL,
order,
width: 'small' as ControlWidth,
grow: true,
explicitInput: { ...mockOptionsListEmbeddableInput, ...input },
} as ControlPanelState;
};
const getRangeSliderControl = (order: number, input?: Partial<RangeSliderEmbeddableInput>) => {
return {
type: RANGE_SLIDER_CONTROL,
order,
width: 'medium' as ControlWidth,
grow: false,
explicitInput: { ...mockRangeSliderEmbeddableInput, ...input },
} as ControlPanelState;
};
const getControlGroupInput = (panels: ControlPanelState[]): ControlGroupInput => {
const panelsObjects = panels.reduce((acc, panel) => {
return { ...acc, [panel.explicitInput.id]: panel };
}, {});
return {
id: 'testControlGroupMigration',
...getDefaultControlGroupInput(),
panels: panelsObjects,
};
};
describe('remove hideExclude and hideExists', () => {
test('should migrate single options list control', () => {
const migratedControlGroupInput: ControlGroupInput = removeHideExcludeAndHideExists(
getControlGroupInput([getOptionsListControl(0, { id: 'testPanelId', hideExclude: true })])
);
expect(migratedControlGroupInput.panels).toEqual({
testPanelId: getOptionsListControl(0, { id: 'testPanelId' }),
});
});
test('should migrate multiple options list controls', () => {
const migratedControlGroupInput: ControlGroupInput = removeHideExcludeAndHideExists(
getControlGroupInput([
getOptionsListControl(0, { id: 'testPanelId1' }),
getOptionsListControl(1, { id: 'testPanelId2', hideExclude: false }),
getOptionsListControl(2, { id: 'testPanelId3', hideExists: true }),
getOptionsListControl(3, {
id: 'testPanelId4',
hideExclude: true,
hideExists: false,
}),
getOptionsListControl(4, {
id: 'testPanelId5',
hideExists: true,
hideExclude: false,
singleSelect: true,
runPastTimeout: true,
selectedOptions: ['test'],
}),
])
);
expect(migratedControlGroupInput.panels).toEqual({
testPanelId1: getOptionsListControl(0, { id: 'testPanelId1' }),
testPanelId2: getOptionsListControl(1, { id: 'testPanelId2' }),
testPanelId3: getOptionsListControl(2, { id: 'testPanelId3' }),
testPanelId4: getOptionsListControl(3, {
id: 'testPanelId4',
}),
testPanelId5: getOptionsListControl(4, {
id: 'testPanelId5',
singleSelect: true,
runPastTimeout: true,
selectedOptions: ['test'],
}),
});
});
test('should migrate multiple different types of controls', () => {
const migratedControlGroupInput: ControlGroupInput = removeHideExcludeAndHideExists(
getControlGroupInput([
getOptionsListControl(0, {
id: 'testPanelId1',
hideExists: true,
hideExclude: true,
runPastTimeout: true,
}),
getRangeSliderControl(1, { id: 'testPanelId2' }),
])
);
expect(migratedControlGroupInput.panels).toEqual({
testPanelId1: getOptionsListControl(0, { id: 'testPanelId1', runPastTimeout: true }),
testPanelId2: getRangeSliderControl(1, { id: 'testPanelId2' }),
});
});
});
});

View file

@ -6,7 +6,8 @@
* Side Public License, v 1.
*/
import { ControlGroupInput, ControlsPanels } from '..';
import { ControlGroupInput, ControlPanelState, ControlsPanels } from '..';
import { OptionsListEmbeddableInput, OPTIONS_LIST_CONTROL } from '../options_list/types';
export const makeControlOrdersZeroBased = (input: ControlGroupInput) => {
if (
@ -30,3 +31,31 @@ export const makeControlOrdersZeroBased = (input: ControlGroupInput) => {
}
return input;
};
/**
* The UX for the "Allow include/exclude" and "Allow exists query" toggles was removed in 8.7.0 so, to
* prevent users from getting stuck when migrating from 8.6.0 (when the toggles were introduced) to 8.7.0
* we must set both the `hideExclude` and `hideExists` keys to `undefined` for all existing options
* list controls.
*/
export const removeHideExcludeAndHideExists = (input: ControlGroupInput) => {
if (input.panels && typeof input.panels === 'object' && Object.keys(input.panels).length > 0) {
const newPanels = Object.keys(input.panels).reduce<ControlsPanels>(
(panelAccumulator, panelId) => {
const panel: ControlPanelState = input.panels[panelId];
if (panel.type === OPTIONS_LIST_CONTROL) {
const explicitInput = panel.explicitInput as OptionsListEmbeddableInput;
delete explicitInput.hideExclude;
delete explicitInput.hideExists;
}
return {
...panelAccumulator,
[panelId]: panel,
};
},
{}
);
input.panels = newPanels;
}
return input;
};

View file

@ -14,7 +14,10 @@ import {
import { SavedObjectReference } from '@kbn/core/types';
import { MigrateFunctionsObject } from '@kbn/kibana-utils-plugin/common';
import { ControlGroupInput, ControlPanelState } from './types';
import { makeControlOrdersZeroBased } from './control_group_migrations';
import {
makeControlOrdersZeroBased,
removeHideExcludeAndHideExists,
} from './control_group_migrations';
type ControlGroupInputWithType = Partial<ControlGroupInput> & { type: string };
@ -92,4 +95,9 @@ export const migrations: MigrateFunctionsObject = {
// for hierarchical chaining it is required that all control orders start at 0.
return makeControlOrdersZeroBased(controlInput);
},
'8.7.0': (state) => {
const controlInput = state as unknown as ControlGroupInput;
// need to set `hideExclude` and `hideExists` to `undefined` for all options list controls.
return removeHideExcludeAndHideExists(controlInput);
},
};

View file

@ -8,3 +8,4 @@
export * from './control_group/mocks';
export * from './options_list/mocks';
export * from './range_slider/mocks';

View file

@ -26,14 +26,13 @@ const mockOptionsListComponentState = {
validSelections: [],
} as OptionsListComponentState;
const mockOptionsListEmbeddableInput = {
export const mockOptionsListEmbeddableInput = {
id: 'sample options list',
fieldName: 'sample field',
dataViewId: 'sample id',
selectedOptions: [],
runPastTimeout: false,
singleSelect: false,
allowExclude: false,
exclude: false,
} as OptionsListEmbeddableInput;

View file

@ -0,0 +1,16 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { RangeSliderEmbeddableInput } from '..';
export const mockRangeSliderEmbeddableInput = {
id: 'sample options list',
fieldName: 'sample field',
dataViewId: 'sample id',
value: ['0', '10'],
} as RangeSliderEmbeddableInput;

View file

@ -6,28 +6,21 @@
* Side Public License, v 1.
*/
import React, { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useState } from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiForm,
EuiFormRow,
EuiIconTip,
EuiSuperSelectOption,
EuiSpacer,
EuiSuperSelect,
EuiSwitch,
EuiSwitchEvent,
EuiButtonGroup,
toSentenceCase,
Direction,
} from '@elastic/eui';
import { css } from '@emotion/react';
import {
getCompatibleSortingTypes,
sortDirections,
DEFAULT_SORT,
OptionsListSortBy,
} from '../../../common/options_list/suggestions_sorting';
@ -49,8 +42,6 @@ interface SwitchProps {
onChange: (event: EuiSwitchEvent) => void;
}
type SortItem = EuiSuperSelectOption<OptionsListSortBy>;
export const OptionsListEditorOptions = ({
initialInput,
onChange,
@ -74,28 +65,6 @@ export const OptionsListEditorOptions = ({
}
}, [fieldType, onChange, state.sortBy]);
const sortByOptions: SortItem[] = useMemo(() => {
return getCompatibleSortingTypes(fieldType).map((key: OptionsListSortBy) => {
return {
value: key,
inputDisplay: OptionsListStrings.editorAndPopover.sortBy[key].getSortByLabel(),
'data-test-subj': `optionsListEditor__sortBy_${key}`,
};
});
}, [fieldType]);
const sortOrderOptions = useMemo(() => {
return sortDirections.map((key) => {
return {
id: key,
value: key,
iconType: `sort${toSentenceCase(key)}ending`,
'data-test-subj': `optionsListEditor__sortOrder_${key}`,
label: OptionsListStrings.editorAndPopover.sortOrder[key].getSortOrderLabel(),
};
});
}, []);
const SwitchWithTooltip = ({
switchProps,
label,
@ -133,98 +102,6 @@ export const OptionsListEditorOptions = ({
data-test-subj={'optionsListControl__allowMultipleAdditionalSetting'}
/>
</EuiFormRow>
<EuiFormRow>
<EuiSwitch
label={OptionsListStrings.editor.getHideExcludeTitle()}
checked={!state.hideExclude}
onChange={() => {
onChange({ hideExclude: !state.hideExclude });
setState((s) => ({ ...s, hideExclude: !s.hideExclude }));
if (initialInput?.exclude) onChange({ exclude: false });
}}
data-test-subj={'optionsListControl__hideExcludeAdditionalSetting'}
/>
</EuiFormRow>
<EuiFormRow>
<SwitchWithTooltip
label={OptionsListStrings.editor.getHideExistsQueryTitle()}
tooltip={OptionsListStrings.editor.getHideExistsQueryTooltip()}
switchProps={{
checked: !state.hideExists,
onChange: () => {
onChange({ hideExists: !state.hideExists });
setState((s) => ({ ...s, hideExists: !s.hideExists }));
if (initialInput?.existsSelected) onChange({ existsSelected: false });
},
}}
data-test-subj={'optionsListControl__hideExistsAdditionalSetting'}
/>
</EuiFormRow>
<EuiFormRow>
<>
<EuiSwitch
label={OptionsListStrings.editor.getHideSortingTitle()}
checked={!state.hideSort}
onChange={() => {
onChange({ hideSort: !state.hideSort });
setState((s) => ({ ...s, hideSort: !s.hideSort }));
}}
data-test-subj={'optionsListControl__hideSortAdditionalSetting'}
/>
{state.hideSort && (
<EuiForm className="optionsList--hiddenEditorForm">
<>
<EuiSpacer size="s" />
<EuiFormRow
display={'rowCompressed'}
label={OptionsListStrings.editor.getSuggestionsSortingTitle()}
>
<EuiButtonGroup
buttonSize="compressed"
options={sortOrderOptions}
idSelected={state.sortDirection}
onChange={(value) => {
onChange({
sort: {
direction: value as Direction,
by: state.sortBy,
},
});
setState((s) => ({ ...s, sortDirection: value as Direction }));
}}
legend={OptionsListStrings.editorAndPopover.getSortDirectionLegend()}
/>
</EuiFormRow>
<EuiFormRow
display={'rowCompressed'}
css={css`
margin-top: 8px !important;
`}
hasEmptyLabelSpace={false}
>
<EuiSuperSelect
onChange={(value) => {
onChange({
sort: {
direction: state.sortDirection,
by: value,
},
});
setState((s) => ({ ...s, sortBy: value }));
}}
options={sortByOptions}
valueOfSelected={state.sortBy}
data-test-subj={'optionsListControl__chooseSortBy'}
compressed={true}
/>
</EuiFormRow>
<EuiSpacer size="s" />
</>
</EuiForm>
)}
</>
</EuiFormRow>
<EuiFormRow>
<SwitchWithTooltip
label={OptionsListStrings.editor.getRunPastTimeoutTitle()}

View file

@ -41,27 +41,6 @@ export const OptionsListStrings = {
defaultMessage:
'Wait to display results until the list is complete. This setting is useful for large data sets, but the results might take longer to populate.',
}),
getHideExcludeTitle: () =>
i18n.translate('controls.optionsList.editor.hideExclude', {
defaultMessage: 'Allow selections to be excluded',
}),
getHideExistsQueryTitle: () =>
i18n.translate('controls.optionsList.editor.hideExistsQuery', {
defaultMessage: 'Allow exists query',
}),
getHideExistsQueryTooltip: () =>
i18n.translate('controls.optionsList.editor.hideExistsQueryTooltip', {
defaultMessage:
'Allows you to create an exists query, which returns all documents that contain an indexed value for the field.',
}),
getHideSortingTitle: () =>
i18n.translate('controls.optionsList.editor.hideSort', {
defaultMessage: 'Allow dynamic sorting of suggestions',
}),
getSuggestionsSortingTitle: () =>
i18n.translate('controls.optionsList.editor.suggestionsSorting', {
defaultMessage: 'Default sort order',
}),
},
popover: {
getAriaLabel: (fieldName: string) =>

View file

@ -221,48 +221,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dashboardControls.controlEditorCancel(true);
});
it('can create control with non-default sorting', async () => {
await dashboardControls.createControl({
controlType: OPTIONS_LIST_CONTROL,
dataViewTitle: 'animals-*',
fieldName: 'sound.keyword',
additionalSettings: {
hideSort: true,
defaultSortType: { by: '_key', direction: 'asc' },
},
});
controlId = (await dashboardControls.getAllControlIds())[1];
expect(await dashboardControls.getControlsCount()).to.be(2);
await dashboardControls.optionsListOpenPopover(controlId);
await ensureAvailableOptionsEql([...animalSoundAvailableOptions].sort(), true);
await dashboardControls.optionsListEnsurePopoverIsClosed(controlId);
});
it('can edit default sorting method', async () => {
await dashboardControls.editExistingControl(controlId);
expect(await testSubjects.getVisibleText('optionsListControl__chooseSortBy')).to.equal(
'Alphabetically'
);
const ascendingButtonSelected = await (
await testSubjects.find('optionsListEditor__sortOrder_asc')
).elementHasClass('uiButtonGroupButton-isSelected');
expect(ascendingButtonSelected).to.be(true);
const descendingButtonSelected = await (
await testSubjects.find('optionsListEditor__sortOrder_desc')
).elementHasClass('uiButtonGroupButton-isSelected');
expect(descendingButtonSelected).to.be(false);
await dashboardControls.optionsListSetAdditionalSettings({
defaultSortType: { by: '_key', direction: 'desc' },
});
await dashboardControls.controlEditorSave();
await dashboardControls.optionsListOpenPopover(controlId);
await ensureAvailableOptionsEql([...animalSoundAvailableOptions].sort().reverse(), true);
await dashboardControls.optionsListEnsurePopoverIsClosed(controlId);
});
after(async () => {
await dashboardControls.clearAllControls();
});

View file

@ -331,25 +331,13 @@ export class DashboardPageControls extends FtrService {
// Options list functions
public async optionsListSetAdditionalSettings({
defaultSortType,
ignoreTimeout,
allowMultiple,
hideExclude,
hideExists,
hideSort,
}: OptionsListAdditionalSettings) {
const getSettingTestSubject = (setting: string) =>
`optionsListControl__${setting}AdditionalSetting`;
if (allowMultiple) await this.testSubjects.click(getSettingTestSubject('allowMultiple'));
if (hideExclude) await this.testSubjects.click(getSettingTestSubject('hideExclude'));
if (hideExists) await this.testSubjects.click(getSettingTestSubject('hideExists'));
if (hideSort) await this.testSubjects.click(getSettingTestSubject('hideSort'));
if (defaultSortType) {
await this.testSubjects.click(`optionsListEditor__sortOrder_${defaultSortType.direction}`);
await this.testSubjects.click('optionsListControl__chooseSortBy');
await this.testSubjects.click(`optionsListEditor__sortBy_${defaultSortType.by}`);
}
if (ignoreTimeout) await this.testSubjects.click(getSettingTestSubject('runPastTimeout'));
}

View file

@ -447,9 +447,6 @@
"controls.optionsList.description": "Ajoutez un menu pour la sélection de valeurs de champ.",
"controls.optionsList.displayName": "Liste des options",
"controls.optionsList.editor.allowMultiselectTitle": "Permettre des sélections multiples dans une liste déroulante",
"controls.optionsList.editor.hideExclude": "Autoriser l'exclusion des sélections",
"controls.optionsList.editor.hideExistsQuery": "Autoriser les requêtes \"existe\"",
"controls.optionsList.editor.hideExistsQueryTooltip": "Vous permet de créer une requête \"existe\", qui retourne tous les documents contenant une valeur indexée pour le champ.",
"controls.optionsList.editor.runPastTimeout": "Ignorer le délai d'expiration pour les résultats",
"controls.optionsList.editor.runPastTimeout.tooltip": "Attendre que la liste soit complète pour afficher les résultats. Ce paramètre est utile pour les ensembles de données volumineux, mais le remplissage des résultats peut prendre plus de temps.",
"controls.optionsList.popover.allOptionsTitle": "Afficher toutes les options",

View file

@ -449,9 +449,6 @@
"controls.optionsList.description": "フィールド値を選択するメニューを追加",
"controls.optionsList.displayName": "オプションリスト",
"controls.optionsList.editor.allowMultiselectTitle": "ドロップダウンでの複数選択を許可",
"controls.optionsList.editor.hideExclude": "選択項目の実行を許可",
"controls.optionsList.editor.hideExistsQuery": "existsクエリを許可",
"controls.optionsList.editor.hideExistsQueryTooltip": "existsクエリを作成できます。このクエリは、フィールドのインデックスが作成された値を含むすべてのドキュメントを返します。",
"controls.optionsList.editor.runPastTimeout": "結果のタイムアウトを無視",
"controls.optionsList.editor.runPastTimeout.tooltip": "リストが入力されるまで待機してから、結果を表示します。この設定は大きいデータセットで有用です。ただし、結果の入力に時間がかかる場合があります。",
"controls.optionsList.popover.allOptionsTitle": "すべてのオプションを表示",

View file

@ -449,9 +449,6 @@
"controls.optionsList.description": "添加用于选择字段值的菜单。",
"controls.optionsList.displayName": "选项列表",
"controls.optionsList.editor.allowMultiselectTitle": "下拉列表中允许多选",
"controls.optionsList.editor.hideExclude": "允许排除所选内容",
"controls.optionsList.editor.hideExistsQuery": "允许存在查询",
"controls.optionsList.editor.hideExistsQueryTooltip": "允许您创建存在查询,它将返回包含字段的索引值的所有文档。",
"controls.optionsList.editor.runPastTimeout": "忽略超时以获取结果",
"controls.optionsList.editor.runPastTimeout.tooltip": "等待显示结果,直到列表完成。此设置用于大型数据集,但可能需要更长时间来填充结果。",
"controls.optionsList.popover.allOptionsTitle": "显示所有选项",