@cdc/dashboard 4.25.3 → 4.25.5-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/Dynamic_Data.md +79 -0
  2. package/Override_Data.md +39 -0
  3. package/dist/cdcdashboard.js +67657 -70123
  4. package/examples/legend-issue-data.json +1874 -0
  5. package/examples/legend-issue.json +749 -0
  6. package/examples/map.json +628 -0
  7. package/index.html +1 -26
  8. package/package.json +10 -15
  9. package/src/CdcDashboardComponent.tsx +32 -5
  10. package/src/_stories/Dashboard.stories.tsx +2 -0
  11. package/src/_stories/_mock/api-filter-map.json +42 -1
  12. package/src/components/CollapsibleVisualizationRow.tsx +2 -2
  13. package/src/components/DashboardFilters/DashboardFilters.tsx +1 -1
  14. package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +129 -0
  15. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +680 -652
  16. package/src/components/DataDesignerModal.tsx +33 -14
  17. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +3 -0
  18. package/src/components/Row.tsx +2 -1
  19. package/src/components/VisualizationRow.tsx +1 -1
  20. package/src/helpers/reloadURLHelpers.ts +10 -2
  21. package/src/helpers/shouldLoadAllFilters.ts +30 -30
  22. package/src/index.tsx +2 -1
  23. package/src/scss/main.scss +0 -5
  24. package/src/store/dashboard.actions.ts +2 -0
  25. package/src/store/dashboard.reducer.ts +15 -0
  26. package/src/types/DataSet.ts +1 -0
  27. package/src/types/SharedFilter.ts +2 -0
  28. package/LICENSE +0 -201
  29. package/examples/private/DEV-10120.json +0 -1294
  30. package/examples/private/DEV-10527.json +0 -845
  31. package/examples/private/DEV-10586.json +0 -54319
  32. package/examples/private/DEV-10856.json +0 -54319
  33. package/examples/private/DEV-9199.json +0 -606
  34. package/examples/private/DEV-9644.json +0 -20092
  35. package/examples/private/DEV-9684.json +0 -2135
  36. package/examples/private/DEV-9932.json +0 -95
  37. package/examples/private/DEV-9989.json +0 -229
  38. package/examples/private/art-dashboard.json +0 -18174
  39. package/examples/private/art-scratch.json +0 -2406
  40. package/examples/private/bird-flu-2.json +0 -440
  41. package/examples/private/bird-flu.json +0 -413
  42. package/examples/private/dashboard-config-ehdi.json +0 -29915
  43. package/examples/private/dashboard-map-filter.json +0 -815
  44. package/examples/private/dashboard-margins.js +0 -15
  45. package/examples/private/dataset.json +0 -1452
  46. package/examples/private/dev-10856-2.json +0 -1348
  47. package/examples/private/ehdi-data.json +0 -29502
  48. package/examples/private/exposure-source-h5-data.csv +0 -26
  49. package/examples/private/fatal-data.csv +0 -3159
  50. package/examples/private/feelings.json +0 -1
  51. package/examples/private/gaza-issue.json +0 -1214
  52. package/examples/private/markup.json +0 -115
  53. package/examples/private/nhis.json +0 -1792
  54. package/examples/private/workforce.json +0 -2041
@@ -1,652 +1,680 @@
1
- import _ from 'lodash'
2
- import { APIFilter } from '../../../../types/APIFilter'
3
- import { getVizRowColumnLocator } from '../../../../helpers/getVizRowColumnLocator'
4
- import { Select, TextField } from '@cdc/core/components/EditorPanel/Inputs'
5
- import DataTransform from '@cdc/core/helpers/DataTransform'
6
- import { useEffect, useMemo, useState } from 'react'
7
- import { SharedFilter } from '../../../../types/SharedFilter'
8
-
9
- // Add defaultValue to SharedFilter type
10
- interface SharedFilter {
11
- defaultValue?: string
12
- resetLabel?: string
13
- }
14
- import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
15
- import Tooltip from '@cdc/core/components/ui/Tooltip'
16
- import Icon from '@cdc/core/components/ui/Icon'
17
- import MultiSelect from '@cdc/core/components/MultiSelect'
18
- import Loading from '@cdc/core/components/Loading'
19
- import { DashboardConfig } from '../../../../types/DashboardConfig'
20
- import { Visualization } from '@cdc/core/types/Visualization'
21
- import { hasDashboardApplyBehavior } from '../../../../helpers/hasDashboardApplyBehavior'
22
- import NestedDropDownDashboard from './NestedDropDownDashboard'
23
- import { FILTER_STYLE } from '../../../../types/FilterStyles'
24
- import { filterOrderOptions } from '@cdc/core/components/Filters'
25
- import FilterOrder from '@cdc/core/components/EditorPanel/VizFilterEditor/components/FilterOrder'
26
-
27
- type FilterEditorProps = {
28
- config: DashboardConfig
29
- filter: SharedFilter
30
- filterIndex: number
31
- updateFilterProp: (name: keyof SharedFilter, value: any) => void
32
- toggleNestedQueryParameters: (checked: boolean) => void
33
- }
34
-
35
- const FilterEditor: React.FC<FilterEditorProps> = ({
36
- filter,
37
- filterIndex,
38
- config,
39
- updateFilterProp,
40
- toggleNestedQueryParameters
41
- }) => {
42
- const [columns, setColumns] = useState<string[]>([])
43
- const [dataFiltersLoading, setDataFiltersLoading] = useState(false)
44
- const transform = new DataTransform()
45
- const filterStyles = Object.values(FILTER_STYLE)
46
-
47
- const parentFilters: string[] = (config.dashboard.sharedFilters || [])
48
- .filter(({ key, type }) => key !== filter.key && type !== 'datafilter')
49
- .map(({ key }) => key)
50
-
51
- const vizRowColumnLocator = getVizRowColumnLocator(config.rows)
52
-
53
- const getVizTitle = (viz, vizKey) => {
54
- let vizName = viz.general?.title || viz.title || vizKey
55
- if (viz.visualizationType === 'markup-include') {
56
- vizName = viz.contentEditor.title || vizKey
57
- }
58
- return vizName
59
- }
60
-
61
- const [usedByNameLookup, usedByOptions] = useMemo(() => {
62
- const nameLookup = {}
63
- const vizOptions = Object.keys(config.visualizations).filter(vizKey => {
64
- const vizLookup = vizRowColumnLocator[vizKey]
65
- if (!vizLookup) return false
66
- const viz = config.visualizations[vizKey] as Visualization
67
- if (viz.type === 'dashboardFilters') return false
68
- const vizName = getVizTitle(viz, vizKey)
69
-
70
- nameLookup[vizKey] = vizName
71
- const usesSharedFilter = viz.usesSharedFilter
72
- const rowIndex = vizLookup.row
73
- const dataConfiguredOnRow = config.rows[rowIndex].dataKey
74
- return filter.setBy !== vizKey && !usesSharedFilter && !dataConfiguredOnRow
75
- })
76
- const rowOptions: number[] = []
77
-
78
- config.rows.forEach((row, rowIndex) => {
79
- if (!!row.dataKey) {
80
- nameLookup[rowIndex] = `Row ${rowIndex + 1}`
81
- rowOptions.push(rowIndex)
82
- }
83
- })
84
-
85
- const rowsNotSelected = rowOptions.filter(row => !filter.usedBy || filter.usedBy.indexOf(row.toString()) === -1)
86
- return [nameLookup, [...vizOptions, ...rowsNotSelected]]
87
- }, [config.visualizations, filter.usedBy, filter.setBy, vizRowColumnLocator])
88
-
89
- const useParameters = useMemo(() => {
90
- if (filter.subGrouping) return !!(filter.setByQueryParameter && filter.subGrouping?.setByQueryParameter)
91
- return !!filter.setByQueryParameter
92
- }, [config, filterIndex])
93
-
94
- const loadColumnData = async () => {
95
- // column data only needed for data filters
96
- if (!config.dashboard.sharedFilters.some(f => f.type === 'datafilter')) return
97
- const columns = {}
98
- const dataKeys = Object.keys(config.datasets)
99
- for (let i = 0; i < dataKeys.length; i++) {
100
- const dataKey = dataKeys[i]
101
- let _dataSet = config.datasets[dataKey]
102
- if (!_dataSet.data && _dataSet.dataUrl) {
103
- setDataFiltersLoading(true)
104
- let data = await fetchRemoteData(_dataSet.dataUrl)
105
- if (_dataSet.dataDescription) {
106
- try {
107
- data = transform.autoStandardize(data)
108
- data = transform.developerStandardize(data, _dataSet.dataDescription)
109
- } catch (e) {
110
- console.error(e)
111
- //Data not able to be standardized, leave as is
112
- } finally {
113
- _dataSet.data = data
114
- }
115
- }
116
- }
117
-
118
- if (_dataSet.data) {
119
- _dataSet.data.forEach(row => {
120
- Object.keys(row).forEach(columnName => {
121
- columns[columnName] = true
122
- })
123
- })
124
- }
125
- }
126
- setDataFiltersLoading(false)
127
- setColumns(Object.keys(columns))
128
- }
129
-
130
- useEffect(() => {
131
- loadColumnData()
132
- }, [config.datasets, config.dashboard.sharedFilters])
133
-
134
- const updateAPIFilter = (key: keyof APIFilter, value: string | boolean) => {
135
- const filterClone = _.cloneDeep(filter)
136
- const _filter = filterClone.apiFilter || { apiEndpoint: '', valueSelector: '', textSelector: '' }
137
- const newAPIFilter: APIFilter = { ..._filter, [key]: value }
138
- updateFilterProp('apiFilter', newAPIFilter)
139
- }
140
-
141
- const updateLabel = (value: string) => {
142
- const duplicateLabels = config.dashboard.sharedFilters.filter(
143
- (filter, i) => filter.key === value && filterIndex !== i
144
- )
145
- // If there are duplicate labels, append the number of duplicates to the label similar functionality to duplicate file names
146
- updateFilterProp('key', duplicateLabels.length ? value + ` (${duplicateLabels.length})` : value)
147
- }
148
-
149
- const isNestedDropDown = filter.filterStyle === FILTER_STYLE.nestedDropdown
150
-
151
- type APIInputProps = {
152
- isSubgroup?: boolean
153
- }
154
- const APIInputs: React.FC<APIInputProps> = ({ isSubgroup = false }) => {
155
- const textSelector = isSubgroup ? 'subgroupTextSelector' : 'textSelector'
156
- const valueSelector = isSubgroup ? 'subgroupValueSelector' : 'valueSelector'
157
- return (
158
- <>
159
- {!isSubgroup && (
160
- <TextField
161
- label='API Endpoint: '
162
- value={filter.apiFilter?.apiEndpoint}
163
- updateField={(_section, _subSection, _key, value) => updateAPIFilter('apiEndpoint', value)}
164
- tooltip={
165
- <>
166
- {isNestedDropDown && (
167
- <Tooltip style={{ textTransform: 'none' }}>
168
- <Tooltip.Target>
169
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
170
- </Tooltip.Target>
171
- <Tooltip.Content>
172
- <p>Your API Endpoint should return both value selector values.</p>
173
- </Tooltip.Content>
174
- </Tooltip>
175
- )}
176
- </>
177
- }
178
- />
179
- )}
180
-
181
- <div className={isNestedDropDown ? 'border border-dark p-1 my-1' : ''}>
182
- <TextField
183
- label={`${isSubgroup ? 'Subgroup ' : ''}Value Selector:`}
184
- value={filter?.apiFilter?.[valueSelector] || ''}
185
- updateField={(_section, _subSection, _key, value) => updateAPIFilter(valueSelector, value)}
186
- tooltip={
187
- <>
188
- <Tooltip style={{ textTransform: 'none' }}>
189
- <Tooltip.Target>
190
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
191
- </Tooltip.Target>
192
- <Tooltip.Content>
193
- <p>Value to use in the html option element</p>
194
- </Tooltip.Content>
195
- </Tooltip>
196
- {` * Required`}
197
- </>
198
- }
199
- />
200
-
201
- <TextField
202
- label={`${isSubgroup ? 'Subgroup ' : ''}Display Text Selector:`}
203
- value={filter?.apiFilter?.[textSelector] || ''}
204
- updateField={(_section, _subSection, _key, value) => updateAPIFilter(textSelector, value)}
205
- tooltip={
206
- <>
207
- <Tooltip style={{ textTransform: 'none' }}>
208
- <Tooltip.Target>
209
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
210
- </Tooltip.Target>
211
- <Tooltip.Content>
212
- <p>Text to use in the html option element. If none is applied value selector will be used.</p>
213
- </Tooltip.Content>
214
- </Tooltip>
215
- {` * Optional`}
216
- </>
217
- }
218
- />
219
- </div>
220
- </>
221
- )
222
- }
223
-
224
- const selectFilterType = (type: string) => {
225
- updateFilterProp('type', type)
226
- if (type === 'datafilter') {
227
- loadColumnData()
228
- }
229
- }
230
-
231
- return (
232
- <>
233
- {dataFiltersLoading && <Loading />}
234
- <label>
235
- <span className='edit-label column-heading'>Filter Type: </span>
236
- <select
237
- defaultValue={filter.type || ''}
238
- onChange={e => selectFilterType(e.target.value)}
239
- disabled={!!filter.type}
240
- >
241
- <option value=''>- Select Option -</option>
242
- <option value='urlfilter'>URL</option>
243
- <option value='datafilter'>Data</option>
244
- </select>
245
- </label>
246
- {filter.type !== undefined && (
247
- <>
248
- <label>
249
- <span className='edit-label column-heading'>Filter Style: </span>
250
- <select
251
- value={filter.filterStyle || FILTER_STYLE.dropdown}
252
- onChange={e => updateFilterProp('filterStyle', e.target.value)}
253
- >
254
- {filterStyles.map(dataKey => (
255
- <option value={dataKey} key={`filter-style-select-item-${dataKey}`}>
256
- {dataKey}
257
- </option>
258
- ))}
259
- </select>
260
- </label>
261
- {filter.filterStyle === FILTER_STYLE.dropdown && (
262
- <label>
263
- <span className='me-1'>Show Dropdown</span>
264
- <input
265
- type='checkbox'
266
- checked={filter.showDropdown}
267
- onChange={e => {
268
- updateFilterProp('showDropdown', !filter.showDropdown)
269
- }}
270
- />
271
- </label>
272
- )}
273
-
274
- <TextField
275
- label='Label'
276
- value={filter.key}
277
- updateField={(_section, _subSection, _key, value) => {
278
- updateLabel(value)
279
- }}
280
- />
281
- {filter.filterStyle === FILTER_STYLE.multiSelect && (
282
- <TextField
283
- label='Select Limit'
284
- value={filter.selectLimit}
285
- updateField={(_section, _subSection, _field, value) => updateFilterProp('selectLimit', value)}
286
- type='number'
287
- tooltip={
288
- <Tooltip style={{ textTransform: 'none' }}>
289
- <Tooltip.Target>
290
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
291
- </Tooltip.Target>
292
- <Tooltip.Content>
293
- <p>The maximum number of items that can be selected.</p>
294
- </Tooltip.Content>
295
- </Tooltip>
296
- }
297
- />
298
- )}
299
-
300
- {filter.type === 'urlfilter' && (
301
- <>
302
- {!hasDashboardApplyBehavior(config.visualizations) && (
303
- <>
304
- <label>
305
- <span className='edit-label column-heading'>URL to Filter: </span>
306
- <select
307
- defaultValue={filter.datasetKey || ''}
308
- onChange={e => updateFilterProp('datasetKey', e.target.value)}
309
- >
310
- <option value=''>- Select Option -</option>
311
- {Object.keys(config.datasets).map(datasetKey => {
312
- if (config.datasets[datasetKey].dataUrl) {
313
- return (
314
- <option key={datasetKey} value={datasetKey}>
315
- {config.datasets[datasetKey].dataUrl}
316
- </option>
317
- )
318
- }
319
- return null
320
- })}
321
- </select>
322
- </label>
323
-
324
- <label>
325
- <span className='edit-label column-heading'>Filter By: </span>
326
- <select
327
- defaultValue={filter.filterBy || ''}
328
- onChange={e => updateFilterProp('filterBy', e.target.value)}
329
- >
330
- <option value=''>- Select Option -</option>
331
- <option key={'query-string'} value={'Query String'}>
332
- Query String
333
- </option>
334
- <option key={'file-name'} value={'File Name'}>
335
- File Name
336
- </option>
337
- </select>
338
- </label>
339
- {filter.filterBy === 'File Name' && (
340
- <>
341
- <TextField
342
- label='File Name: '
343
- value={filter.fileName || ''}
344
- updateField={(_section, _subSection, _key, value) => updateFilterProp('fileName', value)}
345
- tooltip={
346
- <Tooltip style={{ textTransform: 'none' }}>
347
- <Tooltip.Target>
348
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
349
- </Tooltip.Target>
350
- <Tooltip.Content>
351
- <p>{`Add \${query}\ to replace the filename with the active dropdown value.`}</p>
352
- </Tooltip.Content>
353
- </Tooltip>
354
- }
355
- />
356
-
357
- <label>
358
- <span className='edit-label column-heading'>
359
- White Space Replacments
360
- <Tooltip style={{ textTransform: 'none' }}>
361
- <Tooltip.Target>
362
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
363
- </Tooltip.Target>
364
- <Tooltip.Content>
365
- <p>{`Set how whitespace characters will be handled in the file request`}</p>
366
- </Tooltip.Content>
367
- </Tooltip>
368
- </span>
369
- <select
370
- defaultValue={filter.whitespaceReplacement || 'Keep Spaces'}
371
- onChange={e => updateFilterProp('whitespaceReplacement', e.target.value)}
372
- >
373
- <option key={'remove-spaces'} value={'Remove Spaces'}>
374
- Remove Spaces
375
- </option>
376
- <option key={'replace-with-underscore'} value={'Replace With Underscore'}>
377
- Replace With Underscore
378
- </option>
379
- <option key={'keep-spaces'} value={'Keep Spaces'}>
380
- Keep Spaces
381
- </option>
382
- </select>
383
- </label>
384
- </>
385
- )}
386
- </>
387
- )}
388
- {filter.filterBy === 'Query String' && (
389
- <TextField
390
- label='Query string parameter'
391
- value={filter.queryParameter}
392
- updateField={(_section, _subSection, _key, value) => updateFilterProp('queryParameter', value)}
393
- />
394
- )}
395
-
396
- <APIInputs />
397
-
398
- {isNestedDropDown && <APIInputs isSubgroup={true} />}
399
-
400
- <label>
401
- <input
402
- type='checkbox'
403
- checked={useParameters}
404
- aria-label='Create query parameters'
405
- disabled={!filter.apiFilter?.valueSelector && !filter.apiFilter?.subgroupValueSelector}
406
- onChange={e => toggleNestedQueryParameters(e.target.checked)}
407
- />
408
- <span>
409
- {' '}
410
- Create query parameters{' '}
411
- <Tooltip style={{ textTransform: 'none' }}>
412
- <Tooltip.Target>
413
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
414
- </Tooltip.Target>
415
- <Tooltip.Content>
416
- <p>
417
- Query parameters will be added to the URL which correspond to the respective value selector.
418
- </p>
419
- </Tooltip.Content>
420
- </Tooltip>
421
- </span>
422
- </label>
423
-
424
- {!!parentFilters.length && (
425
- <label>
426
- <span className='edit-label column-heading mt-1'>Parent Filter(s): </span>
427
- <MultiSelect
428
- label='Parent Filter(s): '
429
- options={parentFilters.map(key => ({ value: key, label: key }))}
430
- fieldName='parents'
431
- selected={filter.parents}
432
- updateField={(_section, _subsection, _fieldname, newItems) => {
433
- updateFilterProp('parents', newItems)
434
- }}
435
- />
436
- </label>
437
- )}
438
-
439
- <label>
440
- <span className='edit-label column-heading mt-1'>
441
- Used By: (optional)
442
- <Tooltip style={{ textTransform: 'none' }}>
443
- <Tooltip.Target>
444
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
445
- </Tooltip.Target>
446
- <Tooltip.Content>
447
- <p>
448
- Select if you would like specific visualizations or rows to use this filter. Otherwise the
449
- filter will be added to all api requests.
450
- </p>
451
- </Tooltip.Content>
452
- </Tooltip>
453
- </span>
454
- <MultiSelect
455
- options={usedByOptions.map(opt => ({
456
- value: opt,
457
- label: usedByNameLookup[opt]
458
- }))}
459
- fieldName='usedBy'
460
- selected={filter.usedBy}
461
- updateField={(_section, _subsection, _fieldname, newItems) => {
462
- updateFilterProp('usedBy', newItems)
463
- }}
464
- />
465
- </label>
466
-
467
- <TextField
468
- label='Reset Label: '
469
- value={filter.resetLabel || ''}
470
- updateField={(_section, _subSection, _key, value) => updateFilterProp('resetLabel', value)}
471
- />
472
- </>
473
- )}
474
-
475
- {filter.type === 'datafilter' && (
476
- <>
477
- {filter.filterStyle !== FILTER_STYLE.nestedDropdown ? (
478
- <>
479
- <label>
480
- <span className='edit-label column-heading'>Filter: </span>
481
- <select
482
- value={filter.columnName}
483
- onChange={e => {
484
- updateFilterProp('columnName', e.target.value)
485
- }}
486
- >
487
- <option value=''>- Select Option -</option>
488
- {columns.map(dataKey => (
489
- <option value={dataKey} key={`filter-column-select-item-${dataKey}`}>
490
- {dataKey}
491
- </option>
492
- ))}
493
- </select>
494
- </label>
495
-
496
- <Select
497
- value={filter.defaultValue}
498
- options={
499
- filter.resetLabel
500
- ? [filter.resetLabel, ...config.dashboard.sharedFilters[filterIndex].values]
501
- : config.dashboard.sharedFilters[filterIndex].values
502
- }
503
- updateField={(_section, _subSection, _key, value) => updateFilterProp('defaultValue', value)}
504
- label={'Filter Default Value'}
505
- initial={'Select'}
506
- />
507
-
508
- <Select
509
- value={filter.order || 'asc'}
510
- options={filterOrderOptions}
511
- updateField={(_section, _subSection, _key, value) => updateFilterProp('order', value)}
512
- label={'Filter Order'}
513
- />
514
-
515
- {/* if custom order is set use react-dnd library to sort the values */}
516
- {filter.order === 'cust' && (
517
- <FilterOrder
518
- orderedValues={filter.orderedValues || filter.values}
519
- handleFilterOrder={(index1, index2) => {
520
- const values = [...filter.values]
521
- const [removed] = values.splice(index1, 1)
522
- values.splice(index2, 0, removed)
523
- updateFilterProp('orderedValues', values)
524
- }}
525
- />
526
- )}
527
-
528
- <label>
529
- <span className='edit-label column-heading'>Show Dropdown</span>
530
- <input
531
- type='checkbox'
532
- defaultChecked={filter.showDropdown === true}
533
- onChange={e => {
534
- updateFilterProp('showDropdown', !filter.showDropdown)
535
- }}
536
- />
537
- </label>
538
- </>
539
- ) : (
540
- <>
541
- <NestedDropDownDashboard
542
- filter={filter}
543
- updateFilterProp={(name, value) => {
544
- updateFilterProp(name, value)
545
- }}
546
- isDashboard={true}
547
- config={config}
548
- />
549
- <label>
550
- <input
551
- type='checkbox'
552
- checked={useParameters}
553
- aria-label='Create query parameters'
554
- disabled={!filter.columnName || !filter.subGrouping?.columnName}
555
- onChange={e => toggleNestedQueryParameters(e.target.checked)}
556
- />
557
- <span>
558
- {' '}
559
- Create query parameters{' '}
560
- <Tooltip style={{ textTransform: 'none' }}>
561
- <Tooltip.Target>
562
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
563
- </Tooltip.Target>
564
- <Tooltip.Content>
565
- <p>
566
- Query parameters will be added to the URL which correspond to the respective column name.
567
- </p>
568
- </Tooltip.Content>
569
- </Tooltip>
570
- </span>
571
- </label>
572
- </>
573
- )}
574
- <Select
575
- label='Set By:'
576
- value={filter.setBy}
577
- options={Object.values(config.visualizations)
578
- .filter(viz => viz.type !== 'dashboardFilters')
579
- .map(viz => ({
580
- value: viz.uid,
581
- label: getVizTitle(viz, viz.type)
582
- }))}
583
- updateField={(_section, _subSection, _key, value) => updateFilterProp('setBy', value)}
584
- initial='- Select Option -'
585
- />
586
- <label>
587
- <span className='edit-label column-heading mt-1'>
588
- Used By: (optional)
589
- <Tooltip style={{ textTransform: 'none' }}>
590
- <Tooltip.Target>
591
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
592
- </Tooltip.Target>
593
- <Tooltip.Content>
594
- <p>
595
- Select if you would like specific visualizations or rows to use this filter. Otherwise the
596
- filter will be added to all api requests.
597
- </p>
598
- </Tooltip.Content>
599
- </Tooltip>
600
- </span>
601
- <MultiSelect
602
- options={usedByOptions.map(opt => ({
603
- value: opt,
604
- label: usedByNameLookup[opt]
605
- }))}
606
- fieldName='usedBy'
607
- selected={filter.usedBy}
608
- updateField={(_section, _subsection, _fieldname, newItems) => {
609
- updateFilterProp('usedBy', newItems)
610
- }}
611
- />
612
- </label>
613
- <TextField
614
- label='Reset Label: '
615
- value={filter.resetLabel || ''}
616
- updateField={(_section, _subSection, _key, value) => updateFilterProp('resetLabel', value)}
617
- />
618
-
619
- <label>
620
- <span className='edit-label column-heading'>Parent Filter: </span>
621
- <select
622
- value={filter.parents || []}
623
- onChange={e => {
624
- updateFilterProp('parents', e.target.value)
625
- }}
626
- >
627
- <option value=''>Select a filter</option>
628
- {config.dashboard.sharedFilters &&
629
- config.dashboard.sharedFilters.map(sharedFilter => {
630
- if (sharedFilter.key !== filter.key) {
631
- return <option key={sharedFilter.key}>{sharedFilter.key}</option>
632
- }
633
- })}
634
- </select>
635
- </label>
636
-
637
- {!isNestedDropDown && (
638
- <TextField
639
- label='Default Value Set By Query String Parameter: '
640
- value={filter.setByQueryParameter || ''}
641
- updateField={(_section, _subSection, _key, value) => updateFilterProp('setByQueryParameter', value)}
642
- />
643
- )}
644
- </>
645
- )}
646
- </>
647
- )}
648
- </>
649
- )
650
- }
651
-
652
- export default FilterEditor
1
+ import _ from 'lodash'
2
+ import { getVizRowColumnLocator } from '../../../../helpers/getVizRowColumnLocator'
3
+ import { Select, TextField } from '@cdc/core/components/EditorPanel/Inputs'
4
+ import DataTransform from '@cdc/core/helpers/DataTransform'
5
+ import { useEffect, useMemo, useState } from 'react'
6
+ import { SharedFilter } from '../../../../types/SharedFilter'
7
+
8
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
9
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
10
+ import Icon from '@cdc/core/components/ui/Icon'
11
+ import MultiSelect from '@cdc/core/components/MultiSelect'
12
+ import Loading from '@cdc/core/components/Loading'
13
+ import { DashboardConfig } from '../../../../types/DashboardConfig'
14
+ import { Visualization } from '@cdc/core/types/Visualization'
15
+ import { hasDashboardApplyBehavior } from '../../../../helpers/hasDashboardApplyBehavior'
16
+ import APIModal from './APIModal'
17
+ import NestedDropDownDashboard from './NestedDropDownDashboard'
18
+ import { FILTER_STYLE } from '../../../../types/FilterStyles'
19
+ import { filterOrderOptions } from '@cdc/core/helpers/filterOrderOptions'
20
+ import FilterOrder from '@cdc/core/components/EditorPanel/VizFilterEditor/components/FilterOrder'
21
+ import { useGlobalContext } from '@cdc/core/components/GlobalContext'
22
+ import Modal from '@cdc/core/components/ui/Modal'
23
+
24
+ type FilterEditorProps = {
25
+ config: DashboardConfig
26
+ filter: SharedFilter
27
+ filterIndex: number
28
+ updateFilterProp: (name: keyof SharedFilter, value: any) => void
29
+ toggleNestedQueryParameters: (checked: boolean) => void
30
+ }
31
+
32
+ const FilterEditor: React.FC<FilterEditorProps> = ({
33
+ filter,
34
+ filterIndex,
35
+ config,
36
+ updateFilterProp,
37
+ toggleNestedQueryParameters
38
+ }) => {
39
+ const [columns, setColumns] = useState<string[]>([])
40
+ const [dataFiltersLoading, setDataFiltersLoading] = useState(false)
41
+
42
+ const transform = new DataTransform()
43
+ const filterStyles = Object.values(FILTER_STYLE)
44
+
45
+ const parentFilters: string[] = (config.dashboard.sharedFilters || [])
46
+ .filter(({ key, type }) => key !== filter.key && type !== 'datafilter')
47
+ .map(({ key }) => key)
48
+
49
+ const vizRowColumnLocator = getVizRowColumnLocator(config.rows)
50
+
51
+ const getVizTitle = (viz, vizKey) => {
52
+ let vizName = viz.general?.title || viz.title || vizKey
53
+ if (viz.visualizationType === 'markup-include') {
54
+ vizName = viz.contentEditor.title || vizKey
55
+ }
56
+ return vizName
57
+ }
58
+
59
+ const [usedByNameLookup, usedByOptions] = useMemo(() => {
60
+ const nameLookup = {}
61
+ const vizOptions = Object.keys(config.visualizations).filter(vizKey => {
62
+ const vizLookup = vizRowColumnLocator[vizKey]
63
+ if (!vizLookup) return false
64
+ const viz = config.visualizations[vizKey] as Visualization
65
+ if (viz.type === 'dashboardFilters') return false
66
+ const vizName = getVizTitle(viz, vizKey)
67
+
68
+ nameLookup[vizKey] = vizName
69
+ const usesSharedFilter = viz.usesSharedFilter
70
+ const rowIndex = vizLookup.row
71
+ const dataConfiguredOnRow = config.rows[rowIndex].dataKey
72
+ return filter.setBy !== vizKey && !usesSharedFilter && !dataConfiguredOnRow
73
+ })
74
+ const rowOptions: number[] = []
75
+
76
+ config.rows.forEach((row, rowIndex) => {
77
+ if (!!row.dataKey) {
78
+ nameLookup[rowIndex] = `Row ${rowIndex + 1}`
79
+ rowOptions.push(rowIndex)
80
+ }
81
+ })
82
+
83
+ const rowsNotSelected = rowOptions.filter(row => !filter.usedBy || filter.usedBy.indexOf(row.toString()) === -1)
84
+ return [nameLookup, [...vizOptions, ...rowsNotSelected]]
85
+ }, [config.visualizations, filter.usedBy, filter.setBy, vizRowColumnLocator])
86
+
87
+ const useParameters = useMemo(() => {
88
+ if (filter.subGrouping) return !!(filter.setByQueryParameter && filter.subGrouping?.setByQueryParameter)
89
+ return !!filter.setByQueryParameter
90
+ }, [config, filterIndex])
91
+
92
+ const loadColumnData = async () => {
93
+ // column data only needed for data filters
94
+ if (!config.dashboard.sharedFilters.some(f => f.type === 'datafilter')) return
95
+ const columns = {}
96
+ const dataKeys = Object.keys(config.datasets)
97
+ for (let i = 0; i < dataKeys.length; i++) {
98
+ const dataKey = dataKeys[i]
99
+ let _dataSet = config.datasets[dataKey]
100
+ if (!_dataSet.data && _dataSet.dataUrl) {
101
+ setDataFiltersLoading(true)
102
+ let data = await fetchRemoteData(_dataSet.dataUrl)
103
+ if (_dataSet.dataDescription) {
104
+ try {
105
+ data = transform.autoStandardize(data)
106
+ data = transform.developerStandardize(data, _dataSet.dataDescription)
107
+ } catch (e) {
108
+ console.error(e)
109
+ //Data not able to be standardized, leave as is
110
+ } finally {
111
+ _dataSet.data = data
112
+ }
113
+ }
114
+ }
115
+
116
+ if (_dataSet.data) {
117
+ _dataSet.data.forEach(row => {
118
+ Object.keys(row).forEach(columnName => {
119
+ columns[columnName] = true
120
+ })
121
+ })
122
+ }
123
+ }
124
+ setDataFiltersLoading(false)
125
+ setColumns(Object.keys(columns))
126
+ }
127
+
128
+ useEffect(() => {
129
+ loadColumnData()
130
+ }, [config.datasets, config.dashboard.sharedFilters])
131
+
132
+ const updateAPIFilter = (apiEndpoint, valueSelector, textSelector, subgroupValueSelector, subgroupTextSelector) => {
133
+ const newAPIFilter = !isNestedDropdown
134
+ ? {
135
+ apiEndpoint,
136
+ valueSelector,
137
+ textSelector
138
+ }
139
+ : {
140
+ apiEndpoint,
141
+ valueSelector,
142
+ textSelector,
143
+ subgroupValueSelector,
144
+ subgroupTextSelector
145
+ }
146
+ updateFilterProp('apiFilter', newAPIFilter)
147
+ overlay.actions.toggleOverlay(false)
148
+ }
149
+
150
+ const updateLabel = (value: string) => {
151
+ const duplicateLabels = config.dashboard.sharedFilters.filter(
152
+ (filter, i) => filter.key === value && filterIndex !== i
153
+ )
154
+ // If there are duplicate labels, append the number of duplicates to the label similar functionality to duplicate file names
155
+ updateFilterProp('key', duplicateLabels.length ? value + ` (${duplicateLabels.length})` : value)
156
+ }
157
+
158
+ const isNestedDropdown = filter.filterStyle === FILTER_STYLE.nestedDropdown
159
+
160
+ const { overlay } = useGlobalContext()
161
+
162
+ const handleEditAPIValues = (filter, isNestedDropdown, updateAPIFilter) => {
163
+ {
164
+ overlay.actions.openOverlay(
165
+ <Modal>
166
+ <Modal.Content>
167
+ <APIModal filter={filter} isNestedDropdown={isNestedDropdown} updateAPIFilter={updateAPIFilter} />
168
+ </Modal.Content>
169
+ </Modal>
170
+ )
171
+ }
172
+ }
173
+
174
+ const selectFilterType = (type: string) => {
175
+ updateFilterProp('type', type)
176
+ if (type === 'datafilter') {
177
+ loadColumnData()
178
+ }
179
+ }
180
+
181
+ return (
182
+ <>
183
+ {dataFiltersLoading && <Loading />}
184
+ <label>
185
+ <span className='edit-label column-heading'>Filter Type: </span>
186
+ <select
187
+ defaultValue={filter.type || ''}
188
+ onChange={e => selectFilterType(e.target.value)}
189
+ disabled={!!filter.type}
190
+ >
191
+ <option value=''>- Select Option -</option>
192
+ <option value='urlfilter'>URL</option>
193
+ <option value='datafilter'>Data</option>
194
+ </select>
195
+ </label>
196
+ {filter.type !== undefined && (
197
+ <>
198
+ <label>
199
+ <span className='edit-label column-heading'>Filter Style: </span>
200
+ <select
201
+ value={filter.filterStyle || FILTER_STYLE.dropdown}
202
+ onChange={e => updateFilterProp('filterStyle', e.target.value)}
203
+ >
204
+ {filterStyles.map(dataKey => (
205
+ <option value={dataKey} key={`filter-style-select-item-${dataKey}`}>
206
+ {dataKey}
207
+ </option>
208
+ ))}
209
+ </select>
210
+ </label>
211
+ {filter.filterStyle === FILTER_STYLE.dropdown && (
212
+ <label>
213
+ <span className='me-1'>Show Dropdown</span>
214
+ <input
215
+ type='checkbox'
216
+ checked={filter.showDropdown}
217
+ onChange={e => {
218
+ updateFilterProp('showDropdown', !filter.showDropdown)
219
+ }}
220
+ />
221
+ </label>
222
+ )}
223
+
224
+ <TextField
225
+ label='Label'
226
+ value={filter.key}
227
+ updateField={(_section, _subSection, _key, value) => {
228
+ updateLabel(value)
229
+ }}
230
+ />
231
+ {filter.filterStyle === FILTER_STYLE.multiSelect && (
232
+ <TextField
233
+ label='Select Limit'
234
+ value={filter.selectLimit}
235
+ updateField={(_section, _subSection, _field, value) => updateFilterProp('selectLimit', value)}
236
+ type='number'
237
+ tooltip={
238
+ <Tooltip style={{ textTransform: 'none' }}>
239
+ <Tooltip.Target>
240
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
241
+ </Tooltip.Target>
242
+ <Tooltip.Content>
243
+ <p>The maximum number of items that can be selected.</p>
244
+ </Tooltip.Content>
245
+ </Tooltip>
246
+ }
247
+ />
248
+ )}
249
+
250
+ {filter.type === 'urlfilter' && (
251
+ <>
252
+ {!hasDashboardApplyBehavior(config.visualizations) && (
253
+ <>
254
+ <label>
255
+ <span className='edit-label column-heading'>URL to Filter: </span>
256
+ <select
257
+ defaultValue={filter.datasetKey || ''}
258
+ onChange={e => updateFilterProp('datasetKey', e.target.value)}
259
+ >
260
+ <option value=''>- Select Option -</option>
261
+ {Object.keys(config.datasets).map(datasetKey => {
262
+ if (config.datasets[datasetKey].dataUrl) {
263
+ return (
264
+ <option key={datasetKey} value={datasetKey}>
265
+ {config.datasets[datasetKey].dataUrl}
266
+ </option>
267
+ )
268
+ }
269
+ return null
270
+ })}
271
+ </select>
272
+ </label>
273
+
274
+ <label>
275
+ <span className='edit-label column-heading'>Filter By: </span>
276
+ <select
277
+ defaultValue={filter.filterBy || ''}
278
+ onChange={e => updateFilterProp('filterBy', e.target.value)}
279
+ >
280
+ <option value=''>- Select Option -</option>
281
+ <option key={'query-string'} value={'Query String'}>
282
+ Query String
283
+ </option>
284
+ <option key={'file-name'} value={'File Name'}>
285
+ File Name
286
+ </option>
287
+ </select>
288
+ </label>
289
+ {filter.filterBy === 'File Name' && (
290
+ <>
291
+ <TextField
292
+ label='File Name: '
293
+ value={filter.fileName || ''}
294
+ updateField={(_section, _subSection, _key, value) => updateFilterProp('fileName', value)}
295
+ tooltip={
296
+ <Tooltip style={{ textTransform: 'none' }}>
297
+ <Tooltip.Target>
298
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
299
+ </Tooltip.Target>
300
+ <Tooltip.Content>
301
+ <p>{`Add \${query}\ to replace the filename with the active dropdown value.`}</p>
302
+ </Tooltip.Content>
303
+ </Tooltip>
304
+ }
305
+ />
306
+
307
+ <label>
308
+ <span className='edit-label column-heading'>
309
+ White Space Replacments
310
+ <Tooltip style={{ textTransform: 'none' }}>
311
+ <Tooltip.Target>
312
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
313
+ </Tooltip.Target>
314
+ <Tooltip.Content>
315
+ <p>{`Set how whitespace characters will be handled in the file request`}</p>
316
+ </Tooltip.Content>
317
+ </Tooltip>
318
+ </span>
319
+ <select
320
+ defaultValue={filter.whitespaceReplacement || 'Keep Spaces'}
321
+ onChange={e => updateFilterProp('whitespaceReplacement', e.target.value)}
322
+ >
323
+ <option key={'remove-spaces'} value={'Remove Spaces'}>
324
+ Remove Spaces
325
+ </option>
326
+ <option key={'replace-with-underscore'} value={'Replace With Underscore'}>
327
+ Replace With Underscore
328
+ </option>
329
+ <option key={'keep-spaces'} value={'Keep Spaces'}>
330
+ Keep Spaces
331
+ </option>
332
+ </select>
333
+ </label>
334
+ </>
335
+ )}
336
+ </>
337
+ )}
338
+ {filter.filterBy === 'Query String' && (
339
+ <TextField
340
+ label='Query string parameter'
341
+ value={filter.queryParameter}
342
+ updateField={(_section, _subSection, _key, value) => updateFilterProp('queryParameter', value)}
343
+ />
344
+ )}
345
+ <div className='bg-secondary-subtle p-2 my-2'>
346
+ <label>
347
+ <span>API Endpoint: </span>
348
+ <textarea value={filter?.apiFilter?.apiEndpoint || ''} disabled />
349
+ {isNestedDropdown && (
350
+ <Tooltip style={{ textTransform: 'none' }}>
351
+ <Tooltip.Target>
352
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
353
+ </Tooltip.Target>
354
+ <Tooltip.Content>
355
+ <p>Your API Endpoint should return both value selector values.</p>
356
+ </Tooltip.Content>
357
+ </Tooltip>
358
+ )}
359
+ </label>
360
+ <div className={isNestedDropdown ? 'border border-dark p-1 my-1' : ''}>
361
+ <label>
362
+ <span>Value Selector: </span>
363
+ <input type='text' value={filter?.apiFilter?.valueSelector || ''} disabled />
364
+ <Tooltip style={{ textTransform: 'none' }}>
365
+ <Tooltip.Target>
366
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
367
+ </Tooltip.Target>
368
+ <Tooltip.Content>
369
+ <p>Value to use in the html option element</p>
370
+ </Tooltip.Content>
371
+ </Tooltip>
372
+ <div>{` * Required`}</div>
373
+ </label>
374
+ <label>
375
+ <span>Display Text Selector: </span>
376
+ <input type='text' value={filter?.apiFilter?.textSelector || ''} disabled />
377
+ <Tooltip style={{ textTransform: 'none' }}>
378
+ <Tooltip.Target>
379
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
380
+ </Tooltip.Target>
381
+ <Tooltip.Content>
382
+ <p>Text to use in the html option element. If none is applied value selector will be used.</p>
383
+ </Tooltip.Content>
384
+ </Tooltip>
385
+ <div>{` * Optional`}</div>
386
+ </label>
387
+ </div>
388
+
389
+ {isNestedDropdown && (
390
+ <div className={isNestedDropdown ? 'border border-dark p-1 my-1' : ''}>
391
+ <label>
392
+ <span>Subgroup Value Selector: </span>
393
+ <input value={filter?.apiFilter?.subgroupValueSelector || ''} disabled />
394
+ <Tooltip style={{ textTransform: 'none' }}>
395
+ <Tooltip.Target>
396
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
397
+ </Tooltip.Target>
398
+ <Tooltip.Content>
399
+ <p>Value to use in the html option element</p>
400
+ </Tooltip.Content>
401
+ </Tooltip>
402
+ <div>{` * Required`}</div>
403
+ </label>
404
+ <label>
405
+ <span>Subgroup Display Text Selector: </span>
406
+ <input value={filter?.apifilter?.subgroupTextSelector || ''} disabled />
407
+ <Tooltip style={{ textTransform: 'none' }}>
408
+ <Tooltip.Target>
409
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
410
+ </Tooltip.Target>
411
+ <Tooltip.Content>
412
+ <p>Text to use in the html option element. If none is applied value selector will be used.</p>
413
+ </Tooltip.Content>
414
+ </Tooltip>
415
+ <div>{` * Optional`}</div>
416
+ </label>
417
+ </div>
418
+ )}
419
+
420
+ <button
421
+ onClick={() => handleEditAPIValues(filter, isNestedDropdown, updateAPIFilter)}
422
+ className='btn btn-primary mt-2'
423
+ >
424
+ Edit API Values
425
+ </button>
426
+ </div>
427
+
428
+ <label>
429
+ <input
430
+ type='checkbox'
431
+ checked={useParameters}
432
+ aria-label='Create query parameters'
433
+ disabled={!filter.apiFilter?.valueSelector && !filter.apiFilter?.subgroupValueSelector}
434
+ onChange={e => toggleNestedQueryParameters(e.target.checked)}
435
+ />
436
+ <span>
437
+ {' '}
438
+ Create query parameters{' '}
439
+ <Tooltip style={{ textTransform: 'none' }}>
440
+ <Tooltip.Target>
441
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
442
+ </Tooltip.Target>
443
+ <Tooltip.Content>
444
+ <p>
445
+ Query parameters will be added to the URL which correspond to the respective value selector.
446
+ </p>
447
+ </Tooltip.Content>
448
+ </Tooltip>
449
+ </span>
450
+ </label>
451
+
452
+ {!!parentFilters.length && (
453
+ <label>
454
+ <span className='edit-label column-heading mt-1'>Parent Filter(s): </span>
455
+ <MultiSelect
456
+ label='Parent Filter(s): '
457
+ options={parentFilters.map(key => ({ value: key, label: key }))}
458
+ fieldName='parents'
459
+ selected={filter.parents}
460
+ updateField={(_section, _subsection, _fieldname, newItems) => {
461
+ updateFilterProp('parents', newItems)
462
+ }}
463
+ />
464
+ </label>
465
+ )}
466
+
467
+ <label>
468
+ <span className='edit-label column-heading mt-1'>
469
+ Used By: (optional)
470
+ <Tooltip style={{ textTransform: 'none' }}>
471
+ <Tooltip.Target>
472
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
473
+ </Tooltip.Target>
474
+ <Tooltip.Content>
475
+ <p>
476
+ Select if you would like specific visualizations or rows to use this filter. Otherwise the
477
+ filter will be added to all api requests.
478
+ </p>
479
+ </Tooltip.Content>
480
+ </Tooltip>
481
+ </span>
482
+ <MultiSelect
483
+ options={usedByOptions.map(opt => ({
484
+ value: opt,
485
+ label: usedByNameLookup[opt]
486
+ }))}
487
+ fieldName='usedBy'
488
+ selected={filter.usedBy}
489
+ updateField={(_section, _subsection, _fieldname, newItems) => {
490
+ updateFilterProp('usedBy', newItems)
491
+ }}
492
+ />
493
+ </label>
494
+
495
+ <TextField
496
+ label='Reset Label: '
497
+ value={filter.resetLabel || ''}
498
+ updateField={(_section, _subSection, _key, value) => updateFilterProp('resetLabel', value)}
499
+ />
500
+ </>
501
+ )}
502
+
503
+ {filter.type === 'datafilter' && (
504
+ <>
505
+ {filter.filterStyle !== FILTER_STYLE.nestedDropdown ? (
506
+ <>
507
+ <label>
508
+ <span className='edit-label column-heading'>Filter: </span>
509
+ <select
510
+ value={filter.columnName}
511
+ onChange={e => {
512
+ updateFilterProp('columnName', e.target.value)
513
+ }}
514
+ >
515
+ <option value=''>- Select Option -</option>
516
+ {columns.map(dataKey => (
517
+ <option value={dataKey} key={`filter-column-select-item-${dataKey}`}>
518
+ {dataKey}
519
+ </option>
520
+ ))}
521
+ </select>
522
+ </label>
523
+
524
+ <Select
525
+ value={filter.defaultValue}
526
+ options={
527
+ filter.resetLabel
528
+ ? [filter.resetLabel, ...config.dashboard.sharedFilters[filterIndex].values]
529
+ : config.dashboard.sharedFilters[filterIndex].values
530
+ }
531
+ updateField={(_section, _subSection, _key, value) => updateFilterProp('defaultValue', value)}
532
+ label={'Filter Default Value'}
533
+ initial={'Select'}
534
+ />
535
+
536
+ <Select
537
+ value={filter.order || 'asc'}
538
+ options={filterOrderOptions}
539
+ updateField={(_section, _subSection, _key, value) => updateFilterProp('order', value)}
540
+ label={'Filter Order'}
541
+ />
542
+
543
+ {/* if custom order is set use react-dnd library to sort the values */}
544
+ {filter.order === 'cust' && (
545
+ <FilterOrder
546
+ orderedValues={filter.orderedValues || filter.values}
547
+ handleFilterOrder={(index1, index2) => {
548
+ const values = [...filter.values]
549
+ const [removed] = values.splice(index1, 1)
550
+ values.splice(index2, 0, removed)
551
+ updateFilterProp('orderedValues', values)
552
+ }}
553
+ />
554
+ )}
555
+
556
+ <label>
557
+ <span className='edit-label column-heading'>Show Dropdown</span>
558
+ <input
559
+ type='checkbox'
560
+ defaultChecked={filter.showDropdown === true}
561
+ onChange={e => {
562
+ updateFilterProp('showDropdown', !filter.showDropdown)
563
+ }}
564
+ />
565
+ </label>
566
+ </>
567
+ ) : (
568
+ <>
569
+ <NestedDropDownDashboard
570
+ filter={filter}
571
+ updateFilterProp={(name, value) => {
572
+ updateFilterProp(name, value)
573
+ }}
574
+ isDashboard={true}
575
+ config={config}
576
+ />
577
+ <label>
578
+ <input
579
+ type='checkbox'
580
+ checked={useParameters}
581
+ aria-label='Create query parameters'
582
+ disabled={!filter.columnName || !filter.subGrouping?.columnName}
583
+ onChange={e => toggleNestedQueryParameters(e.target.checked)}
584
+ />
585
+ <span>
586
+ {' '}
587
+ Create query parameters{' '}
588
+ <Tooltip style={{ textTransform: 'none' }}>
589
+ <Tooltip.Target>
590
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
591
+ </Tooltip.Target>
592
+ <Tooltip.Content>
593
+ <p>
594
+ Query parameters will be added to the URL which correspond to the respective column name.
595
+ </p>
596
+ </Tooltip.Content>
597
+ </Tooltip>
598
+ </span>
599
+ </label>
600
+ </>
601
+ )}
602
+ <Select
603
+ label='Set By:'
604
+ value={filter.setBy}
605
+ options={Object.values(config.visualizations)
606
+ .filter(viz => viz.type !== 'dashboardFilters')
607
+ .map(viz => ({
608
+ value: viz.uid,
609
+ label: getVizTitle(viz, viz.type)
610
+ }))}
611
+ updateField={(_section, _subSection, _key, value) => updateFilterProp('setBy', value)}
612
+ initial='- Select Option -'
613
+ />
614
+ <label>
615
+ <span className='edit-label column-heading mt-1'>
616
+ Used By: (optional)
617
+ <Tooltip style={{ textTransform: 'none' }}>
618
+ <Tooltip.Target>
619
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
620
+ </Tooltip.Target>
621
+ <Tooltip.Content>
622
+ <p>
623
+ Select if you would like specific visualizations or rows to use this filter. Otherwise the
624
+ filter will be added to all api requests.
625
+ </p>
626
+ </Tooltip.Content>
627
+ </Tooltip>
628
+ </span>
629
+ <MultiSelect
630
+ options={usedByOptions.map(opt => ({
631
+ value: opt,
632
+ label: usedByNameLookup[opt]
633
+ }))}
634
+ fieldName='usedBy'
635
+ selected={filter.usedBy}
636
+ updateField={(_section, _subsection, _fieldname, newItems) => {
637
+ updateFilterProp('usedBy', newItems)
638
+ }}
639
+ />
640
+ </label>
641
+ <TextField
642
+ label='Reset Label: '
643
+ value={filter.resetLabel || ''}
644
+ updateField={(_section, _subSection, _key, value) => updateFilterProp('resetLabel', value)}
645
+ />
646
+
647
+ <label>
648
+ <span className='edit-label column-heading'>Parent Filter: </span>
649
+ <select
650
+ value={filter.parents || []}
651
+ onChange={e => {
652
+ updateFilterProp('parents', e.target.value)
653
+ }}
654
+ >
655
+ <option value=''>Select a filter</option>
656
+ {config.dashboard.sharedFilters &&
657
+ config.dashboard.sharedFilters.map(sharedFilter => {
658
+ if (sharedFilter.key !== filter.key) {
659
+ return <option key={sharedFilter.key}>{sharedFilter.key}</option>
660
+ }
661
+ })}
662
+ </select>
663
+ </label>
664
+
665
+ {!isNestedDropdown && (
666
+ <TextField
667
+ label='Default Value Set By Query String Parameter: '
668
+ value={filter.setByQueryParameter || ''}
669
+ updateField={(_section, _subSection, _key, value) => updateFilterProp('setByQueryParameter', value)}
670
+ />
671
+ )}
672
+ </>
673
+ )}
674
+ </>
675
+ )}
676
+ </>
677
+ )
678
+ }
679
+
680
+ export default FilterEditor