@cdc/dashboard 4.25.1 → 4.25.2-25
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.
- package/dist/cdcdashboard.js +52965 -52581
- package/examples/all-components.json +529 -4607
- package/examples/dashboard-gallery.json +397 -397
- package/examples/private/DEV-10527.json +564 -0
- package/examples/private/DEV-10586.json +54319 -0
- package/examples/private/DEV-10856.json +54319 -0
- package/examples/private/dashboard-map-filter.json +815 -0
- package/examples/private/dev-10856-2.json +1348 -0
- package/examples/private/feelings.json +1 -0
- package/examples/private/nhis.json +1792 -0
- package/index.html +4 -3
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +5 -8
- package/src/CdcDashboardComponent.tsx +58 -56
- package/src/_stories/Dashboard.stories.tsx +31 -0
- package/src/_stories/_mock/dashboard-filter-asc.json +551 -0
- package/src/components/CollapsibleVisualizationRow.tsx +1 -1
- package/src/components/DashboardFilters/DashboardFilters.tsx +10 -5
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +22 -5
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +13 -3
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +129 -40
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +10 -7
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +11 -12
- package/src/components/DashboardFilters/dashboardfilter.styles.css +0 -2
- package/src/components/VisualizationRow.tsx +12 -2
- package/src/helpers/addValuesToDashboardFilters.ts +6 -5
- package/src/helpers/apiFilterHelpers.ts +10 -5
- package/src/helpers/changeFilterActive.ts +17 -4
- package/src/helpers/getFilteredData.ts +13 -4
- package/src/helpers/getUpdateConfig.ts +11 -4
- package/src/helpers/loadAPIFilters.ts +6 -4
- package/src/helpers/tests/updatesChildFilters.test.ts +56 -0
- package/src/helpers/updateChildFilters.ts +50 -0
- package/src/scss/main.scss +0 -3
- package/src/store/dashboard.reducer.ts +46 -24
- package/src/types/SharedFilter.ts +1 -1
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
2
|
import { APIFilter } from '../../../../types/APIFilter'
|
|
3
3
|
import { getVizRowColumnLocator } from '../../../../helpers/getVizRowColumnLocator'
|
|
4
|
-
import { TextField } from '@cdc/core/components/EditorPanel/Inputs'
|
|
4
|
+
import { Select, TextField } from '@cdc/core/components/EditorPanel/Inputs'
|
|
5
5
|
import DataTransform from '@cdc/core/helpers/DataTransform'
|
|
6
6
|
import { useEffect, useMemo, useState } from 'react'
|
|
7
7
|
import { SharedFilter } from '../../../../types/SharedFilter'
|
|
8
|
+
|
|
9
|
+
// Add defaultValue to SharedFilter type
|
|
10
|
+
interface SharedFilter {
|
|
11
|
+
defaultValue?: string
|
|
12
|
+
resetLabel?: string
|
|
13
|
+
}
|
|
8
14
|
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
9
15
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
10
16
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
@@ -14,14 +20,24 @@ import { Visualization } from '@cdc/core/types/Visualization'
|
|
|
14
20
|
import { hasDashboardApplyBehavior } from '../../../../helpers/hasDashboardApplyBehavior'
|
|
15
21
|
import NestedDropDownDashboard from './NestedDropDownDashboard'
|
|
16
22
|
import { FILTER_STYLE } from '../../../../types/FilterStyles'
|
|
23
|
+
import { filterOrderOptions } from '@cdc/core/components/Filters'
|
|
24
|
+
import FilterOrder from '@cdc/core/components/EditorPanel/VizFilterEditor/components/FilterOrder'
|
|
17
25
|
|
|
18
26
|
type FilterEditorProps = {
|
|
19
27
|
config: DashboardConfig
|
|
20
28
|
filter: SharedFilter
|
|
29
|
+
filterIndex: number
|
|
21
30
|
updateFilterProp: (name: keyof SharedFilter, value: any) => void
|
|
31
|
+
toggleNestedQueryParameters: (checked: boolean) => void
|
|
22
32
|
}
|
|
23
33
|
|
|
24
|
-
const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
34
|
+
const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
35
|
+
filter,
|
|
36
|
+
filterIndex,
|
|
37
|
+
config,
|
|
38
|
+
updateFilterProp,
|
|
39
|
+
toggleNestedQueryParameters
|
|
40
|
+
}) => {
|
|
25
41
|
const [columns, setColumns] = useState<string[]>([])
|
|
26
42
|
const transform = new DataTransform()
|
|
27
43
|
const filterStyles = Object.values(FILTER_STYLE)
|
|
@@ -59,6 +75,11 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
|
|
|
59
75
|
return [nameLookup, [...vizOptions, ...rowsNotSelected]]
|
|
60
76
|
}, [config.visualizations, filter.usedBy, filter.setBy, vizRowColumnLocator])
|
|
61
77
|
|
|
78
|
+
const useParameters = useMemo(() => {
|
|
79
|
+
if (filter.subGrouping) return !!(filter.setByQueryParameter && filter.subGrouping?.setByQueryParameter)
|
|
80
|
+
return !!filter.setByQueryParameter
|
|
81
|
+
}, [config, filterIndex])
|
|
82
|
+
|
|
62
83
|
const loadColumnData = async () => {
|
|
63
84
|
const columns = {}
|
|
64
85
|
const dataKeys = Object.keys(config.datasets)
|
|
@@ -94,21 +115,6 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
|
|
|
94
115
|
loadColumnData()
|
|
95
116
|
}, [config.datasets])
|
|
96
117
|
|
|
97
|
-
const addFilterUsedBy = (filter, value) => {
|
|
98
|
-
if (value === '') return
|
|
99
|
-
if (!filter.usedBy) filter.usedBy = []
|
|
100
|
-
filter.usedBy.push(value)
|
|
101
|
-
updateFilterProp('usedBy', filter.usedBy)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const removeFilterUsedBy = (filter, value) => {
|
|
105
|
-
let usedByIndex = filter.usedBy.indexOf(value)
|
|
106
|
-
if (usedByIndex !== -1) {
|
|
107
|
-
filter.usedBy.splice(usedByIndex, 1)
|
|
108
|
-
updateFilterProp('usedBy', filter.usedBy)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
118
|
const updateAPIFilter = (key: keyof APIFilter, value: string | boolean) => {
|
|
113
119
|
const filterClone = _.cloneDeep(filter)
|
|
114
120
|
const _filter = filterClone.apiFilter || { apiEndpoint: '', valueSelector: '', textSelector: '' }
|
|
@@ -116,8 +122,12 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
|
|
|
116
122
|
updateFilterProp('apiFilter', newAPIFilter)
|
|
117
123
|
}
|
|
118
124
|
|
|
119
|
-
const
|
|
120
|
-
|
|
125
|
+
const updateLabel = (value: string) => {
|
|
126
|
+
const duplicateLabels = config.dashboard.sharedFilters.filter(
|
|
127
|
+
(filter, i) => filter.key === value && filterIndex !== i
|
|
128
|
+
)
|
|
129
|
+
// If there are duplicate labels, append the number of duplicates to the label similar functionality to duplicate file names
|
|
130
|
+
updateFilterProp('key', duplicateLabels.length ? value + ` (${duplicateLabels.length})` : value)
|
|
121
131
|
}
|
|
122
132
|
|
|
123
133
|
const isNestedDropDown = filter.filterStyle === FILTER_STYLE.nestedDropdown
|
|
@@ -215,7 +225,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
|
|
|
215
225
|
<span className='edit-label column-heading'>Filter Style: </span>
|
|
216
226
|
<select
|
|
217
227
|
value={filter.filterStyle || FILTER_STYLE.dropdown}
|
|
218
|
-
onChange={e =>
|
|
228
|
+
onChange={e => updateFilterProp('filterStyle', e.target.value)}
|
|
219
229
|
>
|
|
220
230
|
{filterStyles.map(dataKey => (
|
|
221
231
|
<option value={dataKey} key={`filter-style-select-item-${dataKey}`}>
|
|
@@ -240,7 +250,9 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
|
|
|
240
250
|
<TextField
|
|
241
251
|
label='Label'
|
|
242
252
|
value={filter.key}
|
|
243
|
-
updateField={(_section, _subSection, _key, value) =>
|
|
253
|
+
updateField={(_section, _subSection, _key, value) => {
|
|
254
|
+
updateLabel(value)
|
|
255
|
+
}}
|
|
244
256
|
/>
|
|
245
257
|
{filter.filterStyle === FILTER_STYLE.multiSelect && (
|
|
246
258
|
<TextField
|
|
@@ -361,6 +373,30 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
|
|
|
361
373
|
|
|
362
374
|
{isNestedDropDown && <APIInputs isSubgroup={true} />}
|
|
363
375
|
|
|
376
|
+
<label>
|
|
377
|
+
<input
|
|
378
|
+
type='checkbox'
|
|
379
|
+
checked={useParameters}
|
|
380
|
+
aria-label='Create query parameters'
|
|
381
|
+
disabled={!filter.apiFilter?.valueSelector && !filter.apiFilter?.subgroupValueSelector}
|
|
382
|
+
onChange={e => toggleNestedQueryParameters(e.target.checked)}
|
|
383
|
+
/>
|
|
384
|
+
<span>
|
|
385
|
+
{' '}
|
|
386
|
+
Create query parameters{' '}
|
|
387
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
388
|
+
<Tooltip.Target>
|
|
389
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
390
|
+
</Tooltip.Target>
|
|
391
|
+
<Tooltip.Content>
|
|
392
|
+
<p>
|
|
393
|
+
Query parameters will be added to the URL which correspond to the respective value selector.
|
|
394
|
+
</p>
|
|
395
|
+
</Tooltip.Content>
|
|
396
|
+
</Tooltip>
|
|
397
|
+
</span>
|
|
398
|
+
</label>
|
|
399
|
+
|
|
364
400
|
{!!parentFilters.length && (
|
|
365
401
|
<label>
|
|
366
402
|
<span className='edit-label column-heading mt-1'>Parent Filter(s): </span>
|
|
@@ -409,12 +445,6 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
|
|
|
409
445
|
value={filter.resetLabel || ''}
|
|
410
446
|
updateField={(_section, _subSection, _key, value) => updateFilterProp('resetLabel', value)}
|
|
411
447
|
/>
|
|
412
|
-
|
|
413
|
-
<TextField
|
|
414
|
-
label='Default Value Set By Query String Parameter: '
|
|
415
|
-
value={filter.setByQueryParameter || ''}
|
|
416
|
-
updateField={(_section, _subSection, _key, value) => updateFilterProp('setByQueryParameter', value)}
|
|
417
|
-
/>
|
|
418
448
|
</>
|
|
419
449
|
)}
|
|
420
450
|
|
|
@@ -439,6 +469,38 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
|
|
|
439
469
|
</select>
|
|
440
470
|
</label>
|
|
441
471
|
|
|
472
|
+
<Select
|
|
473
|
+
value={filter.defaultValue}
|
|
474
|
+
options={
|
|
475
|
+
filter.resetLabel
|
|
476
|
+
? [filter.resetLabel, ...config.dashboard.sharedFilters[filterIndex].values]
|
|
477
|
+
: config.dashboard.sharedFilters[filterIndex].values
|
|
478
|
+
}
|
|
479
|
+
updateField={(_section, _subSection, _key, value) => updateFilterProp('defaultValue', value)}
|
|
480
|
+
label={'Filter Default Value'}
|
|
481
|
+
initial={'Select'}
|
|
482
|
+
/>
|
|
483
|
+
|
|
484
|
+
<Select
|
|
485
|
+
value={filter.order || 'asc'}
|
|
486
|
+
options={filterOrderOptions}
|
|
487
|
+
updateField={(_section, _subSection, _key, value) => updateFilterProp('order', value)}
|
|
488
|
+
label={'Filter Order'}
|
|
489
|
+
/>
|
|
490
|
+
|
|
491
|
+
{/* if custom order is set use react-dnd library to sort the values */}
|
|
492
|
+
{filter.order === 'cust' && (
|
|
493
|
+
<FilterOrder
|
|
494
|
+
orderedValues={filter.orderedValues || filter.values}
|
|
495
|
+
handleFilterOrder={(index1, index2) => {
|
|
496
|
+
const values = [...filter.values]
|
|
497
|
+
const [removed] = values.splice(index1, 1)
|
|
498
|
+
values.splice(index2, 0, removed)
|
|
499
|
+
updateFilterProp('orderedValues', values)
|
|
500
|
+
}}
|
|
501
|
+
/>
|
|
502
|
+
)}
|
|
503
|
+
|
|
442
504
|
<label>
|
|
443
505
|
<span className='edit-label column-heading'>Show Dropdown</span>
|
|
444
506
|
<input
|
|
@@ -451,14 +513,39 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
|
|
|
451
513
|
</label>
|
|
452
514
|
</>
|
|
453
515
|
) : (
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
updateFilterProp(name, value)
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
516
|
+
<>
|
|
517
|
+
<NestedDropDownDashboard
|
|
518
|
+
filter={filter}
|
|
519
|
+
updateFilterProp={(name, value) => {
|
|
520
|
+
updateFilterProp(name, value)
|
|
521
|
+
}}
|
|
522
|
+
isDashboard={true}
|
|
523
|
+
config={config}
|
|
524
|
+
/>
|
|
525
|
+
<label>
|
|
526
|
+
<input
|
|
527
|
+
type='checkbox'
|
|
528
|
+
checked={useParameters}
|
|
529
|
+
aria-label='Create query parameters'
|
|
530
|
+
disabled={!filter.columnName || !filter.subGrouping?.columnName}
|
|
531
|
+
onChange={e => toggleNestedQueryParameters(e.target.checked)}
|
|
532
|
+
/>
|
|
533
|
+
<span>
|
|
534
|
+
{' '}
|
|
535
|
+
Create query parameters{' '}
|
|
536
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
537
|
+
<Tooltip.Target>
|
|
538
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
539
|
+
</Tooltip.Target>
|
|
540
|
+
<Tooltip.Content>
|
|
541
|
+
<p>
|
|
542
|
+
Query parameters will be added to the URL which correspond to the respective column name.
|
|
543
|
+
</p>
|
|
544
|
+
</Tooltip.Content>
|
|
545
|
+
</Tooltip>
|
|
546
|
+
</span>
|
|
547
|
+
</label>
|
|
548
|
+
</>
|
|
462
549
|
)}
|
|
463
550
|
<label>
|
|
464
551
|
<span className='edit-label column-heading'>Set By: </span>
|
|
@@ -527,11 +614,13 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
|
|
|
527
614
|
</select>
|
|
528
615
|
</label>
|
|
529
616
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
617
|
+
{!isNestedDropDown && (
|
|
618
|
+
<TextField
|
|
619
|
+
label='Default Value Set By Query String Parameter: '
|
|
620
|
+
value={filter.setByQueryParameter || ''}
|
|
621
|
+
updateField={(_section, _subSection, _key, value) => updateFilterProp('setByQueryParameter', value)}
|
|
622
|
+
/>
|
|
623
|
+
)}
|
|
535
624
|
</>
|
|
536
625
|
)}
|
|
537
626
|
</>
|
|
@@ -22,13 +22,16 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
|
|
|
22
22
|
const datasets = Object.keys(config.datasets)
|
|
23
23
|
const columnNameOptionsInDataset = []
|
|
24
24
|
datasets.map(datasetKey => {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
const data = config.datasets[datasetKey].data
|
|
26
|
+
if (data) {
|
|
27
|
+
const columnNamesInDataset = Object.keys(data[0])
|
|
28
|
+
columnNamesInDataset.forEach(columnName =>
|
|
29
|
+
columnNameOptionsInDataset.push({
|
|
30
|
+
datasetKey,
|
|
31
|
+
columnName
|
|
32
|
+
})
|
|
33
|
+
)
|
|
34
|
+
}
|
|
32
35
|
})
|
|
33
36
|
|
|
34
37
|
const subGroupingColumnNameOptions = []
|
|
@@ -14,6 +14,7 @@ import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavi
|
|
|
14
14
|
import * as apiFilterHelpers from '../../helpers/apiFilterHelpers'
|
|
15
15
|
import { applyQueuedActive } from '@cdc/core/components/Filters/helpers/applyQueuedActive'
|
|
16
16
|
import './dashboardfilter.styles.css'
|
|
17
|
+
import { updateChildFilters } from '../../helpers/updateChildFilters'
|
|
17
18
|
|
|
18
19
|
type SubOptions = { subOptions?: Record<'value' | 'text', string>[] }
|
|
19
20
|
|
|
@@ -64,17 +65,15 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
64
65
|
const queryParams = getQueryParams()
|
|
65
66
|
let needsQueryUpdate = false
|
|
66
67
|
dashboardConfig.sharedFilters.forEach(sharedFilter => {
|
|
67
|
-
if (sharedFilter.queuedActive)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
needsQueryUpdate = true
|
|
77
|
-
}
|
|
68
|
+
if (sharedFilter.queuedActive) applyQueuedActive(sharedFilter)
|
|
69
|
+
if (
|
|
70
|
+
sharedFilter.setByQueryParameter &&
|
|
71
|
+
queryParams[sharedFilter.setByQueryParameter] !== sharedFilter.active
|
|
72
|
+
) {
|
|
73
|
+
queryParams[sharedFilter.setByQueryParameter] = Array.isArray(sharedFilter.active)
|
|
74
|
+
? sharedFilter.active.join(',')
|
|
75
|
+
: sharedFilter.active
|
|
76
|
+
needsQueryUpdate = true
|
|
78
77
|
}
|
|
79
78
|
})
|
|
80
79
|
|
|
@@ -183,7 +182,7 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
183
182
|
>
|
|
184
183
|
<Filters
|
|
185
184
|
show={visualizationConfig?.sharedFilterIndexes?.map(Number)}
|
|
186
|
-
filters={dashboardConfig.dashboard.sharedFilters || []}
|
|
185
|
+
filters={updateChildFilters(dashboardConfig.dashboard.sharedFilters, state.data) || []}
|
|
187
186
|
apiFilterDropdowns={apiFilterDropdowns}
|
|
188
187
|
handleOnChange={handleOnChange}
|
|
189
188
|
showSubmit={visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad}
|
|
@@ -68,6 +68,7 @@ type VizRowProps = {
|
|
|
68
68
|
updateChildConfig: Function
|
|
69
69
|
apiFilterDropdowns: APIFilterDropdowns
|
|
70
70
|
currentViewport: ViewPort
|
|
71
|
+
isLastRow: boolean
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
const VisualizationRow: React.FC<VizRowProps> = ({
|
|
@@ -80,7 +81,8 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
80
81
|
setSharedFilter,
|
|
81
82
|
updateChildConfig,
|
|
82
83
|
apiFilterDropdowns,
|
|
83
|
-
currentViewport
|
|
84
|
+
currentViewport,
|
|
85
|
+
isLastRow
|
|
84
86
|
}) => {
|
|
85
87
|
const { config, filteredData: dashboardFilteredData, data: rawData } = useContext(DashboardContext)
|
|
86
88
|
const [show, setShow] = React.useState(row.columns.map((col, i) => i === 0))
|
|
@@ -156,11 +158,19 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
156
158
|
|
|
157
159
|
const shouldShow = row.toggle === undefined || (row.toggle && show[colIndex])
|
|
158
160
|
|
|
161
|
+
const hiddenDashboardFilters =
|
|
162
|
+
visualizationConfig.type === 'dashboardFilters' &&
|
|
163
|
+
visualizationConfig.sharedFilterIndexes &&
|
|
164
|
+
visualizationConfig.sharedFilterIndexes.filter(
|
|
165
|
+
idx => config.dashboard.sharedFilters?.[idx]?.showDropdown === false
|
|
166
|
+
).length === visualizationConfig.sharedFilterIndexes.length
|
|
167
|
+
const hasMarginBottom = !isLastRow && !hiddenDashboardFilters
|
|
168
|
+
|
|
159
169
|
return (
|
|
160
170
|
<div
|
|
161
171
|
key={`vis__${index}__${colIndex}`}
|
|
162
172
|
className={`col-12 col-md-${col.width}${!shouldShow ? ' d-none' : ''}${
|
|
163
|
-
hideVisualization ? ' hide-parent-visualization' : ' mb-4'
|
|
173
|
+
hideVisualization ? ' hide-parent-visualization' : hasMarginBottom ? ' mb-4' : ''
|
|
164
174
|
}`}
|
|
165
175
|
>
|
|
166
176
|
{row.toggle && !hideVisualization && (
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
2
|
import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
|
|
3
3
|
import { SharedFilter } from '../types/SharedFilter'
|
|
4
|
+
import { handleSorting } from '@cdc/core/components/Filters'
|
|
4
5
|
|
|
5
6
|
// Gets filter values from dataset
|
|
6
7
|
const generateValuesForFilter = (columnName: string, data: Record<string, any[]>) => {
|
|
@@ -38,8 +39,8 @@ export const addValuesToDashboardFilters = (
|
|
|
38
39
|
if (filter.type === 'urlfilter') return filter
|
|
39
40
|
const filterCopy = _.cloneDeep(filter)
|
|
40
41
|
const filterValues = generateValuesForFilter(getSelector(filter), data)
|
|
41
|
-
|
|
42
42
|
filterCopy.values = filterValues
|
|
43
|
+
|
|
43
44
|
if (filterValues.length > 0) {
|
|
44
45
|
const queryStringFilterValue = getQueryStringFilterValue(filterCopy)
|
|
45
46
|
if (queryStringFilterValue) {
|
|
@@ -49,11 +50,11 @@ export const addValuesToDashboardFilters = (
|
|
|
49
50
|
const active: string[] = Array.isArray(filterCopy.active) ? filterCopy.active : [filterCopy.active]
|
|
50
51
|
filterCopy.active = active.filter(val => defaultValues.includes(val))
|
|
51
52
|
} else {
|
|
52
|
-
const
|
|
53
|
-
const defaultValue =
|
|
54
|
-
filterCopy.active = defaultValue
|
|
53
|
+
const hasResetLabel = filters.find(filter => filter.resetLabel)
|
|
54
|
+
const defaultValue = hasResetLabel ? hasResetLabel.resetLabel : filterCopy.active || filterCopy.values[0]
|
|
55
|
+
filterCopy.active = filterCopy.defaultValue || defaultValue
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
|
-
return filterCopy
|
|
58
|
+
return handleSorting(filterCopy)
|
|
58
59
|
})
|
|
59
60
|
}
|
|
@@ -134,10 +134,16 @@ export const setAutoLoadDefaultValue = (
|
|
|
134
134
|
): SharedFilter => {
|
|
135
135
|
const sharedFiltersCopy = _.cloneDeep(sharedFilters)
|
|
136
136
|
const sharedFilter = _.cloneDeep(sharedFiltersCopy[sharedFilterIndex])
|
|
137
|
-
|
|
138
|
-
const hasQueryParameter = sharedFilter.setByQueryParameter
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
|
|
138
|
+
const hasQueryParameter = sharedFilter.setByQueryParameter ? defaultQueryParamValue !== undefined : false
|
|
139
|
+
if (!autoLoadFilterIndexes.length || !dropdownOptions?.length) {
|
|
140
|
+
if (hasQueryParameter && sharedFilter.apiFilter) {
|
|
141
|
+
const subQueryValue = getQueryParam(sharedFilter.subGrouping?.setByQueryParameter)
|
|
142
|
+
const isNestedDropdown = subQueryValue !== undefined
|
|
143
|
+
sharedFilter.queuedActive = isNestedDropdown ? [defaultQueryParamValue, subQueryValue] : defaultQueryParamValue
|
|
144
|
+
}
|
|
145
|
+
return sharedFilter // no autoLoading happening
|
|
146
|
+
}
|
|
141
147
|
if (autoLoadFilterIndexes.includes(sharedFilterIndex) || hasQueryParameter) {
|
|
142
148
|
const filterParents = sharedFiltersCopy.filter(f => sharedFilter.parents?.includes(f.key))
|
|
143
149
|
const notAllParentFiltersSelected = filterParents.some(p => !(p.active || p.queuedActive))
|
|
@@ -148,7 +154,6 @@ export const setAutoLoadDefaultValue = (
|
|
|
148
154
|
setActiveNestedDropdown(dropdownOptions, sharedFilter)
|
|
149
155
|
} else {
|
|
150
156
|
const defaultValue = dropdownOptions[0]?.value
|
|
151
|
-
const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
|
|
152
157
|
if (!sharedFilter.active) {
|
|
153
158
|
sharedFilter.active = defaultQueryParamValue || defaultValue
|
|
154
159
|
} else {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
2
|
import { FilterBehavior } from '../helpers/FilterBehavior'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getQueryParams,
|
|
5
|
+
removeQueryParam,
|
|
6
|
+
updateQueryParam,
|
|
7
|
+
updateQueryString
|
|
8
|
+
} from '@cdc/core/helpers/queryStringUtils'
|
|
4
9
|
import { SharedFilter } from '../types/SharedFilter'
|
|
5
10
|
import { DashboardFilters } from '../types/DashboardFilters'
|
|
6
11
|
import { FILTER_STYLE } from '../types/FilterStyles'
|
|
@@ -12,9 +17,11 @@ const handleChildren = (sharedFilters: SharedFilter[], parentIndex: number) => {
|
|
|
12
17
|
.filter(i => i !== null)
|
|
13
18
|
if (childFilterIndexes.length) {
|
|
14
19
|
childFilterIndexes.forEach(filterIndex => {
|
|
15
|
-
sharedFilters[filterIndex]
|
|
16
|
-
if (
|
|
17
|
-
|
|
20
|
+
const cur = sharedFilters[filterIndex]
|
|
21
|
+
if (cur.setByQueryParameter) removeQueryParam(cur.setByQueryParameter)
|
|
22
|
+
cur.active = ''
|
|
23
|
+
if (cur.subGrouping) {
|
|
24
|
+
cur.subGrouping.active = ''
|
|
18
25
|
}
|
|
19
26
|
})
|
|
20
27
|
}
|
|
@@ -45,7 +52,13 @@ export const changeFilterActive = (
|
|
|
45
52
|
updateQueryString(queryParams)
|
|
46
53
|
}
|
|
47
54
|
}
|
|
55
|
+
} else if (currentFilter.subGrouping) {
|
|
56
|
+
updateQueryParam(currentFilter.setByQueryParameter, value[0])
|
|
57
|
+
updateQueryParam(currentFilter.subGrouping.setByQueryParameter, value[1])
|
|
58
|
+
sharedFiltersCopy[filterIndex].queuedActive = value
|
|
48
59
|
} else {
|
|
60
|
+
const paramVal = Array.isArray(value) ? value.join(',') : value
|
|
61
|
+
if (currentFilter.setByQueryParameter) updateQueryParam(currentFilter.setByQueryParameter, paramVal)
|
|
49
62
|
sharedFiltersCopy[filterIndex].queuedActive = value
|
|
50
63
|
}
|
|
51
64
|
return [sharedFiltersCopy, handleChildren(sharedFiltersCopy, filterIndex)]
|
|
@@ -6,11 +6,18 @@ import { getFormattedData } from './getFormattedData'
|
|
|
6
6
|
import { getVizKeys } from './getVizKeys'
|
|
7
7
|
|
|
8
8
|
export const getApplicableFilters = (dashboard: Dashboard, key: string | number): false | SharedFilter[] => {
|
|
9
|
-
const c = dashboard.sharedFilters?.filter(
|
|
9
|
+
const c = dashboard.sharedFilters?.filter(
|
|
10
|
+
sharedFilter =>
|
|
11
|
+
(sharedFilter.usedBy && sharedFilter.usedBy.indexOf(`${key}`) !== -1) || sharedFilter.usedBy?.indexOf(key) !== -1
|
|
12
|
+
)
|
|
10
13
|
return c?.length > 0 ? c : false
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
export const getFilteredData = (
|
|
16
|
+
export const getFilteredData = (
|
|
17
|
+
state: DashboardState,
|
|
18
|
+
initialFilteredData?: Record<string, any>,
|
|
19
|
+
dataOverride?: Object
|
|
20
|
+
) => {
|
|
14
21
|
const newFilteredData = initialFilteredData || {}
|
|
15
22
|
const { config } = state
|
|
16
23
|
getVizKeys(config).forEach(key => {
|
|
@@ -18,7 +25,8 @@ export const getFilteredData = (state: DashboardState, initialFilteredData?: Rec
|
|
|
18
25
|
if (applicableFilters) {
|
|
19
26
|
const { dataKey, data, dataDescription } = config.visualizations[key]
|
|
20
27
|
const _data = (dataOverride || state.data)[dataKey] || data
|
|
21
|
-
const formattedData =
|
|
28
|
+
const formattedData =
|
|
29
|
+
dataOverride?.[dataKey] || (dataDescription ? getFormattedData(_data, dataDescription) : _data)
|
|
22
30
|
|
|
23
31
|
newFilteredData[key] = filterData(applicableFilters, formattedData)
|
|
24
32
|
}
|
|
@@ -29,7 +37,8 @@ export const getFilteredData = (state: DashboardState, initialFilteredData?: Rec
|
|
|
29
37
|
const { dataKey, data, dataDescription } = row
|
|
30
38
|
const _data = (dataOverride || state.data)[dataKey] || data
|
|
31
39
|
if (applicableFilters) {
|
|
32
|
-
const formattedData =
|
|
40
|
+
const formattedData =
|
|
41
|
+
dataOverride?.[dataKey] ?? dataDescription ? getFormattedData(_data, dataDescription) : _data
|
|
33
42
|
|
|
34
43
|
newFilteredData[index] = filterData(applicableFilters, formattedData)
|
|
35
44
|
} else {
|
|
@@ -31,7 +31,7 @@ export const getUpdateConfig =
|
|
|
31
31
|
const defaultValues = _filter.pivot ? _filter.values : _filter.values[0]
|
|
32
32
|
|
|
33
33
|
const queryStringFilterValue = getQueryStringFilterValue(_filter)
|
|
34
|
-
if(queryStringFilterValue){
|
|
34
|
+
if (queryStringFilterValue) {
|
|
35
35
|
_filter.active = queryStringFilterValue
|
|
36
36
|
} else {
|
|
37
37
|
_filter.active = _filter.active || defaultValues
|
|
@@ -57,19 +57,26 @@ export const getUpdateConfig =
|
|
|
57
57
|
visualizationKeys.forEach(visualizationKey => {
|
|
58
58
|
const row = vizRowColumnLocator[visualizationKey]
|
|
59
59
|
if (newConfig.rows[row]?.datakey) return // data configured on the row level
|
|
60
|
-
const applicableFilters = newConfig.dashboard.sharedFilters.filter(
|
|
60
|
+
const applicableFilters = newConfig.dashboard.sharedFilters.filter(
|
|
61
|
+
sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) !== -1
|
|
62
|
+
)
|
|
61
63
|
|
|
62
64
|
if (applicableFilters.length > 0) {
|
|
63
65
|
const visualization = newConfig.visualizations[visualizationKey]
|
|
64
66
|
const _newConfigDataSet = newConfig.datasets[visualization.dataKey]
|
|
65
|
-
const formattedData = getFormattedData(
|
|
67
|
+
const formattedData = getFormattedData(
|
|
68
|
+
_newConfigDataSet?.data || visualization.data,
|
|
69
|
+
visualization.dataDescription
|
|
70
|
+
)
|
|
66
71
|
const _data = formattedData || (dataOverride || state.data)[visualization.dataKey]
|
|
67
72
|
newFilteredData[visualizationKey] = filterData(applicableFilters, _data)
|
|
68
73
|
}
|
|
69
74
|
})
|
|
70
75
|
|
|
71
76
|
newConfig.rows.forEach((row, rowIndex) => {
|
|
72
|
-
const applicableFilters = newConfig.dashboard.sharedFilters.filter(
|
|
77
|
+
const applicableFilters = newConfig.dashboard.sharedFilters.filter(
|
|
78
|
+
sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(rowIndex) !== -1
|
|
79
|
+
)
|
|
73
80
|
|
|
74
81
|
if (applicableFilters.length > 0) {
|
|
75
82
|
const formattedData = getFormattedData(row.data, row.dataDescription)
|
|
@@ -42,7 +42,7 @@ export const loadAPIFiltersFactory = (
|
|
|
42
42
|
return Promise.all(
|
|
43
43
|
Object.keys(toFetch).map(
|
|
44
44
|
endpoint =>
|
|
45
|
-
new Promise<
|
|
45
|
+
new Promise<{ error: boolean }>(resolve => {
|
|
46
46
|
fetch(endpoint)
|
|
47
47
|
.then(resp => resp.json())
|
|
48
48
|
.then(data => {
|
|
@@ -67,13 +67,15 @@ export const loadAPIFiltersFactory = (
|
|
|
67
67
|
type: 'ADD_ERROR_MESSAGE',
|
|
68
68
|
payload: 'There was a problem returning data. Please try again.'
|
|
69
69
|
})
|
|
70
|
+
resolve({ error: true })
|
|
70
71
|
})
|
|
71
72
|
.finally(() => {
|
|
72
|
-
resolve()
|
|
73
|
+
resolve({ error: false })
|
|
73
74
|
})
|
|
74
75
|
})
|
|
75
76
|
)
|
|
76
|
-
).then(
|
|
77
|
+
).then(responses => {
|
|
78
|
+
const hasError = responses.some(({ error }) => error)
|
|
77
79
|
const toLoad = sharedFilters.reduce((acc, curr, index) => {
|
|
78
80
|
// the filter is autoloading and it hasn't finished yet
|
|
79
81
|
if (_autoLoadFilterIndexes.includes(index) && !curr.active) {
|
|
@@ -84,7 +86,7 @@ export const loadAPIFiltersFactory = (
|
|
|
84
86
|
}
|
|
85
87
|
return acc
|
|
86
88
|
}, [])
|
|
87
|
-
if (!toLoad.length || recursiveLimit === 0) {
|
|
89
|
+
if (hasError || !toLoad.length || recursiveLimit === 0) {
|
|
88
90
|
setAPIFilterDropdowns(newDropdowns)
|
|
89
91
|
dispatch({ type: 'SET_SHARED_FILTERS', payload: sharedFilters })
|
|
90
92
|
return sharedFilters
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SharedFilter } from '../../types/SharedFilter'
|
|
2
|
+
import { updateChildFilters } from '../updateChildFilters'
|
|
3
|
+
|
|
4
|
+
describe('updateChildFilters', () => {
|
|
5
|
+
it('should filter data based on the provided filters', () => {
|
|
6
|
+
const filters = [
|
|
7
|
+
{
|
|
8
|
+
tier: 1,
|
|
9
|
+
columnName: 'name',
|
|
10
|
+
active: 'John',
|
|
11
|
+
key: 'Parent Filter',
|
|
12
|
+
values: ['John', 'Kelly', 'Norman', 'Jane']
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
tier: 2,
|
|
16
|
+
columnName: 'lastName',
|
|
17
|
+
active: '',
|
|
18
|
+
key: 'Child Filter',
|
|
19
|
+
parents: 'Parent Filter',
|
|
20
|
+
values: ['Deer', 'Roberts']
|
|
21
|
+
}
|
|
22
|
+
] as SharedFilter[]
|
|
23
|
+
const data = {
|
|
24
|
+
vizKey: [
|
|
25
|
+
[
|
|
26
|
+
{ name: 'John', lastName: 'Deer' },
|
|
27
|
+
{ name: 'John', lastName: 'Roberts' },
|
|
28
|
+
{ name: 'Kelly', lastName: 'Adams' },
|
|
29
|
+
{ name: 'Norman', lastName: 'Sally' },
|
|
30
|
+
{ name: 'Jane', lastName: 'Gorman' }
|
|
31
|
+
]
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let exprectedResult = [
|
|
36
|
+
{
|
|
37
|
+
tier: 1,
|
|
38
|
+
columnName: 'name',
|
|
39
|
+
active: 'John',
|
|
40
|
+
key: 'Parent Filter',
|
|
41
|
+
values: ['John', 'Kelly', 'Norman', 'Jane']
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
tier: 2,
|
|
45
|
+
columnName: 'lastName',
|
|
46
|
+
active: '',
|
|
47
|
+
key: 'Child Filter',
|
|
48
|
+
parents: 'Parent Filter',
|
|
49
|
+
values: ['Deer', 'Roberts'] // updated values only
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
const result = updateChildFilters(filters, data)
|
|
53
|
+
|
|
54
|
+
expect(result).toEqual(exprectedResult)
|
|
55
|
+
})
|
|
56
|
+
})
|