@cdc/dashboard 4.24.10 → 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 (58) hide show
  1. package/dist/cdcdashboard.js +51165 -49100
  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-9644.json +20092 -0
  6. package/examples/private/DEV-9684.json +2135 -0
  7. package/examples/private/DEV-9989.json +229 -0
  8. package/examples/private/art-dashboard.json +18174 -0
  9. package/examples/private/art-scratch.json +2406 -0
  10. package/examples/private/dashboard-config-ehdi.json +29915 -0
  11. package/examples/private/dashboard-margins.js +15 -0
  12. package/examples/private/dataset.json +1452 -0
  13. package/examples/private/ehdi-data.json +29502 -0
  14. package/examples/private/gaza-issue.json +1214 -0
  15. package/examples/private/workforce.json +2041 -0
  16. package/package.json +9 -9
  17. package/src/CdcDashboard.tsx +43 -29
  18. package/src/CdcDashboardComponent.tsx +91 -52
  19. package/src/DashboardContext.tsx +2 -0
  20. package/src/_stories/Dashboard.stories.tsx +8 -0
  21. package/src/_stories/_mock/api-filter-error.json +55 -0
  22. package/src/_stories/_mock/group-pivot-filter.json +10 -5
  23. package/src/components/CollapsibleVisualizationRow.tsx +8 -2
  24. package/src/components/DashboardFilters/DashboardFilters.tsx +121 -58
  25. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +3 -1
  26. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +54 -50
  27. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +13 -7
  28. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +21 -0
  29. package/src/components/DashboardFilters/dashboardfilter.styles.css +27 -0
  30. package/src/components/Grid.tsx +1 -1
  31. package/src/components/Header/Header.tsx +71 -10
  32. package/src/components/Header/index.scss +0 -5
  33. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +28 -6
  34. package/src/components/MultiConfigTabs/MultiTabs.tsx +2 -0
  35. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +4 -11
  36. package/src/components/Row.tsx +59 -13
  37. package/src/components/VisualizationRow.tsx +30 -22
  38. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +0 -1
  39. package/src/components/Widget.tsx +23 -1
  40. package/src/data/initial-state.js +2 -1
  41. package/src/helpers/addValuesToDashboardFilters.ts +4 -2
  42. package/src/helpers/apiFilterHelpers.ts +55 -20
  43. package/src/helpers/changeFilterActive.ts +3 -0
  44. package/src/helpers/filterData.ts +1 -1
  45. package/src/helpers/getVizRowColumnLocator.ts +1 -0
  46. package/src/helpers/loadAPIFilters.ts +32 -10
  47. package/src/helpers/reloadURLHelpers.ts +9 -2
  48. package/src/helpers/shouldLoadAllFilters.ts +30 -0
  49. package/src/helpers/tests/apiFilterHelpers.test.ts +85 -4
  50. package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +10 -4
  51. package/src/helpers/tests/reloadURLHelpers.test.ts +11 -5
  52. package/src/helpers/tests/shouldLoadAllFilters.test.ts +117 -0
  53. package/src/scss/editor-panel.scss +0 -3
  54. package/src/scss/grid.scss +22 -23
  55. package/src/scss/main.scss +0 -27
  56. package/src/store/dashboard.reducer.ts +9 -2
  57. package/src/store/errorMessage/errorMessage.actions.ts +7 -0
  58. package/src/store/errorMessage/errorMessage.reducer.ts +24 -0
@@ -12,11 +12,6 @@
12
12
  margin-bottom: 2px;
13
13
  width: 100%;
14
14
  }
15
- label,
16
- input:not([type='checkbox']),
17
- select {
18
- width: 100% !important;
19
- }
20
15
 
21
16
  label {
22
17
  display: flex !important;
@@ -66,8 +66,16 @@ const Tab = ({ name, handleClick, tabs, index, active }) => {
66
66
 
67
67
  return (
68
68
  <li className='nav-item d-flex mt-0'>
69
- {canMoveLeft && editing && <button onClick={() => handleReorder(index, -1)}>{'<'}</button>}
70
- <div className={`edit nav-link${active ? ' active' : ''}`} aria-current={active ? 'page' : null} onClick={onClick}>
69
+ {canMoveLeft && editing && (
70
+ <button className='border-0' onClick={() => handleReorder(index, -1)}>
71
+ {'<'}
72
+ </button>
73
+ )}
74
+ <div
75
+ className={`edit nav-link${active ? ' active' : ''}`}
76
+ aria-current={active ? 'page' : null}
77
+ onClick={onClick}
78
+ >
71
79
  {editing ? (
72
80
  <div className='d-flex'>
73
81
  <input type='text' defaultValue={name} onBlur={saveName} ref={inputRef} />
@@ -78,13 +86,17 @@ const Tab = ({ name, handleClick, tabs, index, active }) => {
78
86
  ) : (
79
87
  <>
80
88
  {name}
81
- <button className='remove' onClick={handleRemove}>
89
+ <button className='btn btn-danger border-0 ml-1' onClick={handleRemove}>
82
90
  X
83
91
  </button>
84
92
  </>
85
93
  )}
86
94
  </div>
87
- {canMoveRight && editing && <button onClick={() => handleReorder(index, 1)}>{'>'}</button>}
95
+ {canMoveRight && editing && (
96
+ <button className='border-0' onClick={() => handleReorder(index, 1)}>
97
+ {'>'}
98
+ </button>
99
+ )}
88
100
  </li>
89
101
  )
90
102
  }
@@ -92,7 +104,10 @@ const Tab = ({ name, handleClick, tabs, index, active }) => {
92
104
  const MultiConfigTabs = () => {
93
105
  const { config } = useContext(DashboardContext)
94
106
  const dispatch = useContext(DashboardDispatchContext)
95
- const tabs = useMemo<string[]>(() => (config.multiDashboards || []).map(({ label }) => label), [config.multiDashboards])
107
+ const tabs = useMemo<string[]>(
108
+ () => (config.multiDashboards || []).map(({ label }) => label),
109
+ [config.multiDashboards]
110
+ )
96
111
  const activeTab = useMemo<number>(() => config.activeDashboard, [config.activeDashboard])
97
112
 
98
113
  const saveAndLoad = (indexToSwitchTo: number) => {
@@ -104,7 +119,14 @@ const MultiConfigTabs = () => {
104
119
  return (
105
120
  <ul className='nav nav-tabs multi-config-tabs'>
106
121
  {tabs.map((tab, index) => (
107
- <Tab key={tab + index} name={tab} tabs={tabs} index={index} handleClick={() => saveAndLoad(index)} active={index === activeTab} />
122
+ <Tab
123
+ key={tab + index}
124
+ name={tab}
125
+ tabs={tabs}
126
+ index={index}
127
+ handleClick={() => saveAndLoad(index)}
128
+ active={index === activeTab}
129
+ />
108
130
  ))}
109
131
  <li className='nav-item'>
110
132
  <button className='nav-link add' onClick={() => dispatch({ type: 'ADD_NEW_DASHBOARD' })}>
@@ -2,6 +2,7 @@ import { useContext, useMemo } from 'react'
2
2
  import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
3
3
 
4
4
  import './multiconfigtabs.styles.css'
5
+ import { updateQueryParam } from '@cdc/core/helpers/queryStringUtils'
5
6
 
6
7
  const MultiTabs = () => {
7
8
  const { config } = useContext(DashboardContext)
@@ -15,6 +16,7 @@ const MultiTabs = () => {
15
16
  const load = (indexToSwitchTo: number, e) => {
16
17
  e.preventDefault() // some form wrapper is causing this to act as a submit button
17
18
  dispatch({ type: 'SWITCH_CONFIG', payload: indexToSwitchTo })
19
+ updateQueryParam('cove-tab', indexToSwitchTo)
18
20
  }
19
21
 
20
22
  if (!config.multiDashboards) return null
@@ -13,11 +13,6 @@
13
13
  &:hover {
14
14
  :is(button) {
15
15
  display: inline-block;
16
- color: var(--gray);
17
- &:hover {
18
- color: var(--black);
19
- font-weight: bold;
20
- }
21
16
  }
22
17
 
23
18
  background: var(--quaternary);
@@ -32,12 +27,10 @@
32
27
  color: white;
33
28
  font-weight: bold;
34
29
  }
35
- }
36
- .remove {
37
- color: var(--gray);
38
- &:hover {
39
- color: var(--black);
40
- font-weight: bold;
30
+ .btn-danger {
31
+ text-decoration: none;
32
+ padding: 0px 5px;
33
+ font-size: inherit;
41
34
  }
42
35
  }
43
36
  .add {
@@ -122,39 +122,82 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
122
122
  }
123
123
 
124
124
  const layoutList = [
125
- <li className={curr === '12' ? `current row-menu__list--item` : `row-menu__list--item`} onClick={() => setRowLayout([12])} key='12' title='1 Column'>
125
+ <li
126
+ className={curr === '12' ? `current row-menu__list--item` : `row-menu__list--item`}
127
+ onClick={() => setRowLayout([12])}
128
+ key='12'
129
+ title='1 Column'
130
+ >
126
131
  <OneColIcon />
127
132
  </li>,
128
- <li className={curr === '66' ? `current row-menu__list--item` : `row-menu__list--item`} onClick={() => setRowLayout([6, 6])} key='66' title='2 Columns'>
133
+ <li
134
+ className={curr === '66' ? `current row-menu__list--item` : `row-menu__list--item`}
135
+ onClick={() => setRowLayout([6, 6])}
136
+ key='66'
137
+ title='2 Columns'
138
+ >
129
139
  <TwoColIcon />
130
140
  </li>,
131
- <li className={curr === '444' ? `current row-menu__list--item` : `row-menu__list--item`} onClick={() => setRowLayout([4, 4, 4])} key='444' title='3 Columns'>
141
+ <li
142
+ className={curr === '444' ? `current row-menu__list--item` : `row-menu__list--item`}
143
+ onClick={() => setRowLayout([4, 4, 4])}
144
+ key='444'
145
+ title='3 Columns'
146
+ >
132
147
  <ThreeColIcon />
133
148
  </li>,
134
- <li className={curr === '48' ? `current row-menu__list--item` : `row-menu__list--item`} onClick={() => setRowLayout([4, 8])} key='48' title='2 Columns'>
149
+ <li
150
+ className={curr === '48' ? `current row-menu__list--item` : `row-menu__list--item`}
151
+ onClick={() => setRowLayout([4, 8])}
152
+ key='48'
153
+ title='2 Columns'
154
+ >
135
155
  <FourEightColIcon />
136
156
  </li>,
137
- <li className={curr === '84' ? `current row-menu__list--item` : `row-menu__list--item`} onClick={() => setRowLayout([8, 4])} key='84' title='2 Columns'>
157
+ <li
158
+ className={curr === '84' ? `current row-menu__list--item` : `row-menu__list--item`}
159
+ onClick={() => setRowLayout([8, 4])}
160
+ key='84'
161
+ title='2 Columns'
162
+ >
138
163
  <EightFourColIcon />
139
164
  </li>,
140
- <li className={curr === 'toggle' ? `current row-menu__list--item` : `row-menu__list--item`} onClick={() => setRowLayout([12, 12, 12], true)} key='toggle' title='Toggle between up to three visualizations'>
165
+ <li
166
+ className={curr === 'toggle' ? `current row-menu__list--item` : `row-menu__list--item`}
167
+ onClick={() => setRowLayout([12, 12, 12], true)}
168
+ key='toggle'
169
+ title='Toggle between up to three visualizations'
170
+ >
141
171
  <ToggleIcon />
142
172
  </li>
143
173
  ]
144
174
 
145
175
  return (
146
176
  <nav className='row-menu'>
147
- <div className='row-menu__btn'>
148
- <ul className='row-menu__flyout'>{layoutList}</ul>
149
- </div>
177
+ <ul className='row-menu__flyout'>{layoutList}</ul>
150
178
  <div className='spacer'></div>
151
- <button className={rowIdx === 0 ? 'row-menu__btn row-menu__btn-disabled' : 'row-menu__btn'} title='Move Row Up' onClick={() => moveRow('up')}>
179
+ <button
180
+ className={`btn btn-primary row-menu__btn border-0`}
181
+ title='Move Row Up'
182
+ onClick={() => moveRow('up')}
183
+ disabled={rowIdx === 0}
184
+ >
152
185
  <Icon display='caretUp' color='#fff' size={25} />
153
186
  </button>
154
- <button className={rowIdx + 1 === rows.length ? 'row-menu__btn row-menu__btn-disabled' : 'row-menu__btn'} title='Move Row Down' onClick={() => moveRow('down')}>
187
+ <button
188
+ className={'btn btn-primary row-menu__btn border-0'}
189
+ title='Move Row Down'
190
+ onClick={() => moveRow('down')}
191
+ disabled={rowIdx + 1 === rows.length}
192
+ >
155
193
  <Icon display='caretDown' color='#fff' size={25} />
156
194
  </button>
157
- <button className={rowIdx === 0 && rows.length === 1 ? 'row-menu__btn row-menu__btn--remove row-menu__btn-disabled' : 'row-menu__btn row-menu__btn--remove'} title='Delete Row' onClick={deleteRow}>
195
+ <button
196
+ className={'btn btn-danger row-menu__btn row-menu__btn--remove border-0'}
197
+ title='Delete Row'
198
+ onClick={deleteRow}
199
+ disabled={rowIdx === 0 && rows.length === 1}
200
+ >
158
201
  <Icon display='close' color='#fff' size={25} />
159
202
  </button>
160
203
  </nav>
@@ -177,7 +220,10 @@ const Row: React.FC<RowProps> = ({ row, idx: rowIdx, uuid }) => {
177
220
  visualizationType: type,
178
221
  editing: true
179
222
  }
180
- dispatch({ type: 'ADD_FOOTNOTE', payload: { id: uid, rowIndex: rowIdx, config: newVisualizationConfig as Visualization } })
223
+ dispatch({
224
+ type: 'ADD_FOOTNOTE',
225
+ payload: { id: uid, rowIndex: rowIdx, config: newVisualizationConfig as Visualization }
226
+ })
181
227
  } else {
182
228
  dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey: row.footnotesId, configureData: { editing: true } } })
183
229
  }
@@ -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,24 +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
- const body = <></>
161
-
162
159
  return (
163
160
  <div
164
161
  key={`vis__${index}__${colIndex}`}
165
- 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
+ }`}
166
165
  >
166
+ {row.toggle && !hideVisualization && (
167
+ <Toggle
168
+ row={row}
169
+ visualizations={config.visualizations}
170
+ active={show.indexOf(true)}
171
+ setToggled={setToggled}
172
+ />
173
+ )}
167
174
  <VisualizationWrapper
168
175
  allExpanded={allExpanded}
169
176
  currentViewport={currentViewport}
170
177
  groupName={groupName}
178
+ hideVisualization={hideVisualization}
171
179
  row={row}
172
180
  >
173
181
  {visualizationConfig.type === 'chart' && (
@@ -274,7 +282,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
274
282
  configUrl={undefined}
275
283
  />
276
284
  )}
277
- {visualizationConfig.type === 'dashboardFilters' && !hideFilter && (
285
+ {visualizationConfig.type === 'dashboardFilters' && (
278
286
  <DashboardSharedFilters
279
287
  setConfig={newConfig => {
280
288
  updateChildConfig(col.widget, newConfig)
@@ -310,7 +318,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
310
318
  }
311
319
  return <React.Fragment key={`vis__${index}__${colIndex}`}></React.Fragment>
312
320
  })}
313
- {row.footnotesId ? (
321
+ {row.footnotesId && !inNoDataState ? (
314
322
  <FootnotesStandAlone
315
323
  isEditor={false}
316
324
  visualizationKey={row.footnotesId}
@@ -119,7 +119,6 @@ const VisualizationsPanel = () => {
119
119
  <Widget addVisualization={() => addVisualization('dashboardFilters', '')} type='dashboardFilters' />
120
120
  <Widget addVisualization={() => addVisualization('table', '')} type='table' />
121
121
  </div>
122
- <span className='subheading-3'>Advanced</span>
123
122
  <AdvancedEditor
124
123
  loadConfig={loadConfig}
125
124
  config={config}
@@ -5,6 +5,7 @@ import { useGlobalContext } from '@cdc/core/components/GlobalContext'
5
5
  import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
6
6
 
7
7
  import { DataTransform } from '@cdc/core/helpers/DataTransform'
8
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
8
9
  import Icon from '@cdc/core/components/ui/Icon'
9
10
  import { AnyVisualization } from '@cdc/core/types/Visualization'
10
11
  import { iconHash } from '../helpers/iconHash'
@@ -39,7 +40,7 @@ type WidgetProps = {
39
40
 
40
41
  const Widget = ({ widgetConfig, addVisualization, type }: WidgetProps) => {
41
42
  const { overlay } = useGlobalContext()
42
- const { config } = useContext(DashboardContext)
43
+ const { config, data } = useContext(DashboardContext)
43
44
  const dispatch = useContext(DashboardDispatchContext)
44
45
 
45
46
  const transform = new DataTransform()
@@ -79,9 +80,30 @@ const Widget = ({ widgetConfig, addVisualization, type }: WidgetProps) => {
79
80
  })
80
81
  }
81
82
 
83
+ const changeDataLimit = (dataUrl, limit) => {
84
+ const url = new URL(dataUrl)
85
+ url.searchParams.set('$limit', limit)
86
+ // Replace encoded $ with actual $ for the URL
87
+ return url.href.replace(/%24/g, '$')
88
+ }
89
+
90
+ const loadSampleData = () => {
91
+ const dataKey = config.rows[widgetConfig.rowIdx]?.dataKey || widgetConfig?.dataKey
92
+ const dataset = config.datasets[dataKey]
93
+ const _data = data[dataset?.dataUrl]
94
+ if (_data && !_data.length) {
95
+ const url = changeDataLimit(dataset.dataUrl, 100)
96
+ fetchRemoteData(url).then(responseData => {
97
+ responseData.sample = true
98
+ dispatch({ type: 'SET_DATA', payload: { ...data, [dataKey]: responseData } })
99
+ })
100
+ }
101
+ }
102
+
82
103
  const editWidget = () => {
83
104
  if (!widgetConfig) return
84
105
  dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey: widgetConfig.uid, configureData: { editing: true } } })
106
+ loadSampleData()
85
107
  }
86
108
 
87
109
  let isConfigurationReady = false
@@ -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]
@@ -1,5 +1,6 @@
1
1
  import { ConfigRow } from '../types/ConfigRow'
2
2
 
3
+ // returns a dictionary of widget names and their corresponding row and column index
3
4
  export const getVizRowColumnLocator = (rows: ConfigRow[]): Record<string, { row: number; column: number }> =>
4
5
  rows.reduce((acc, curr, index) => {
5
6
  curr.columns?.forEach((column, columnIndex) => {