-
Notifications
You must be signed in to change notification settings - Fork 8.5k
[Controls] Bulk select for options list control #221010
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
cqliu1
merged 36 commits into
elastic:main
from
cqliu1:controls/options-list/select-deselect-all
Jun 26, 2025
Merged
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
fefe947
Add Select all and Deselect all buttons to options list popover
cqliu1 080d7d7
Remove dup types
cqliu1 3b75d48
Merge branch 'main' of https://github.com/elastic/kibana into control…
cqliu1 0bf6334
Change to EuiButtonEmpty to EuiLink for bulk selection buttons
cqliu1 b3cec9a
Remove filter in select all
cqliu1 1d56acc
Disable select and deselect when is enabled
cqliu1 b9109fb
Update test
cqliu1 c5c2673
Clean up handleBulkAction
cqliu1 48967f3
Fix select all
cqliu1 5699c18
Add spacing
cqliu1 b287c99
Fix alignment in action bar
cqliu1 609a32b
Move invalid selection count from action bar to invalid selections co…
cqliu1 42c6087
Remove unused invalid selections usage in action bar
cqliu1 a56e09b
Merge branch 'main' of https://github.com/elastic/kibana into control…
cqliu1 c435182
Merge branch 'main' into controls/options-list/select-deselect-all
cqliu1 ce31f29
Merge branch 'controls/options-list/select-deselect-all' of https://g…
cqliu1 04ca74d
Clean up code
cqliu1 2f2ed5f
Add aria-label to options list popover search filter
cqliu1 e925d35
Add invalid selection count to title
cqliu1 dac3d79
switch to checkbox
cqliu1 40efd4a
Add curly brackets
cqliu1 73855a4
Merge branch 'main' into controls/options-list/select-deselect-all
cqliu1 dd6ce97
Fix select all checkbox
cqliu1 670edb8
Fix select all checkbox label
cqliu1 b16f91b
Add invalid selection count to header
cqliu1 5567aa1
Merge branch 'main' of https://github.com/elastic/kibana into control…
cqliu1 6a739eb
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine 3edfa43
Remove scss file
cqliu1 58905a8
Add padding between cardinality and select all
cqliu1 890f9ca
Enable virtualization on EuiSelectable for invalid selections
cqliu1 4726e3a
Merge branch 'controls/options-list/select-deselect-all' of https://g…
cqliu1 52466b3
Merge branch 'main' of https://github.com/elastic/kibana into control…
cqliu1 d3847e6
Merge branch 'main' of https://github.com/elastic/kibana into control…
cqliu1 a647172
Merge branch 'main' into controls/options-list/select-deselect-all
cqliu1 74b472b
make the test more user focused
mbondyra 2d3adff
Update options_list_popover_action_bar.test.tsx
mbondyra File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
...ls/data_controls/options_list_control/components/options_list_popover_action_bar.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| /* | ||
| * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| import React from 'react'; | ||
| import { render, screen } from '@testing-library/react'; | ||
| import { take } from 'lodash'; | ||
| import { getOptionsListContextMock } from '../../mocks/api_mocks'; | ||
| import { OptionsListControlContext } from '../options_list_context_provider'; | ||
| import type { OptionsListComponentApi } from '../types'; | ||
| import { OptionsListPopoverActionBar } from './options_list_popover_action_bar'; | ||
| import { OptionsListDisplaySettings } from '../../../../../common/options_list'; | ||
| import { MAX_OPTIONS_LIST_BULK_SELECT_SIZE } from '../constants'; | ||
|
|
||
| const allOptions = [ | ||
| { value: 'moo', docCount: 1 }, | ||
| { value: 'tweet', docCount: 2 }, | ||
| { value: 'oink', docCount: 3 }, | ||
| { value: 'bark', docCount: 4 }, | ||
| { value: 'meow', docCount: 5 }, | ||
| { value: 'woof', docCount: 6 }, | ||
| { value: 'roar', docCount: 7 }, | ||
| { value: 'honk', docCount: 8 }, | ||
| { value: 'beep', docCount: 9 }, | ||
| { value: 'chirp', docCount: 10 }, | ||
| { value: 'baa', docCount: 11 }, | ||
| { value: 'toot', docCount: 11 }, | ||
| ]; | ||
|
|
||
| const renderComponent = ({ | ||
| componentApi, | ||
| displaySettings, | ||
| showOnlySelected, | ||
| }: { | ||
| componentApi: OptionsListComponentApi; | ||
| displaySettings: OptionsListDisplaySettings; | ||
| showOnlySelected?: boolean; | ||
| }) => { | ||
| return render( | ||
| <OptionsListControlContext.Provider | ||
| value={{ | ||
| componentApi, | ||
| displaySettings, | ||
| }} | ||
| > | ||
| <OptionsListPopoverActionBar | ||
| showOnlySelected={showOnlySelected ?? false} | ||
| setShowOnlySelected={() => {}} | ||
| /> | ||
| </OptionsListControlContext.Provider> | ||
| ); | ||
| }; | ||
|
|
||
| const getSelectAllCheckbox = () => screen.getByRole('checkbox', { name: /Select all/i }); | ||
|
|
||
| const getSearchInput = () => screen.getByRole('searchbox', { name: /Filter suggestions/i }); | ||
|
|
||
| describe('Options list popover', () => { | ||
| test('displays search input', async () => { | ||
| const contextMock = getOptionsListContextMock(); | ||
| contextMock.componentApi.setTotalCardinality(allOptions.length); | ||
| contextMock.componentApi.setAvailableOptions(take(allOptions, 5)); | ||
| contextMock.componentApi.setSearchString('moo'); | ||
| renderComponent(contextMock); | ||
|
|
||
| expect(getSearchInput()).toBeEnabled(); | ||
| expect(getSearchInput()).toHaveValue('moo'); | ||
| }); | ||
|
|
||
| test('displays total cardinality for available options', async () => { | ||
| const contextMock = getOptionsListContextMock(); | ||
| contextMock.componentApi.setTotalCardinality(allOptions.length); | ||
| contextMock.componentApi.setAvailableOptions(take(allOptions, 5)); | ||
| renderComponent(contextMock); | ||
|
|
||
| expect(screen.getByTestId('optionsList-cardinality-label')).toHaveTextContent( | ||
| allOptions.length.toString() | ||
| ); | ||
| }); | ||
|
|
||
| test('displays "Select all" checkbox next to total cardinality', async () => { | ||
| const contextMock = getOptionsListContextMock(); | ||
| contextMock.componentApi.setTotalCardinality(80); | ||
| contextMock.componentApi.setAvailableOptions(take(allOptions, 10)); | ||
| renderComponent(contextMock); | ||
|
|
||
| expect(getSelectAllCheckbox()).toBeEnabled(); | ||
| expect(getSelectAllCheckbox()).not.toBeChecked(); | ||
| }); | ||
|
|
||
| test('Select all is checked when all available options are selected ', async () => { | ||
| const contextMock = getOptionsListContextMock(); | ||
| contextMock.componentApi.setTotalCardinality(80); | ||
| contextMock.componentApi.setAvailableOptions([{ value: 'moo', docCount: 1 }]); | ||
| contextMock.componentApi.setSelectedOptions(['moo']); | ||
| renderComponent(contextMock); | ||
|
|
||
| expect(getSelectAllCheckbox()).toBeEnabled(); | ||
| expect(getSelectAllCheckbox()).toBeChecked(); | ||
| }); | ||
|
|
||
| test('bulk selections are disabled when there are more than 100 available options', async () => { | ||
| const contextMock = getOptionsListContextMock(); | ||
| contextMock.componentApi.setTotalCardinality(MAX_OPTIONS_LIST_BULK_SELECT_SIZE + 1); | ||
| contextMock.componentApi.setAvailableOptions(take(allOptions, 10)); | ||
| renderComponent(contextMock); | ||
|
|
||
| expect(getSelectAllCheckbox()).toBeDisabled(); | ||
| }); | ||
|
|
||
| test('bulk selections are disabled when there are no available options', async () => { | ||
| const contextMock = getOptionsListContextMock(); | ||
| contextMock.componentApi.setTotalCardinality(0); | ||
| contextMock.componentApi.setAvailableOptions([]); | ||
| renderComponent(contextMock); | ||
|
|
||
| expect(getSelectAllCheckbox()).toBeDisabled(); | ||
| }); | ||
|
|
||
| test('bulk selections are disabled when showOnlySelected is true', async () => { | ||
| const contextMock = getOptionsListContextMock(); | ||
| contextMock.componentApi.setTotalCardinality(0); | ||
| contextMock.componentApi.setAvailableOptions([]); | ||
| renderComponent({ ...contextMock, showOnlySelected: true }); | ||
|
|
||
| expect(getSelectAllCheckbox()).toBeDisabled(); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,6 +57,11 @@ export const OptionsListPopoverInvalidSelections = () => { | |
| key: String(key), | ||
| label: fieldFormatter(key), | ||
| checked: 'on', | ||
| css: css` | ||
| .euiSelectableListItem__prepend { | ||
| margin-inline-end: 0; | ||
| } | ||
| `, | ||
| className: 'optionsList__selectionInvalid', | ||
| 'data-test-subj': `optionsList-control-invalid-selection-${key}`, | ||
| prepend: ( | ||
|
|
@@ -76,15 +81,15 @@ export const OptionsListPopoverInvalidSelections = () => { | |
| <> | ||
| <EuiSpacer size="s" /> | ||
| <EuiTitle size="xxs" data-test-subj="optionList__invalidSelectionLabel" css={styles.title}> | ||
| <EuiFlexGroup gutterSize="s" alignItems="center"> | ||
| <EuiFlexGroup gutterSize="s" alignItems="center" justifyContent="flexStart"> | ||
| <EuiFlexItem grow={false}> | ||
| <EuiIcon | ||
| type="warning" | ||
| title={OptionsListStrings.popover.getInvalidSelectionScreenReaderText()} | ||
| size="s" | ||
| /> | ||
| </EuiFlexItem> | ||
| <EuiFlexItem> | ||
| <EuiFlexItem grow={false}> | ||
| <label> | ||
| {OptionsListStrings.popover.getInvalidSelectionsSectionTitle(invalidSelections.size)} | ||
| </label> | ||
|
|
@@ -97,7 +102,7 @@ export const OptionsListPopoverInvalidSelections = () => { | |
| invalidSelections.size | ||
| )} | ||
| options={selectableOptions} | ||
| listProps={{ onFocusBadge: false, isVirtualized: false }} | ||
| listProps={{ onFocusBadge: false }} | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By enable virtualization in the Jun-20-2025.16-34-52.mp4 |
||
| onChange={(newSuggestions, _, changedOption) => { | ||
| setSelectableOptions(newSuggestions); | ||
| componentApi.deselectOption(changedOption.key); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some edits to these tests to make it more user-focused, like using selectors like
getByRole, notgetByTestId. or usinggetselectors and notqueryselectors (query should only be used when checking for non-existance, since they have worse messaging)