@cdc/dashboard 4.24.11 → 4.24.12-2

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 (39) hide show
  1. package/dist/cdcdashboard.js +49442 -49230
  2. package/examples/ed-visits-county-file.json +141 -357
  3. package/examples/private/DEV-10120.json +1294 -0
  4. package/examples/private/DEV-9199.json +606 -0
  5. package/examples/private/DEV-9684.json +2135 -0
  6. package/examples/private/DEV-9989.json +229 -0
  7. package/examples/private/art-dashboard.json +18174 -0
  8. package/examples/private/art-scratch.json +2406 -0
  9. package/examples/private/dashboard-config-ehdi.json +29915 -0
  10. package/examples/private/dashboard-margins.js +15 -0
  11. package/examples/private/dataset.json +1452 -0
  12. package/examples/private/ehdi-data.json +29502 -0
  13. package/examples/private/gaza-issue.json +1214 -0
  14. package/examples/private/workforce.json +2041 -0
  15. package/package.json +9 -9
  16. package/src/CdcDashboard.tsx +8 -15
  17. package/src/CdcDashboardComponent.tsx +53 -38
  18. package/src/DashboardContext.tsx +2 -0
  19. package/src/components/CollapsibleVisualizationRow.tsx +8 -2
  20. package/src/components/DashboardFilters/DashboardFilters.tsx +107 -59
  21. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +2 -0
  22. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +54 -50
  23. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +6 -2
  24. package/src/components/DashboardFilters/dashboardfilter.styles.css +16 -0
  25. package/src/components/VisualizationRow.tsx +30 -20
  26. package/src/components/Widget.tsx +1 -1
  27. package/src/data/initial-state.js +2 -1
  28. package/src/helpers/addValuesToDashboardFilters.ts +4 -2
  29. package/src/helpers/apiFilterHelpers.ts +55 -20
  30. package/src/helpers/changeFilterActive.ts +3 -0
  31. package/src/helpers/filterData.ts +1 -1
  32. package/src/helpers/loadAPIFilters.ts +25 -8
  33. package/src/helpers/reloadURLHelpers.ts +9 -2
  34. package/src/helpers/shouldLoadAllFilters.ts +30 -0
  35. package/src/helpers/tests/apiFilterHelpers.test.ts +85 -4
  36. package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +8 -3
  37. package/src/helpers/tests/reloadURLHelpers.test.ts +11 -5
  38. package/src/helpers/tests/shouldLoadAllFilters.test.ts +117 -0
  39. package/src/store/dashboard.reducer.ts +2 -1
@@ -41,11 +41,10 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
41
41
  if (viz.type === 'dashboardFilters') return false
42
42
  const vizName = viz.general?.title || viz.title || vizKey
43
43
  nameLookup[vizKey] = vizName
44
- const notAdded = !filter.usedBy || filter.usedBy.indexOf(vizKey) === -1
45
44
  const usesSharedFilter = viz.usesSharedFilter
46
45
  const rowIndex = vizLookup.row
47
46
  const dataConfiguredOnRow = config.rows[rowIndex].dataKey
48
- return filter.setBy !== vizKey && notAdded && !usesSharedFilter && !dataConfiguredOnRow
47
+ return filter.setBy !== vizKey && !usesSharedFilter && !dataConfiguredOnRow
49
48
  })
50
49
  const rowOptions: number[] = []
51
50
 
@@ -363,20 +362,23 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
363
362
  {isNestedDropDown && <APIInputs isSubgroup={true} />}
364
363
 
365
364
  {!!parentFilters.length && (
366
- <MultiSelect
367
- label='Parent Filter(s): '
368
- options={parentFilters.map(key => ({ value: key, label: key }))}
369
- fieldName='parents'
370
- selected={filter.parents}
371
- updateField={(_section, _subsection, _fieldname, newItems) => {
372
- updateFilterProp('parents', newItems)
373
- }}
374
- />
365
+ <label>
366
+ <span className='edit-label column-heading mt-1'>Parent Filter(s): </span>
367
+ <MultiSelect
368
+ label='Parent Filter(s): '
369
+ options={parentFilters.map(key => ({ value: key, label: key }))}
370
+ fieldName='parents'
371
+ selected={filter.parents}
372
+ updateField={(_section, _subsection, _fieldname, newItems) => {
373
+ updateFilterProp('parents', newItems)
374
+ }}
375
+ />
376
+ </label>
375
377
  )}
376
378
 
377
- <MultiSelect
378
- label='Used By: (optional)'
379
- tooltip={
379
+ <label>
380
+ <span className='edit-label column-heading mt-1'>
381
+ Used By: (optional)
380
382
  <Tooltip style={{ textTransform: 'none' }}>
381
383
  <Tooltip.Target>
382
384
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
@@ -388,17 +390,19 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
388
390
  </p>
389
391
  </Tooltip.Content>
390
392
  </Tooltip>
391
- }
392
- options={[...usedByOptions, ...(filter.usedBy || [])].map(opt => ({
393
- value: opt,
394
- label: usedByNameLookup[opt]
395
- }))}
396
- fieldName='usedBy'
397
- selected={filter.usedBy}
398
- updateField={(_section, _subsection, _fieldname, newItems) => {
399
- updateFilterProp('usedBy', newItems)
400
- }}
401
- />
393
+ </span>
394
+ <MultiSelect
395
+ options={usedByOptions.map(opt => ({
396
+ value: opt,
397
+ label: usedByNameLookup[opt]
398
+ }))}
399
+ fieldName='usedBy'
400
+ selected={filter.usedBy}
401
+ updateField={(_section, _subsection, _fieldname, newItems) => {
402
+ updateFilterProp('usedBy', newItems)
403
+ }}
404
+ />
405
+ </label>
402
406
 
403
407
  <TextField
404
408
  label='Reset Label: '
@@ -473,31 +477,31 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
473
477
  </select>
474
478
  </label>
475
479
  <label>
476
- <span className='edit-label column-heading'>Used By: </span>
477
- <ul>
478
- {filter.usedBy &&
479
- filter.usedBy.map(opt => (
480
- <li key={`used-by-list-item-${opt}`}>
481
- <span>{usedByNameLookup[opt] || opt}</span>{' '}
482
- <button
483
- onClick={e => {
484
- e.preventDefault()
485
- removeFilterUsedBy(filter, opt)
486
- }}
487
- >
488
- X
489
- </button>
490
- </li>
491
- ))}
492
- </ul>
493
- <select value='' onChange={e => addFilterUsedBy(filter, e.target.value)}>
494
- <option value=''>- Select Option -</option>
495
- {usedByOptions.map(opt => (
496
- <option value={opt} key={`used-by-select-item-${opt}`}>
497
- {usedByNameLookup[opt] || opt}
498
- </option>
499
- ))}
500
- </select>
480
+ <span className='edit-label column-heading mt-1'>
481
+ Used By: (optional)
482
+ <Tooltip style={{ textTransform: 'none' }}>
483
+ <Tooltip.Target>
484
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
485
+ </Tooltip.Target>
486
+ <Tooltip.Content>
487
+ <p>
488
+ Select if you would like specific visualizations or rows to use this filter. Otherwise the
489
+ filter will be added to all api requests.
490
+ </p>
491
+ </Tooltip.Content>
492
+ </Tooltip>
493
+ </span>
494
+ <MultiSelect
495
+ options={usedByOptions.map(opt => ({
496
+ value: opt,
497
+ label: usedByNameLookup[opt]
498
+ }))}
499
+ fieldName='usedBy'
500
+ selected={filter.usedBy}
501
+ updateField={(_section, _subsection, _fieldname, newItems) => {
502
+ updateFilterProp('usedBy', newItems)
503
+ }}
504
+ />
501
505
  </label>
502
506
  <TextField
503
507
  label='Reset Label: '
@@ -31,6 +31,7 @@ type DashboardFiltersProps = {
31
31
  isEditor?: boolean
32
32
  setConfig: (config: DashboardFilters) => void
33
33
  currentViewport?: ViewPort
34
+ setAPILoading: (loading: boolean) => void
34
35
  }
35
36
 
36
37
  const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
@@ -41,7 +42,7 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
41
42
  isEditor = false
42
43
  }) => {
43
44
  const state = useContext(DashboardContext)
44
- const { config: dashboardConfig, reloadURLData, loadAPIFilters, setAPIFilterDropdowns } = state
45
+ const { config: dashboardConfig, reloadURLData, loadAPIFilters, setAPIFilterDropdowns, setAPILoading } = state
45
46
  const dispatch = useContext(DashboardDispatchContext)
46
47
 
47
48
  const applyFilters = e => {
@@ -81,7 +82,7 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
81
82
  updateQueryString(queryParams)
82
83
  }
83
84
  }
84
-
85
+ setAPILoading(true)
85
86
  dispatch({ type: 'SET_SHARED_FILTERS', payload: dashboardConfig.sharedFilters })
86
87
  dispatch({ type: 'SET_FILTERED_DATA', payload: getFilteredData(_.cloneDeep(state)) })
87
88
  loadAPIFilters(dashboardConfig.sharedFilters, apiFilterDropdowns)
@@ -105,6 +106,9 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
105
106
  visualizationConfig
106
107
  )
107
108
 
109
+ // sets the active filter option that the user just selected.
110
+ dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
111
+
108
112
  if (hasDashboardApplyBehavior(dashboardConfig.visualizations)) {
109
113
  const isAutoSelectFilter = visualizationConfig.autoLoad
110
114
  const missingFilterSelections = newConfig.dashboard.sharedFilters.some(f => !f.active)
@@ -8,4 +8,20 @@
8
8
  height: calc(1.5em + 0.75rem + 2px);
9
9
  align-self: flex-end;
10
10
  }
11
+ .loading-filter {
12
+ position: relative;
13
+ .spinner-border {
14
+ position: absolute;
15
+ top: 55%;
16
+ right: 10%;
17
+ width: 1.5rem;
18
+ height: 1.5rem;
19
+ }
20
+ }
21
+ :is(select):disabled {
22
+ background-color: var(--lightestGray);
23
+ & > :is(option) {
24
+ color: var(--darkGray);
25
+ }
26
+ }
11
27
  }
@@ -24,6 +24,7 @@ type VisualizationWrapperProps = {
24
24
  children: React.ReactNode
25
25
  currentViewport: ViewPort
26
26
  groupName: string
27
+ hideVisualization: boolean
27
28
  row: ConfigRow
28
29
  }
29
30
 
@@ -31,10 +32,13 @@ const VisualizationWrapper: React.FC<VisualizationWrapperProps> = ({
31
32
  allExpanded,
32
33
  currentViewport,
33
34
  groupName,
35
+ hideVisualization,
34
36
  row,
35
37
  children
36
38
  }) => {
37
- return row.expandCollapseAllButtons ? (
39
+ return hideVisualization ? (
40
+ <></>
41
+ ) : row.expandCollapseAllButtons ? (
38
42
  <div className='collapsable-multiviz-container'>
39
43
  <CollapsibleVisualizationRow
40
44
  allExpanded={allExpanded}
@@ -47,7 +51,7 @@ const VisualizationWrapper: React.FC<VisualizationWrapperProps> = ({
47
51
  </div>
48
52
  ) : (
49
53
  <>
50
- <h3>{groupName}</h3>
54
+ {groupName !== '' ? <h3>{groupName}</h3> : <></>}
51
55
  {children}
52
56
  </>
53
57
  )
@@ -59,6 +63,7 @@ type VizRowProps = {
59
63
  groupName: string
60
64
  row: ConfigRow
61
65
  rowIndex: number
66
+ inNoDataState: boolean
62
67
  setSharedFilter: Function
63
68
  updateChildConfig: Function
64
69
  apiFilterDropdowns: APIFilterDropdowns
@@ -71,6 +76,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
71
76
  groupName,
72
77
  row,
73
78
  rowIndex: index,
79
+ inNoDataState,
74
80
  setSharedFilter,
75
81
  updateChildConfig,
76
82
  apiFilterDropdowns,
@@ -81,11 +87,6 @@ const VisualizationRow: React.FC<VizRowProps> = ({
81
87
  const setToggled = (colIndex: number) => {
82
88
  setShow(show.map((_, i) => i === colIndex))
83
89
  }
84
- const inNoDataState = useMemo(() => {
85
- const vals = Object.values(rawData).flatMap(val => val)
86
- if (!vals.length) return true
87
- return vals.some(val => val === undefined)
88
- }, [rawData])
89
90
 
90
91
  const footnotesConfig = useMemo(() => {
91
92
  if (row.footnotesId) {
@@ -95,7 +96,10 @@ const VisualizationRow: React.FC<VizRowProps> = ({
95
96
  // the multiViz filtering filtering is applied after the dashboard filters
96
97
  const categoryFootnote = footnoteConfig.formattedData.filter(d => d[row.multiVizColumn] === vizCategory)
97
98
  footnoteConfig.formattedData = categoryFootnote
99
+ } else {
100
+ footnoteConfig.formattedData = dashboardFilteredData[row.footnotesId]
98
101
  }
102
+
99
103
  return footnoteConfig
100
104
  }
101
105
  return null
@@ -120,13 +124,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
120
124
  return false
121
125
  }
122
126
  return (
123
- <div
124
- className={`row mb-5 ${row.equalHeight ? 'equal-height' : ''} ${row.toggle ? 'toggle' : ''}`}
125
- key={`row__${index}`}
126
- >
127
- {row.toggle && (
128
- <Toggle row={row} visualizations={config.visualizations} active={show.indexOf(true)} setToggled={setToggled} />
129
- )}
127
+ <div className={`row${row.equalHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`} key={`row__${index}`}>
130
128
  {row.columns.map((col, colIndex) => {
131
129
  if (col.width) {
132
130
  if (!col.widget) return <div key={`row__${index}__col__${colIndex}`} className={`col col-${col.width}`}></div>
@@ -150,22 +148,34 @@ const VisualizationRow: React.FC<VizRowProps> = ({
150
148
  {visualizationConfig.dataKey} (Go to Table)
151
149
  </a>
152
150
  )
153
- const hideFilter =
151
+
152
+ const hideVisualization =
154
153
  inNoDataState &&
155
- visualizationConfig.type === 'dashboardFilters' &&
156
- applyButtonNotClicked(visualizationConfig)
154
+ visualizationConfig.filterBehavior !== 'Apply Button' &&
155
+ (visualizationConfig.type !== 'dashboardFilters' || applyButtonNotClicked(visualizationConfig))
157
156
 
158
157
  const shouldShow = row.toggle === undefined || (row.toggle && show[colIndex])
159
158
 
160
159
  return (
161
160
  <div
162
161
  key={`vis__${index}__${colIndex}`}
163
- className={`p-1 col-12 col-md-${col.width} ${!shouldShow ? 'd-none' : ''}`}
162
+ className={`col-12 col-md-${col.width}${!shouldShow ? ' d-none' : ''}${
163
+ hideVisualization ? ' hide-parent-visualization' : ' mt-5 p-1'
164
+ }`}
164
165
  >
166
+ {row.toggle && !hideVisualization && (
167
+ <Toggle
168
+ row={row}
169
+ visualizations={config.visualizations}
170
+ active={show.indexOf(true)}
171
+ setToggled={setToggled}
172
+ />
173
+ )}
165
174
  <VisualizationWrapper
166
175
  allExpanded={allExpanded}
167
176
  currentViewport={currentViewport}
168
177
  groupName={groupName}
178
+ hideVisualization={hideVisualization}
169
179
  row={row}
170
180
  >
171
181
  {visualizationConfig.type === 'chart' && (
@@ -272,7 +282,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
272
282
  configUrl={undefined}
273
283
  />
274
284
  )}
275
- {visualizationConfig.type === 'dashboardFilters' && !hideFilter && (
285
+ {visualizationConfig.type === 'dashboardFilters' && (
276
286
  <DashboardSharedFilters
277
287
  setConfig={newConfig => {
278
288
  updateChildConfig(col.widget, newConfig)
@@ -308,7 +318,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
308
318
  }
309
319
  return <React.Fragment key={`vis__${index}__${colIndex}`}></React.Fragment>
310
320
  })}
311
- {row.footnotesId ? (
321
+ {row.footnotesId && !inNoDataState ? (
312
322
  <FootnotesStandAlone
313
323
  isEditor={false}
314
324
  visualizationKey={row.footnotesId}
@@ -90,7 +90,7 @@ const Widget = ({ widgetConfig, addVisualization, type }: WidgetProps) => {
90
90
  const loadSampleData = () => {
91
91
  const dataKey = config.rows[widgetConfig.rowIdx]?.dataKey || widgetConfig?.dataKey
92
92
  const dataset = config.datasets[dataKey]
93
- const _data = data[dataset.dataUrl]
93
+ const _data = data[dataset?.dataUrl]
94
94
  if (_data && !_data.length) {
95
95
  const url = changeDataLimit(dataset.dataUrl, 100)
96
96
  fetchRemoteData(url).then(responseData => {
@@ -1,6 +1,7 @@
1
1
  export default {
2
2
  dashboard: {
3
- theme: 'theme-blue'
3
+ theme: 'theme-blue',
4
+ sharedFilters: []
4
5
  },
5
6
  rows: [[{ width: 12 }, {}, {}]],
6
7
  visualizations: {},
@@ -30,9 +30,11 @@ const getSelector = (filter: SharedFilter) => {
30
30
 
31
31
  export const addValuesToDashboardFilters = (
32
32
  filters: SharedFilter[],
33
- data: Record<string, any[]>
33
+ data: Record<string, any[]>,
34
+ filtersToSkip: number[] = []
34
35
  ): Array<SharedFilter> => {
35
- return filters?.map(filter => {
36
+ return filters?.map((filter, index) => {
37
+ if (filtersToSkip.includes(index)) return filter
36
38
  if (filter.type === 'urlfilter') return filter
37
39
  const filterCopy = _.cloneDeep(filter)
38
40
  const filterValues = generateValuesForFilter(getSelector(filter), data)
@@ -3,7 +3,7 @@ import { APIFilterDropdowns, DropdownOptions } from '../components/DashboardFilt
3
3
  import { APIFilter } from '../types/APIFilter'
4
4
  import { SharedFilter } from '../types/SharedFilter'
5
5
  import _ from 'lodash'
6
- import { getQueryParams } from '@cdc/core/helpers/queryStringUtils'
6
+ import { getQueryParam } from '@cdc/core/helpers/queryStringUtils'
7
7
  import { FILTER_STYLE } from '../types/FilterStyles'
8
8
 
9
9
  /** key for the dropdowns object */
@@ -17,10 +17,10 @@ export const getLoadingFilterMemo = (
17
17
  apiFiltersEndpoints.reduce((acc, endpoint, currIndex) => {
18
18
  const _key: DropdownsKey = endpoint
19
19
  const hasChanged = changedChildFilterIndexes.includes(currIndex)
20
- if (apiFilterDropdowns[_key] != null && !hasChanged) {
20
+ if (apiFilterDropdowns[_key] && !hasChanged) {
21
21
  acc[_key] = apiFilterDropdowns[_key]
22
22
  } else {
23
- acc[_key] = null
23
+ acc[_key] = undefined
24
24
  }
25
25
  return acc
26
26
  }, {})
@@ -37,7 +37,7 @@ export const getParentParams = (
37
37
  const key = filter.apiFilter.valueSelector || ''
38
38
  const subKey = filter.apiFilter.subgroupValueSelector || ''
39
39
  const val = filter.queuedActive ? filter.queuedActive[0] : (filter.active as string) || ''
40
- const subVal = filter.queuedActive ? filter.queuedActive[1] : filter.subGrouping.active || ''
40
+ const subVal = filter.queuedActive ? filter.queuedActive[1] : filter.subGrouping?.active || ''
41
41
  return [
42
42
  { key, value: val },
43
43
  { key: subKey, value: subVal }
@@ -53,6 +53,8 @@ export const getParentParams = (
53
53
  })
54
54
  }
55
55
 
56
+ export const notAllParentsSelected = parentParams => parentParams?.some(({ value }) => value === '')
57
+
56
58
  export const getFilterValues = (data: Array<Object>, apiFilter: APIFilter): DropdownOptions => {
57
59
  const { textSelector, valueSelector, subgroupTextSelector, subgroupValueSelector } = apiFilter
58
60
  if (subgroupValueSelector) {
@@ -83,9 +85,8 @@ export const getToFetch = (
83
85
  const _key = baseEndpoint
84
86
  if (apiFilterDropdowns[_key]) return // don't reload cached filter
85
87
  const parentParams = getParentParams(filter, sharedFilters)
86
- const notAllParentsSelected = parentParams?.some(({ value }) => value === '')
87
88
 
88
- if (notAllParentsSelected) return // don't send request for dependent children filter options
89
+ if (notAllParentsSelected(parentParams)) return // don't send request for dependent children filter options
89
90
 
90
91
  const endpoint = baseEndpoint + (parentParams ? gatherQueryParams(baseEndpoint, parentParams) : '')
91
92
  toFetch[endpoint] = [_key, index]
@@ -93,6 +94,38 @@ export const getToFetch = (
93
94
  return toFetch
94
95
  }
95
96
 
97
+ export const setActiveNestedDropdown = (dropdownOptions, sharedFilter) => {
98
+ const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
99
+ const defaultValue = dropdownOptions[0]?.value
100
+ const subDefaultValue = dropdownOptions[0]?.subOptions[0].value
101
+ const subDefaultQueryParamValue = getQueryParam(sharedFilter?.subGrouping.setByQueryParameter)
102
+ if (!sharedFilter.active) {
103
+ sharedFilter.active = defaultQueryParamValue || defaultValue
104
+ sharedFilter.subGrouping.active = subDefaultQueryParamValue || subDefaultValue
105
+ } else {
106
+ const currentOption = dropdownOptions.find(option => option.value === sharedFilter.active)
107
+ sharedFilter.active = currentOption ? currentOption.value : defaultValue
108
+ if (currentOption) {
109
+ const currentSubOption = currentOption.subOptions.find(option => option.value === sharedFilter.subGrouping.active)
110
+ sharedFilter.subGrouping.active = currentSubOption?.value || subDefaultValue
111
+ } else {
112
+ sharedFilter.subGrouping.active = subDefaultValue
113
+ }
114
+ }
115
+ }
116
+
117
+ export const setActiveMultiDropdown = (dropdownOptions, sharedFilter) => {
118
+ const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
119
+ const multiDefaultQueryParamValue = Array.isArray(defaultQueryParamValue)
120
+ ? defaultQueryParamValue
121
+ : defaultQueryParamValue?.split(',')
122
+ const multiDefaultValue = defaultQueryParamValue ? multiDefaultQueryParamValue : [dropdownOptions[0]?.value]
123
+ const currentOption = ((sharedFilter.active as string[]) || []).filter(activeVal =>
124
+ dropdownOptions.find(option => option.value === activeVal)
125
+ )
126
+ sharedFilter.active = currentOption.length ? currentOption : multiDefaultValue
127
+ }
128
+
96
129
  export const setAutoLoadDefaultValue = (
97
130
  sharedFilterIndex: number,
98
131
  dropdownOptions: DropdownOptions,
@@ -102,24 +135,26 @@ export const setAutoLoadDefaultValue = (
102
135
  const sharedFiltersCopy = _.cloneDeep(sharedFilters)
103
136
  const sharedFilter = _.cloneDeep(sharedFiltersCopy[sharedFilterIndex])
104
137
  if (!autoLoadFilterIndexes.length || !dropdownOptions?.length) return sharedFilter // no autoLoading happening
105
- if (autoLoadFilterIndexes.includes(sharedFilterIndex)) {
138
+ const hasQueryParameter = sharedFilter.setByQueryParameter
139
+ ? Boolean(getQueryParam(sharedFilter.setByQueryParameter))
140
+ : false
141
+ if (autoLoadFilterIndexes.includes(sharedFilterIndex) || hasQueryParameter) {
106
142
  const filterParents = sharedFiltersCopy.filter(f => sharedFilter.parents?.includes(f.key))
107
143
  const notAllParentFiltersSelected = filterParents.some(p => !(p.active || p.queuedActive))
108
144
  if (filterParents && notAllParentFiltersSelected) return sharedFilter
109
- const defaultValue =
110
- sharedFilter.filterStyle === FILTER_STYLE.multiSelect ? [dropdownOptions[0]?.value] : dropdownOptions[0]?.value
111
- if (!sharedFilter.active) {
112
- const queryParams = getQueryParams()
113
- const defaultQueryParamValue = queryParams[sharedFilter?.setByQueryParameter]
114
- sharedFilter.active = defaultQueryParamValue || defaultValue
115
- } else if (sharedFilter.filterStyle === FILTER_STYLE.multiSelect) {
116
- const currentOption = (sharedFilter.active as string[]).filter(activeVal =>
117
- dropdownOptions.find(option => option.value === activeVal)
118
- )
119
- sharedFilter.active = currentOption.length ? currentOption : defaultValue
145
+ if (sharedFilter.filterStyle === FILTER_STYLE.multiSelect) {
146
+ setActiveMultiDropdown(dropdownOptions, sharedFilter)
147
+ } else if (sharedFilter.filterStyle === FILTER_STYLE.nestedDropdown) {
148
+ setActiveNestedDropdown(dropdownOptions, sharedFilter)
120
149
  } else {
121
- const currentOption = dropdownOptions.find(option => option.value === sharedFilter.active)
122
- sharedFilter.active = currentOption ? currentOption.value : defaultValue
150
+ const defaultValue = dropdownOptions[0]?.value
151
+ const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
152
+ if (!sharedFilter.active) {
153
+ sharedFilter.active = defaultQueryParamValue || defaultValue
154
+ } else {
155
+ const currentOption = dropdownOptions.find(option => option.value === sharedFilter.active)
156
+ sharedFilter.active = currentOption ? currentOption.value : defaultValue
157
+ }
123
158
  }
124
159
  }
125
160
  return sharedFilter
@@ -13,6 +13,9 @@ const handleChildren = (sharedFilters: SharedFilter[], parentIndex: number) => {
13
13
  if (childFilterIndexes.length) {
14
14
  childFilterIndexes.forEach(filterIndex => {
15
15
  sharedFilters[filterIndex].active = ''
16
+ if (sharedFilters[filterIndex].subGrouping) {
17
+ sharedFilters[filterIndex].subGrouping.active = ''
18
+ }
16
19
  })
17
20
  }
18
21
  return childFilterIndexes
@@ -24,7 +24,7 @@ function getMaxTierAndSetFilterTiers(filters: SharedFilter[]): number {
24
24
  }
25
25
 
26
26
  function filter(data = [], filters: SharedFilter[], condition) {
27
- const activeFilters = filters.filter(f => f.resetLabel !== f.active)
27
+ const activeFilters = _.filter(filters, f => (f.resetLabel === f.active ? f.values?.includes(f.resetLabel) : true))
28
28
  return data.filter(row => {
29
29
  const foundMatchingFilter = activeFilters.find(filter => {
30
30
  const currentValue = row[filter.columnName]
@@ -3,6 +3,7 @@ import { APIFilterDropdowns } from '../components/DashboardFilters'
3
3
  import { SharedFilter } from '../types/SharedFilter'
4
4
  import * as apiFilterHelpers from './apiFilterHelpers'
5
5
  import { APIFilter } from '../types/APIFilter'
6
+ import { getParentParams, notAllParentsSelected } from './apiFilterHelpers'
6
7
 
7
8
  export const loadAPIFiltersFactory = (
8
9
  dispatch: Function,
@@ -13,20 +14,30 @@ export const loadAPIFiltersFactory = (
13
14
  const loadAPIFilters = (
14
15
  sharedFilters: SharedFilter[],
15
16
  dropdowns: APIFilterDropdowns,
16
- recursiveLimit = 3
17
+ loadAll?: boolean,
18
+ recursiveLimit = 50
17
19
  ): Promise<SharedFilter[]> => {
18
20
  if (!sharedFilters) return
21
+ const allIndexes = sharedFilters.map((_, index) => index)
22
+ const _autoLoadFilterIndexes = loadAll ? allIndexes : autoLoadFilterIndexes
19
23
  sharedFilters = sharedFilters.map((filter, index) =>
20
24
  apiFilterHelpers.setAutoLoadDefaultValue(
21
25
  index,
22
26
  dropdowns[filter.apiFilter?.apiEndpoint],
23
27
  sharedFilters,
24
- autoLoadFilterIndexes
28
+ _autoLoadFilterIndexes
25
29
  )
26
30
  )
27
31
  const sharedAPIFilters = sharedFilters.filter(f => f.apiFilter)
28
32
  const filterLookup = new Map(sharedAPIFilters.map(filter => [filter.apiFilter.apiEndpoint, filter.apiFilter]))
29
33
  const toFetch = apiFilterHelpers.getToFetch(sharedFilters, dropdowns)
34
+ const loadingDropdowns = Object.values(toFetch).reduce(
35
+ (acc, [dropdownsKey]) => ({ ...acc, [dropdownsKey]: null }),
36
+ {}
37
+ )
38
+ setAPIFilterDropdowns(currentState => {
39
+ return { ...currentState, ...loadingDropdowns }
40
+ })
30
41
  const newDropdowns = _.cloneDeep(dropdowns)
31
42
  return Promise.all(
32
43
  Object.keys(toFetch).map(
@@ -47,7 +58,7 @@ export const loadAPIFiltersFactory = (
47
58
  index,
48
59
  _filterValues,
49
60
  sharedFilters,
50
- autoLoadFilterIndexes
61
+ _autoLoadFilterIndexes
51
62
  )
52
63
  sharedFilters[index] = newDefaultSelectedFilter
53
64
  })
@@ -63,16 +74,22 @@ export const loadAPIFiltersFactory = (
63
74
  })
64
75
  )
65
76
  ).then(() => {
66
- const finishedLoading = sharedFilters.reduce((acc, curr, index) => {
67
- if (autoLoadFilterIndexes.includes(index) && !curr.active) return false
77
+ const toLoad = sharedFilters.reduce((acc, curr, index) => {
78
+ // the filter is autoloading and it hasn't finished yet
79
+ if (_autoLoadFilterIndexes.includes(index) && !curr.active) {
80
+ if (notAllParentsSelected(getParentParams(curr, sharedFilters))) {
81
+ return acc
82
+ }
83
+ return [...acc, index]
84
+ }
68
85
  return acc
69
- }, true)
70
- if (finishedLoading || recursiveLimit === 0) {
86
+ }, [])
87
+ if (!toLoad.length || recursiveLimit === 0) {
71
88
  setAPIFilterDropdowns(newDropdowns)
72
89
  dispatch({ type: 'SET_SHARED_FILTERS', payload: sharedFilters })
73
90
  return sharedFilters
74
91
  } else {
75
- return loadAPIFilters(sharedFilters, newDropdowns, recursiveLimit - 1)
92
+ return loadAPIFilters(sharedFilters, newDropdowns, loadAll, recursiveLimit - 1)
76
93
  }
77
94
  })
78
95
  }
@@ -4,6 +4,7 @@ import { capitalizeSplitAndJoin } from '@cdc/core/helpers/cove/string'
4
4
  import { AnyVisualization, Visualization } from '@cdc/core/types/Visualization'
5
5
  import _ from 'lodash'
6
6
  import { DashboardConfig } from '../types/DashboardConfig'
7
+ import { ConfigRow } from '../types/ConfigRow'
7
8
 
8
9
  export const isUpdateNeeded = (
9
10
  filters: SharedFilter[],
@@ -104,9 +105,15 @@ export const getVisualizationsWithFormattedData = (visualizations: Record<string
104
105
  export const filterUsedByDataUrl = (
105
106
  filter: SharedFilter,
106
107
  datasetKey: string,
107
- visualizations: Record<string, AnyVisualization>
108
+ visualizations: Record<string, AnyVisualization>,
109
+ rows: ConfigRow[]
108
110
  ) => {
109
111
  if (!filter.usedBy || !filter.usedBy.length) return true
110
- const vizUsingFilters = filter.usedBy?.map(vizKey => visualizations[vizKey])
112
+ const vizUsingFilters = filter.usedBy?.map(vizOrRowKey => visualizations[vizOrRowKey] || rows[vizOrRowKey])
113
+ // push any footnotes which are using the filter also
114
+ filter.usedBy?.forEach(vizOrRowKey => {
115
+ if (rows[vizOrRowKey] && rows[vizOrRowKey].footnotesId)
116
+ return vizUsingFilters.push(visualizations[rows[vizOrRowKey].footnotesId])
117
+ })
111
118
  return vizUsingFilters?.some(viz => viz?.dataKey === datasetKey)
112
119
  }
@@ -0,0 +1,30 @@
1
+ import { getQueryParam } from '@cdc/core/helpers/queryStringUtils'
2
+ import { Visualization } from '@cdc/core/types/Visualization'
3
+
4
+ export const shouldLoadAllFilters = (config, isEditorPanel): boolean => {
5
+ const autoLoad = Boolean(getQueryParam('cove-auto-load'))
6
+ const activeConfig = config.multiDashboards ? config.multiDashboards[config.activeDashboard] : config
7
+ const hasFilterByFileNameFunctionality = activeConfig.dashboard.sharedFilters?.some(
8
+ filter => filter.filterBy === 'File Name'
9
+ )
10
+ const isAutoLoadTab = Object.values(activeConfig.visualizations).reduce((acc, viz: Visualization) => {
11
+ if (acc === false) return acc
12
+ if (viz.visualizationType === 'dashboardFilters') {
13
+ if (viz.filterBehavior === 'Apply Button') return false
14
+ if (viz.autoLoad) {
15
+ return true
16
+ }
17
+ }
18
+ return acc
19
+ }, undefined)
20
+ if (autoLoad || isAutoLoadTab || hasFilterByFileNameFunctionality || isEditorPanel) {
21
+ const rowDataSetKeys = activeConfig.rows.map(row => row.dataKey).filter(Boolean)
22
+ const dataKeys = Object.values(activeConfig.visualizations)
23
+ .map((visualization: Visualization) => visualization.dataKey)
24
+ .filter(Boolean)
25
+ .concat(rowDataSetKeys)
26
+ const missingData = dataKeys.find(dataset => !config.datasets[dataset].data?.length)
27
+ return Boolean(missingData)
28
+ }
29
+ return false
30
+ }