@cdc/dashboard 4.25.1 → 4.25.3-6

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 (38) hide show
  1. package/dist/cdcdashboard.js +53114 -52737
  2. package/examples/all-components.json +529 -4607
  3. package/examples/dashboard-gallery.json +397 -397
  4. package/examples/private/DEV-10527.json +845 -0
  5. package/examples/private/DEV-10586.json +54319 -0
  6. package/examples/private/DEV-10856.json +54319 -0
  7. package/examples/private/DEV-9932.json +95 -0
  8. package/examples/private/dashboard-map-filter.json +815 -0
  9. package/examples/private/dev-10856-2.json +1348 -0
  10. package/examples/private/feelings.json +1 -0
  11. package/examples/private/markup.json +115 -0
  12. package/examples/private/nhis.json +1792 -0
  13. package/index.html +4 -3
  14. package/package.json +9 -9
  15. package/src/CdcDashboard.tsx +5 -8
  16. package/src/CdcDashboardComponent.tsx +58 -56
  17. package/src/_stories/Dashboard.stories.tsx +31 -0
  18. package/src/_stories/_mock/dashboard-filter-asc.json +551 -0
  19. package/src/components/CollapsibleVisualizationRow.tsx +1 -1
  20. package/src/components/DashboardFilters/DashboardFilters.tsx +10 -5
  21. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +22 -5
  22. package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +13 -3
  23. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +129 -40
  24. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +10 -7
  25. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +11 -12
  26. package/src/components/DashboardFilters/dashboardfilter.styles.css +0 -2
  27. package/src/components/VisualizationRow.tsx +29 -15
  28. package/src/helpers/addValuesToDashboardFilters.ts +6 -5
  29. package/src/helpers/apiFilterHelpers.ts +10 -5
  30. package/src/helpers/changeFilterActive.ts +17 -4
  31. package/src/helpers/getFilteredData.ts +13 -4
  32. package/src/helpers/getUpdateConfig.ts +11 -4
  33. package/src/helpers/loadAPIFilters.ts +6 -4
  34. package/src/helpers/tests/updatesChildFilters.test.ts +56 -0
  35. package/src/helpers/updateChildFilters.ts +50 -0
  36. package/src/scss/main.scss +0 -3
  37. package/src/store/dashboard.reducer.ts +46 -24
  38. 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> = ({ filter, config, updateFilterProp }) => {
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 handleFilterStyleChange = value => {
120
- updateFilterProp('filterStyle', value)
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 => handleFilterStyleChange(e.target.value)}
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) => updateFilterProp('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
- <NestedDropDownDashboard
455
- filter={filter}
456
- updateFilterProp={(name, value) => {
457
- updateFilterProp(name, value)
458
- }}
459
- isDashboard={true}
460
- config={config}
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
- <TextField
531
- label='Default Value Set By Query String Parameter: '
532
- value={filter.setByQueryParameter || ''}
533
- updateField={(_section, _subSection, _key, value) => updateFilterProp('setByQueryParameter', value)}
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 columnNamesInDataset = Object.keys(config.datasets[datasetKey].data[0])
26
- columnNamesInDataset.forEach(columnName =>
27
- columnNameOptionsInDataset.push({
28
- datasetKey,
29
- columnName
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
- 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
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}
@@ -1,7 +1,5 @@
1
1
  .cove-dashboard-filters-container {
2
2
  :is(label) {
3
- margin-bottom: 0;
4
- margin-top: 0.5rem;
5
3
  font-size: var(--filter-label-font-size);
6
4
  font-weight: 700;
7
5
  }
@@ -1,5 +1,5 @@
1
1
  import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
2
- import React, { useContext, useMemo } from 'react'
2
+ import React, { useContext, useEffect, useMemo, useState } from 'react'
3
3
  import Toggle from './Toggle'
4
4
  import _ from 'lodash'
5
5
  import { ConfigRow } from '../types/ConfigRow'
@@ -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,13 +81,23 @@ 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
- const [show, setShow] = React.useState(row.columns.map((col, i) => i === 0))
87
- const setToggled = (colIndex: number) => {
88
- setShow(show.map((_, i) => i === colIndex))
89
- }
88
+ const [toggledRow, setToggled] = React.useState<number>(0)
89
+
90
+ useEffect(() => {
91
+ if (row.toggle) setToggled(0)
92
+ }, [config.activeDashboard, index])
93
+
94
+ const show = useMemo(() => {
95
+ if (row.toggle) {
96
+ return row.columns.map((col, i) => i === toggledRow)
97
+ } else {
98
+ return row.columns.map((col, i) => true)
99
+ }
100
+ }, [config.activeDashboard, toggledRow])
90
101
 
91
102
  const footnotesConfig = useMemo(() => {
92
103
  if (row.footnotesId) {
@@ -125,6 +136,9 @@ const VisualizationRow: React.FC<VizRowProps> = ({
125
136
  }
126
137
  return (
127
138
  <div className={`row${row.equalHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`} key={`row__${index}`}>
139
+ {row.toggle && !inNoDataState && (
140
+ <Toggle row={row} visualizations={config.visualizations} active={toggledRow} setToggled={setToggled} />
141
+ )}
128
142
  {row.columns.map((col, colIndex) => {
129
143
  if (col.width) {
130
144
  if (!col.widget) return <div key={`row__${index}__col__${colIndex}`} className={`col col-${col.width}`}></div>
@@ -156,21 +170,21 @@ const VisualizationRow: React.FC<VizRowProps> = ({
156
170
 
157
171
  const shouldShow = row.toggle === undefined || (row.toggle && show[colIndex])
158
172
 
173
+ const hiddenDashboardFilters =
174
+ visualizationConfig.type === 'dashboardFilters' &&
175
+ visualizationConfig.sharedFilterIndexes &&
176
+ visualizationConfig.sharedFilterIndexes.filter(
177
+ idx => config.dashboard.sharedFilters?.[idx]?.showDropdown === false
178
+ ).length === visualizationConfig.sharedFilterIndexes.length
179
+ const hasMarginBottom = !isLastRow && !hiddenDashboardFilters
180
+
159
181
  return (
160
182
  <div
161
183
  key={`vis__${index}__${colIndex}`}
162
184
  className={`col-12 col-md-${col.width}${!shouldShow ? ' d-none' : ''}${
163
- hideVisualization ? ' hide-parent-visualization' : ' mb-4'
185
+ hideVisualization ? ' hide-parent-visualization' : hasMarginBottom ? ' mb-4' : ''
164
186
  }`}
165
187
  >
166
- {row.toggle && !hideVisualization && (
167
- <Toggle
168
- row={row}
169
- visualizations={config.visualizations}
170
- active={show.indexOf(true)}
171
- setToggled={setToggled}
172
- />
173
- )}
174
188
  <VisualizationWrapper
175
189
  allExpanded={allExpanded}
176
190
  currentViewport={currentViewport}
@@ -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 defaultLabel = filters.find(filter => filter.resetLabel)
53
- const defaultValue = defaultLabel ? defaultLabel.resetLabel : filterCopy.values[0] || filterCopy.active
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
- if (!autoLoadFilterIndexes.length || !dropdownOptions?.length) return sharedFilter // no autoLoading happening
138
- const hasQueryParameter = sharedFilter.setByQueryParameter
139
- ? Boolean(getQueryParam(sharedFilter.setByQueryParameter))
140
- : false
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 { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
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].active = ''
16
- if (sharedFilters[filterIndex].subGrouping) {
17
- sharedFilters[filterIndex].subGrouping.active = ''
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(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(`${key}`) !== -1)
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 = (state: DashboardState, initialFilteredData?: Record<string, any>, dataOverride?: Object) => {
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 = dataOverride?.[dataKey] || (dataDescription ? getFormattedData(_data, dataDescription) : _data)
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 = dataOverride?.[dataKey] ?? dataDescription ? getFormattedData(_data, dataDescription) : _data
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(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) !== -1)
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(_newConfigDataSet?.data || visualization.data, visualization.dataDescription)
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(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(rowIndex) !== -1)
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<void>(resolve => {
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