|
1 | | -import React, {FC, useContext, useState} from 'react' |
| 1 | +import React, {FC, Fragment, ChangeEvent, useContext, useState} from 'react' |
| 2 | +import { |
| 3 | + TechnoSpinner, |
| 4 | + ComponentSize, |
| 5 | + Dropdown, |
| 6 | + DropdownMenuTheme, |
| 7 | + IconFont, |
| 8 | + Input, |
| 9 | +} from '@influxdata/clockface' |
2 | 10 |
|
3 | 11 | // Components |
4 | | -import {ComponentStatus} from '@influxdata/clockface' |
5 | 12 | import SelectorTitle from 'src/dataExplorer/components/SelectorTitle' |
6 | | -import SearchableDropdown from 'src/shared/components/SearchableDropdown' |
7 | 13 |
|
8 | 14 | // Contexts |
9 | 15 | import {FluxQueryBuilderContext} from 'src/dataExplorer/context/fluxQueryBuilder' |
10 | 16 | import {BucketContext} from 'src/shared/contexts/buckets' |
11 | 17 | import {event} from 'src/cloud/utils/reporting' |
12 | 18 |
|
| 19 | +// Types |
| 20 | +import {RemoteDataState, Bucket} from 'src/types' |
| 21 | + |
13 | 22 | const BUCKET_TOOLTIP = `A bucket is a named location where time series data \ |
14 | 23 | is stored. You can think of a bucket like you would a database in SQL systems.` |
15 | 24 |
|
| 25 | +const REMAP_BUCKET_TYPES = { |
| 26 | + user: 'My Data', |
| 27 | + system: 'System Data', |
| 28 | + sample: 'Sample Data', |
| 29 | +} |
| 30 | + |
16 | 31 | const BucketSelector: FC = () => { |
17 | 32 | const {selectedBucket, selectBucket} = useContext(FluxQueryBuilderContext) |
18 | | - const {buckets} = useContext(BucketContext) |
| 33 | + const {loading, buckets} = useContext(BucketContext) |
| 34 | + const [isSearchActive, setIsSearchActive] = useState(false) |
19 | 35 | const [searchTerm, setSearchTerm] = useState('') |
20 | 36 |
|
21 | | - const handleSelectBucket = (name: string) => { |
22 | | - const bucket = buckets.find(b => b.name === name) |
23 | | - if (!bucket) { |
24 | | - return |
25 | | - } |
| 37 | + const _buckets = buckets.filter(b => |
| 38 | + `${b.name}`.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase()) |
| 39 | + ) |
| 40 | + |
| 41 | + const handleSelectBucket = (buck: Bucket) => { |
26 | 42 | event('bucketSelected', {search: searchTerm.length}) |
27 | | - selectBucket(bucket) |
| 43 | + selectBucket(buck) |
| 44 | + } |
| 45 | + |
| 46 | + const handleChange = (e: ChangeEvent<HTMLInputElement>): void => { |
| 47 | + setSearchTerm(e.target.value) |
| 48 | + } |
| 49 | + |
| 50 | + let buttonText = 'Loading buckets...' |
| 51 | + if (loading === RemoteDataState.Done && !selectedBucket?.name) { |
| 52 | + buttonText = 'Select bucket...' |
| 53 | + } else if (loading === RemoteDataState.Done && selectedBucket?.name) { |
| 54 | + buttonText = selectedBucket.name |
28 | 55 | } |
29 | 56 |
|
30 | | - const handleChangeSearchTerm = (value: string) => { |
31 | | - setSearchTerm(value) |
| 57 | + const button = (active, onClick) => ( |
| 58 | + <Dropdown.Button |
| 59 | + onClick={onClick} |
| 60 | + active={active} |
| 61 | + testID="bucket-selector--dropdown-button" |
| 62 | + > |
| 63 | + {buttonText} |
| 64 | + </Dropdown.Button> |
| 65 | + ) |
| 66 | + |
| 67 | + if (loading !== RemoteDataState.Done) { |
| 68 | + return ( |
| 69 | + <div> |
| 70 | + <SelectorTitle title="Bucket" info={BUCKET_TOOLTIP} /> |
| 71 | + <Dropdown |
| 72 | + button={button} |
| 73 | + menu={onCollapse => ( |
| 74 | + <Dropdown.Menu onCollapse={onCollapse}> |
| 75 | + <Dropdown.ItemEmpty> |
| 76 | + <TechnoSpinner |
| 77 | + strokeWidth={ComponentSize.Small} |
| 78 | + diameterPixels={32} |
| 79 | + /> |
| 80 | + </Dropdown.ItemEmpty> |
| 81 | + </Dropdown.Menu> |
| 82 | + )} |
| 83 | + /> |
| 84 | + </div> |
| 85 | + ) |
32 | 86 | } |
33 | 87 |
|
| 88 | + if (!_buckets.length) { |
| 89 | + return ( |
| 90 | + <div> |
| 91 | + <SelectorTitle title="Bucket" info={BUCKET_TOOLTIP} /> |
| 92 | + <Dropdown |
| 93 | + button={button} |
| 94 | + menu={onCollapse => ( |
| 95 | + <Dropdown.Menu onCollapse={onCollapse}> |
| 96 | + <Dropdown.ItemEmpty>No Buckets Available</Dropdown.ItemEmpty> |
| 97 | + </Dropdown.Menu> |
| 98 | + )} |
| 99 | + /> |
| 100 | + </div> |
| 101 | + ) |
| 102 | + } |
| 103 | + |
| 104 | + const body = Object.entries( |
| 105 | + _buckets.reduce((acc, curr) => { |
| 106 | + if (!acc[curr.type]) { |
| 107 | + acc[curr.type] = [] |
| 108 | + } |
| 109 | + |
| 110 | + acc[curr.type].push(curr) |
| 111 | + return acc |
| 112 | + }, {}) as Record<string, Bucket[]> |
| 113 | + ).map(([k, v]) => { |
| 114 | + const items = v.map(bucket => ( |
| 115 | + <Dropdown.Item |
| 116 | + key={bucket.name} |
| 117 | + value={bucket} |
| 118 | + onClick={handleSelectBucket} |
| 119 | + selected={bucket.name === selectedBucket?.name} |
| 120 | + title={bucket.name} |
| 121 | + wrapText={true} |
| 122 | + testID={`bucket-selector--dropdown--${bucket.name}`} |
| 123 | + > |
| 124 | + {bucket.name} |
| 125 | + </Dropdown.Item> |
| 126 | + )) |
| 127 | + |
| 128 | + let name = k |
| 129 | + |
| 130 | + if (REMAP_BUCKET_TYPES.hasOwnProperty(k)) { |
| 131 | + name = REMAP_BUCKET_TYPES[k] |
| 132 | + } |
| 133 | + |
| 134 | + return ( |
| 135 | + <Fragment key={name}> |
| 136 | + <Dropdown.Divider text={name} /> |
| 137 | + {items} |
| 138 | + </Fragment> |
| 139 | + ) |
| 140 | + }) |
| 141 | + |
34 | 142 | return ( |
35 | 143 | <div> |
36 | 144 | <SelectorTitle title="Bucket" info={BUCKET_TOOLTIP} /> |
37 | | - <SearchableDropdown |
38 | | - searchTerm={searchTerm} |
39 | | - searchPlaceholder="Search buckets" |
40 | | - selectedOption={selectedBucket?.name || 'Select bucket...'} |
41 | | - onSelect={handleSelectBucket} |
42 | | - onChangeSearchTerm={handleChangeSearchTerm} |
43 | | - options={buckets.map(b => b.name)} |
44 | | - buttonStatus={ComponentStatus.Default} |
45 | | - testID="bucket-selector--dropdown" |
46 | | - buttonTestID="bucket-selector--dropdown-button" |
47 | | - menuTestID="bucket-selector--dropdown-menu" |
48 | | - emptyText="No Buckets Found" |
| 145 | + <Dropdown |
| 146 | + button={button} |
| 147 | + menu={onCollapse => ( |
| 148 | + <Dropdown.Menu |
| 149 | + onCollapse={() => { |
| 150 | + if (isSearchActive === false) { |
| 151 | + onCollapse() |
| 152 | + } |
| 153 | + }} |
| 154 | + theme={DropdownMenuTheme.Onyx} |
| 155 | + testID="searchable-dropdown--menu" |
| 156 | + > |
| 157 | + <div className="searchable-dropdown--input-container"> |
| 158 | + <Input |
| 159 | + icon={IconFont.Search_New} |
| 160 | + onFocus={() => setIsSearchActive(true)} |
| 161 | + onChange={handleChange} |
| 162 | + onBlur={() => setIsSearchActive(false)} |
| 163 | + value={searchTerm} |
| 164 | + placeholder="Search buckets" |
| 165 | + size={ComponentSize.Small} |
| 166 | + autoFocus={true} |
| 167 | + /> |
| 168 | + </div> |
| 169 | + {body} |
| 170 | + </Dropdown.Menu> |
| 171 | + )} |
49 | 172 | /> |
50 | 173 | </div> |
51 | 174 | ) |
|
0 commit comments