@cdc/dashboard 4.24.10 → 4.24.11

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 (32) hide show
  1. package/dist/cdcdashboard.js +46738 -44885
  2. package/examples/private/DEV-9644.json +20092 -0
  3. package/package.json +9 -9
  4. package/src/CdcDashboard.tsx +35 -14
  5. package/src/CdcDashboardComponent.tsx +38 -14
  6. package/src/_stories/Dashboard.stories.tsx +8 -0
  7. package/src/_stories/_mock/api-filter-error.json +55 -0
  8. package/src/_stories/_mock/group-pivot-filter.json +10 -5
  9. package/src/components/DashboardFilters/DashboardFilters.tsx +57 -42
  10. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +1 -1
  11. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +7 -5
  12. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +21 -0
  13. package/src/components/DashboardFilters/dashboardfilter.styles.css +11 -0
  14. package/src/components/Grid.tsx +1 -1
  15. package/src/components/Header/Header.tsx +71 -10
  16. package/src/components/Header/index.scss +0 -5
  17. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +28 -6
  18. package/src/components/MultiConfigTabs/MultiTabs.tsx +2 -0
  19. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +4 -11
  20. package/src/components/Row.tsx +59 -13
  21. package/src/components/VisualizationRow.tsx +0 -2
  22. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +0 -1
  23. package/src/components/Widget.tsx +23 -1
  24. package/src/helpers/getVizRowColumnLocator.ts +1 -0
  25. package/src/helpers/loadAPIFilters.ts +7 -2
  26. package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +2 -1
  27. package/src/scss/editor-panel.scss +0 -3
  28. package/src/scss/grid.scss +22 -23
  29. package/src/scss/main.scss +0 -27
  30. package/src/store/dashboard.reducer.ts +7 -1
  31. package/src/store/errorMessage/errorMessage.actions.ts +7 -0
  32. package/src/store/errorMessage/errorMessage.reducer.ts +24 -0
@@ -6,6 +6,7 @@ import './index.scss'
6
6
  import MultiConfigTabs from '../MultiConfigTabs'
7
7
  import { Tab } from '../../types/Tab'
8
8
  import _ from 'lodash'
9
+ import { getVizRowColumnLocator } from '../../helpers/getVizRowColumnLocator'
9
10
 
10
11
  type HeaderProps = {
11
12
  back?: any
@@ -16,7 +17,7 @@ type HeaderProps = {
16
17
  const Header = (props: HeaderProps) => {
17
18
  const tabs: Tab[] = ['Dashboard Description', 'Data Table Settings', 'Dashboard Preview']
18
19
  const { visualizationKey, subEditor } = props
19
- const { config, setParentConfig, tabSelected } = useContext(DashboardContext)
20
+ const { config, setParentConfig, tabSelected, data } = useContext(DashboardContext)
20
21
  if (!config) return null
21
22
  const dispatch = useContext(DashboardDispatchContext)
22
23
  const back = () => {
@@ -24,6 +25,22 @@ const Header = (props: HeaderProps) => {
24
25
  const newConfig = _.cloneDeep(config)
25
26
  newConfig.visualizations[visualizationKey].editing = false
26
27
  dispatch({ type: 'SET_CONFIG', payload: newConfig })
28
+
29
+ // the Widget component will do a data fetch if no data is available for the visualization
30
+ // this is intended to help visualization developers.
31
+ type SampleData = Record<string, { sample: boolean }> & Object[]
32
+ if (Object.values(data).some((d: SampleData) => d.sample)) {
33
+ const sampleDataRemoved = Object.keys(data).reduce((acc, key) => {
34
+ if ((data[key] as SampleData).sample) {
35
+ acc[key] = []
36
+ } else {
37
+ acc[key] = data[key]
38
+ }
39
+ return acc
40
+ }, {})
41
+
42
+ dispatch({ type: 'SET_DATA', payload: sampleDataRemoved })
43
+ }
27
44
  }
28
45
 
29
46
  const changeConfigValue = (parentObj, key, value) => {
@@ -81,10 +98,18 @@ const Header = (props: HeaderProps) => {
81
98
  <div className='heading-1'>
82
99
  Dashboard Editor{' '}
83
100
  <span className='small'>
84
- <input type='checkbox' onChange={handleCheck} checked={multiInitialized} disabled={multiInitialized} /> make multidashboard
101
+ <input type='checkbox' onChange={handleCheck} checked={multiInitialized} disabled={multiInitialized} /> make
102
+ multidashboard
85
103
  </span>
86
104
  <br />
87
- {<input type='text' placeholder='Enter Dashboard Name Here' defaultValue={config.dashboard?.title} onChange={e => changeConfigValue('dashboard', 'title', e.target.value)} />}
105
+ {
106
+ <input
107
+ type='text'
108
+ placeholder='Enter Dashboard Name Here'
109
+ defaultValue={config.dashboard?.title}
110
+ onChange={e => changeConfigValue('dashboard', 'title', e.target.value)}
111
+ />
112
+ }
88
113
  </div>
89
114
  )}
90
115
  {!subEditor && (
@@ -106,18 +131,34 @@ const Header = (props: HeaderProps) => {
106
131
  })}
107
132
  </ul>
108
133
  <div className='heading-body'>
109
- {tabSelected === 'Dashboard Description' && <input type='text' className='description-input' placeholder='Type a dashboard description here.' defaultValue={config.dashboard?.description} onChange={e => changeConfigValue('dashboard', 'description', e.target.value)} />}
134
+ {tabSelected === 'Dashboard Description' && (
135
+ <input
136
+ type='text'
137
+ className='description-input'
138
+ placeholder='Type a dashboard description here.'
139
+ defaultValue={config.dashboard?.description}
140
+ onChange={e => changeConfigValue('dashboard', 'description', e.target.value)}
141
+ />
142
+ )}
110
143
  {tabSelected === 'Data Table Settings' && (
111
144
  <>
112
145
  <div className='wrap'>
113
146
  <label>
114
- <input type='checkbox' defaultChecked={config.table.show} onChange={e => changeConfigValue('table', 'show', e.target.checked)} />
147
+ <input
148
+ type='checkbox'
149
+ defaultChecked={config.table.show}
150
+ onChange={e => changeConfigValue('table', 'show', e.target.checked)}
151
+ />
115
152
  Show Data Table(s)
116
153
  </label>
117
154
  <br />
118
155
 
119
156
  <label>
120
- <input type='checkbox' defaultChecked={config.table.expanded} onChange={e => changeConfigValue('table', 'expanded', e.target.checked)} />
157
+ <input
158
+ type='checkbox'
159
+ defaultChecked={config.table.expanded}
160
+ onChange={e => changeConfigValue('table', 'expanded', e.target.checked)}
161
+ />
121
162
  Expanded by Default
122
163
  </label>
123
164
  <br />
@@ -125,19 +166,39 @@ const Header = (props: HeaderProps) => {
125
166
 
126
167
  <div className='wrap'>
127
168
  <label>
128
- <input type='checkbox' defaultChecked={config.table.limitHeight} onChange={e => changeConfigValue('table', 'limitHeight', e.target.checked)} />
169
+ <input
170
+ type='checkbox'
171
+ defaultChecked={config.table.limitHeight}
172
+ onChange={e => changeConfigValue('table', 'limitHeight', e.target.checked)}
173
+ />
129
174
  Limit Table Height
130
175
  </label>
131
- {config.table.limitHeight && <input className='table-height-input' type='text' placeholder='Height (px)' defaultValue={config.table.height} onChange={e => changeConfigValue('table', 'height', e.target.value)} />}
176
+ {config.table.limitHeight && (
177
+ <input
178
+ className='table-height-input'
179
+ type='text'
180
+ placeholder='Height (px)'
181
+ defaultValue={config.table.height}
182
+ onChange={e => changeConfigValue('table', 'height', e.target.value)}
183
+ />
184
+ )}
132
185
  </div>
133
186
 
134
187
  <div className='wrap'>
135
188
  <label>
136
- <input type='checkbox' defaultChecked={config.table.download} onChange={e => changeConfigValue('table', 'download', e.target.checked)} />
189
+ <input
190
+ type='checkbox'
191
+ defaultChecked={config.table.download}
192
+ onChange={e => changeConfigValue('table', 'download', e.target.checked)}
193
+ />
137
194
  Show Download CSV Link
138
195
  </label>
139
196
  <label>
140
- <input type='checkbox' defaultChecked={config.table.showDownloadUrl} onChange={e => changeConfigValue('table', 'showDownloadUrl', e.target.checked)} />
197
+ <input
198
+ type='checkbox'
199
+ defaultChecked={config.table.showDownloadUrl}
200
+ onChange={e => changeConfigValue('table', 'showDownloadUrl', e.target.checked)}
201
+ />
141
202
  Show URL to Automatically Updated Data
142
203
  </label>
143
204
  </div>
@@ -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
  }
@@ -157,8 +157,6 @@ const VisualizationRow: React.FC<VizRowProps> = ({
157
157
 
158
158
  const shouldShow = row.toggle === undefined || (row.toggle && show[colIndex])
159
159
 
160
- const body = <></>
161
-
162
160
  return (
163
161
  <div
164
162
  key={`vis__${index}__${colIndex}`}
@@ -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,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) => {
@@ -6,6 +6,7 @@ import { APIFilter } from '../types/APIFilter'
6
6
 
7
7
  export const loadAPIFiltersFactory = (
8
8
  dispatch: Function,
9
+ dispatchErrorMessages: Function,
9
10
  setAPIFilterDropdowns: Function,
10
11
  autoLoadFilterIndexes: number[]
11
12
  ) => {
@@ -36,7 +37,6 @@ export const loadAPIFiltersFactory = (
36
37
  .then(data => {
37
38
  if (!Array.isArray(data)) {
38
39
  console.error('COVE only supports response data in the shape Array<Object>')
39
- return
40
40
  }
41
41
  const [_key, index] = toFetch[endpoint]
42
42
  const apiFilter = filterLookup.get(_key) as APIFilter
@@ -51,7 +51,12 @@ export const loadAPIFiltersFactory = (
51
51
  )
52
52
  sharedFilters[index] = newDefaultSelectedFilter
53
53
  })
54
- .catch(console.error)
54
+ .catch(() => {
55
+ dispatchErrorMessages({
56
+ type: 'ADD_ERROR_MESSAGE',
57
+ payload: 'There was a problem returning data. Please try again.'
58
+ })
59
+ })
55
60
  .finally(() => {
56
61
  resolve()
57
62
  })
@@ -66,6 +66,7 @@ global.fetch = fetch
66
66
 
67
67
  describe('loadAPIFiltersFactory', () => {
68
68
  const dispatch = vi.fn()
69
+ const dispatchErrorMessages = vi.fn()
69
70
  const setAPIFilterDropdowns = vi.fn()
70
71
  const apiFilterDropdowns = {
71
72
  'cdc.gov/filters/Sex': [
@@ -76,7 +77,7 @@ describe('loadAPIFiltersFactory', () => {
76
77
  afterEach(() => {
77
78
  vi.restoreAllMocks()
78
79
  })
79
- const loadAPIFilters = loadAPIFiltersFactory(dispatch, setAPIFilterDropdowns, [2])
80
+ const loadAPIFilters = loadAPIFiltersFactory(dispatch, dispatchErrorMessages, setAPIFilterDropdowns, [2])
80
81
  it('creates a function', () => {
81
82
  expect(typeof loadAPIFilters).toEqual('function')
82
83
  })
@@ -384,9 +384,6 @@
384
384
  font-weight: normal;
385
385
  }
386
386
 
387
- .btn {
388
- margin-top: 1em;
389
- }
390
387
  .sort-list {
391
388
  list-style: none;
392
389
  > li {
@@ -51,27 +51,25 @@ $red: #f74242;
51
51
  margin-top: 2em;
52
52
  }
53
53
 
54
- .row-menu__btn:hover .row-menu__flyout {
55
- transition: width 0.2s cubic-bezier(0.16, 1, 0.3, 1);
56
- width: 180px;
57
-
58
- li {
59
- display: flex;
60
- }
61
54
 
62
- li + li {
63
- margin-left: 0.3em;
64
- }
65
- }
66
55
 
67
56
  .row-menu__flyout {
57
+ background-color: var(--blue);
58
+ $blue: #005eaa;
59
+ background-color: #c2c2c2;
60
+ border-radius: 0.2em 0.2em 0 0;
61
+ outline: none;
62
+
63
+ padding: 0.2em 0.3em;
64
+ fill: #fff;
68
65
  list-style: none;
69
66
  display: flex;
70
67
  justify-content: flex-start;
71
68
  overflow: hidden;
69
+ transition: background-color 300ms cubic-bezier(0.16, 1, 0.3, 1);
72
70
  transition: width 0.2s cubic-bezier(0.16, 1, 0.3, 1);
73
71
  z-index: 1;
74
- width: 25px;
72
+ width: 35px;
75
73
 
76
74
  li:not(.current) {
77
75
  display: none;
@@ -89,6 +87,18 @@ $red: #f74242;
89
87
  .row-menu__list--item {
90
88
  display: flex;
91
89
  }
90
+ &:hover {
91
+ transition: width 0.2s cubic-bezier(0.16, 1, 0.3, 1);
92
+ width: 180px;
93
+ background-color: lighten($blue, 8%);
94
+ li {
95
+ display: flex;
96
+ }
97
+
98
+ li + li {
99
+ margin-left: 0.3em;
100
+ }
101
+ }
92
102
  }
93
103
 
94
104
  .row-menu__btn {
@@ -96,7 +106,6 @@ $red: #f74242;
96
106
  border-radius: 0.2em 0.2em 0 0;
97
107
  outline: none;
98
108
  transition: background-color 300ms cubic-bezier(0.16, 1, 0.3, 1);
99
- cursor: pointer;
100
109
  padding: 0.2em 0.3em;
101
110
  display: flex;
102
111
  fill: #fff;
@@ -294,11 +303,6 @@ $red: #f74242;
294
303
  }
295
304
  }
296
305
 
297
- .btn.add-row {
298
- font-size: 1.1em;
299
- width: 100%;
300
- }
301
-
302
306
  .btn--fluid {
303
307
  @extend .btn;
304
308
  width: 100%;
@@ -359,11 +363,6 @@ $red: #f74242;
359
363
 
360
364
  &:hover {
361
365
  .row-menu .row-menu__btn {
362
- background-color: var(--blue);
363
- $blue: #005eaa;
364
- &:hover {
365
- background-color: lighten($blue, 8%);
366
- }
367
366
 
368
367
  &.row-menu__btn--edit {
369
368
  background-color: transparent;
@@ -138,21 +138,6 @@
138
138
  }
139
139
 
140
140
  .btn {
141
- background: #005eaa;
142
- color: #fff;
143
- border: 0;
144
- padding: 0.4em 0.8em;
145
- font-size: 0.9em;
146
- display: block;
147
- border-radius: 5px;
148
- transition: 0.1s all;
149
- cursor: pointer;
150
-
151
- &[disabled] {
152
- opacity: 0.5;
153
- z-index: -1;
154
- position: relative;
155
- }
156
141
 
157
142
  // Expand and Collapse Buttons for Multiviz Dashboard
158
143
  &.expand-collapse-buttons {
@@ -260,15 +245,6 @@
260
245
  width: 100%;
261
246
  }
262
247
 
263
- .cove-dashboard-filters-container {
264
- z-index: 5;
265
- }
266
-
267
- .cove-dashboard-filters {
268
- display: inline-flex;
269
- margin: 1em;
270
- }
271
-
272
248
  @include breakpointClass(md) {
273
249
  .dashboard-row {
274
250
  flex-direction: row;
@@ -301,9 +277,6 @@
301
277
  }
302
278
  }
303
279
 
304
- .dashboard-filters-section {
305
- margin: 0 0 1em;
306
- }
307
280
  .builder-grid .editor-heading {
308
281
  position: relative;
309
282
  right: -2em;
@@ -105,8 +105,14 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
105
105
  _.remove(newMultiDashboards, (_, index) => {
106
106
  return index === action.payload
107
107
  })
108
+ const config = {
109
+ ...state.config,
110
+ multiDashboards: newMultiDashboards,
111
+ ...newMultiDashboards[0],
112
+ activeDashboard: 0
113
+ }
108
114
  if (newMultiDashboards.length === 0) return { ...state, config: _.omit(state.config, 'multiDashboards') }
109
- return applyMultiDashboards(state, newMultiDashboards)
115
+ return applyMultiDashboards({ ...state, config }, newMultiDashboards)
110
116
  }
111
117
  case 'RENAME_DASHBOARD_TAB': {
112
118
  const newMultiDashboards = state.config.multiDashboards.map(dashboard => {
@@ -0,0 +1,7 @@
1
+ import { Action } from '@cdc/core/types/Action'
2
+
3
+ type ADD_ERROR_MESSAGE = Action<'ADD_ERROR_MESSAGE', string>
4
+ type DISMISS_ERROR_MESSAGE = Action<'DISMISS_ERROR_MESSAGE', number>
5
+
6
+ type errorMessagesActions = ADD_ERROR_MESSAGE | DISMISS_ERROR_MESSAGE
7
+ export default errorMessagesActions