Skip to content

Commit 263c283

Browse files
committed
Add Select all and Deselect all buttons to options list popover
Fix select and deselect logic Fix types Fetch more options on select/deselect all Add loadMoreOptions callback Add const for bulk selection limit Add i18n Remove unused import
1 parent d2325ce commit 263c283

File tree

5 files changed

+133
-3
lines changed

5 files changed

+133
-3
lines changed

src/platform/plugins/shared/controls/public/controls/data_controls/options_list_control/components/options_list_popover_action_bar.tsx

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
import React, { useMemo } from 'react';
10+
import React, { useCallback, useMemo } from 'react';
1111

1212
import {
1313
EuiButtonIcon,
1414
EuiFieldSearch,
1515
EuiFlexGroup,
1616
EuiFlexItem,
1717
EuiFormRow,
18+
EuiLink,
1819
EuiText,
1920
EuiToolTip,
2021
} from '@elastic/eui';
@@ -27,6 +28,7 @@ import { getCompatibleSearchTechniques } from '../../../../../common/options_lis
2728
import { useOptionsListContext } from '../options_list_context_provider';
2829
import { OptionsListPopoverSortingButton } from './options_list_popover_sorting_button';
2930
import { OptionsListStrings } from '../options_list_strings';
31+
import { MAX_OPTIONS_LIST_BULK_SELECT_SIZE, MAX_OPTIONS_LIST_REQUEST_SIZE } from '../constants';
3032

3133
interface OptionsListPopoverProps {
3234
showOnlySelected: boolean;
@@ -50,13 +52,15 @@ export const OptionsListPopoverActionBar = ({
5052
totalCardinality,
5153
field,
5254
allowExpensiveQueries,
55+
availableOptions = [],
5356
] = useBatchedPublishingSubjects(
5457
componentApi.searchTechnique$,
5558
componentApi.searchStringValid$,
5659
componentApi.invalidSelections$,
5760
componentApi.totalCardinality$,
5861
componentApi.field$,
59-
componentApi.parentApi.allowExpensiveQueries$
62+
componentApi.parentApi.allowExpensiveQueries$,
63+
componentApi.availableOptions$
6064
);
6165

6266
const compatibleSearchTechniques = useMemo(() => {
@@ -69,6 +73,11 @@ export const OptionsListPopoverActionBar = ({
6973
[searchTechnique, compatibleSearchTechniques]
7074
);
7175

76+
const loadMoreOptions = useCallback(() => {
77+
componentApi.setRequestSize(Math.min(totalCardinality, MAX_OPTIONS_LIST_REQUEST_SIZE));
78+
componentApi.loadMoreSubject.next(); // trigger refetch with loadMoreSubject
79+
}, [componentApi, totalCardinality]);
80+
7281
return (
7382
<div className="optionsList__actions">
7483
{compatibleSearchTechniques.length > 0 && (
@@ -99,7 +108,81 @@ export const OptionsListPopoverActionBar = ({
99108
{allowExpensiveQueries && (
100109
<EuiFlexItem grow={false}>
101110
<EuiText size="xs" color="subdued" data-test-subj="optionsList-cardinality-label">
102-
{OptionsListStrings.popover.getCardinalityLabel(totalCardinality)}
111+
{OptionsListStrings.popover.getCardinalityLabel(totalCardinality)} |{' '}
112+
<EuiToolTip
113+
content={
114+
totalCardinality > MAX_OPTIONS_LIST_BULK_SELECT_SIZE
115+
? OptionsListStrings.popover.getMaximumBulkSelectionTooltip()
116+
: undefined
117+
}
118+
>
119+
<EuiLink
120+
disabled={
121+
availableOptions.length < 1 ||
122+
totalCardinality < 1 ||
123+
totalCardinality > MAX_OPTIONS_LIST_BULK_SELECT_SIZE
124+
}
125+
onClick={() => {
126+
if (totalCardinality > availableOptions.length) {
127+
loadMoreOptions();
128+
129+
const subscription = componentApi.availableOptions$.subscribe(
130+
(options = []) => {
131+
if (options.length === totalCardinality) {
132+
componentApi.selectAll(
133+
options.map(({ value }) => value as string) ?? []
134+
);
135+
subscription.unsubscribe();
136+
}
137+
}
138+
);
139+
} else {
140+
componentApi.selectAll(
141+
availableOptions.map(({ value }) => value as string) ?? []
142+
);
143+
}
144+
}}
145+
>
146+
{OptionsListStrings.popover.getSelectAllButtonLabel()}
147+
</EuiLink>
148+
</EuiToolTip>{' '}
149+
<EuiToolTip
150+
content={
151+
totalCardinality > 100
152+
? OptionsListStrings.popover.getMaximumBulkSelectionTooltip()
153+
: undefined
154+
}
155+
>
156+
<EuiLink
157+
disabled={
158+
availableOptions.length < 1 ||
159+
totalCardinality < 1 ||
160+
totalCardinality > MAX_OPTIONS_LIST_BULK_SELECT_SIZE
161+
}
162+
onClick={() => {
163+
if (totalCardinality > availableOptions.length) {
164+
loadMoreOptions();
165+
166+
const subscription = componentApi.availableOptions$.subscribe(
167+
(options = []) => {
168+
if (options.length === totalCardinality) {
169+
componentApi.deselectAll(
170+
options.map(({ value }) => value as string) ?? []
171+
);
172+
subscription.unsubscribe();
173+
}
174+
}
175+
);
176+
} else {
177+
componentApi.deselectAll(
178+
availableOptions.map(({ value }) => value as string) ?? []
179+
);
180+
}
181+
}}
182+
>
183+
{OptionsListStrings.popover.getDeselectAllButtonLabel()}
184+
</EuiLink>
185+
</EuiToolTip>
103186
</EuiText>
104187
</EuiFlexItem>
105188
)}

src/platform/plugins/shared/controls/public/controls/data_controls/options_list_control/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ export const OPTIONS_LIST_DEFAULT_SORT: OptionsListSortingType = {
2020

2121
export const MIN_OPTIONS_LIST_REQUEST_SIZE = 10;
2222
export const MAX_OPTIONS_LIST_REQUEST_SIZE = 1000;
23+
export const MAX_OPTIONS_LIST_BULK_SELECT_SIZE = 100;

src/platform/plugins/shared/controls/public/controls/data_controls/options_list_control/get_options_list_control_factory.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,34 @@ export const getOptionsListControlFactory = (): DataControlFactory<
398398
setSort: (sort: OptionsListSortingType | undefined) => {
399399
sort$.next(sort);
400400
},
401+
selectAll: (keys: string[]) => {
402+
const field = api.field$.getValue();
403+
if (keys.length < 1 || !field) {
404+
api.setBlockingError(
405+
new Error(OptionsListStrings.control.getInvalidSelectionMessage())
406+
);
407+
return;
408+
}
409+
410+
const selectedOptions = selectionsManager.api.selectedOptions$.getValue() ?? [];
411+
const newSelections = keys.filter((value) => !selectedOptions.includes(value as string));
412+
selectionsManager.api.setSelectedOptions([...selectedOptions, ...newSelections]);
413+
},
414+
deselectAll: (keys: string[]) => {
415+
const field = api.field$.getValue();
416+
if (keys.length < 1 || !field) {
417+
api.setBlockingError(
418+
new Error(OptionsListStrings.control.getInvalidSelectionMessage())
419+
);
420+
return;
421+
}
422+
423+
const selectedOptions = selectionsManager.api.selectedOptions$.getValue() ?? [];
424+
const remainingSelections = selectedOptions.filter(
425+
(option) => !keys.includes(option as string)
426+
);
427+
selectionsManager.api.setSelectedOptions(remainingSelections);
428+
},
401429
};
402430

403431
if (selectionsManager.api.hasInitialSelections) {

src/platform/plugins/shared/controls/public/controls/data_controls/options_list_control/options_list_strings.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import { i18n } from '@kbn/i18n';
1111
import { OptionsListSearchTechnique } from '../../../../common/options_list/suggestions_searching';
12+
import { MAX_OPTIONS_LIST_BULK_SELECT_SIZE } from './constants';
1213

1314
export const OptionsListStrings = {
1415
control: {
@@ -267,6 +268,19 @@ export const OptionsListStrings = {
267268
'Appears in {documentCount, number} {documentCount, plural, one {document} other {documents}}',
268269
values: { documentCount },
269270
}),
271+
getSelectAllButtonLabel: () =>
272+
i18n.translate('controls.optionsList.popover.selectAllButtonLabel', {
273+
defaultMessage: 'Select all',
274+
}),
275+
getDeselectAllButtonLabel: () =>
276+
i18n.translate('controls.optionsList.popover.deselectAllButtonLabel', {
277+
defaultMessage: 'Deselect all',
278+
}),
279+
getMaximumBulkSelectionTooltip: () =>
280+
i18n.translate('controls.optionsList.popover.maximumBulkSelectionTooltip', {
281+
defaultMessage: 'Bulk selection is only available for fewer than {maxOptions} options',
282+
values: { maxOptions: MAX_OPTIONS_LIST_BULK_SELECT_SIZE },
283+
}),
270284
},
271285
controlAndPopover: {
272286
getExists: (negate: number = +false) =>

src/platform/plugins/shared/controls/public/controls/data_controls/options_list_control/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ export type OptionsListComponentApi = OptionsListControlApi &
4949
StateManager<TemporaryState>['api'] & {
5050
deselectOption: (key: string | undefined) => void;
5151
makeSelection: (key: string | undefined, showOnlySelected: boolean) => void;
52+
selectAll: (keys: string[]) => void;
53+
deselectAll: (keys: string[]) => void;
5254
loadMoreSubject: Subject<void>;
5355
sort$: PublishingSubject<OptionsListSortingType | undefined>;
5456
setSort: (sort: OptionsListSortingType | undefined) => void;
57+
selectAll: (keys: string[]) => void;
58+
deselectAll: (keys: string[]) => void;
5559
};

0 commit comments

Comments
 (0)