@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/dashboard",
3
- "version": "4.24.10",
3
+ "version": "4.24.12-2",
4
4
  "description": "React component for combining multiple visualizations into a single dashboard",
5
5
  "moduleName": "CdcDashboard",
6
6
  "main": "dist/cdcdashboard",
@@ -27,13 +27,13 @@
27
27
  },
28
28
  "license": "Apache-2.0",
29
29
  "dependencies": {
30
- "@cdc/chart": "^4.24.10",
31
- "@cdc/core": "^4.24.10",
32
- "@cdc/data-bite": "^4.24.10",
33
- "@cdc/filtered-text": "^4.24.10",
34
- "@cdc/map": "^4.24.10",
35
- "@cdc/markup-include": "^4.24.10",
36
- "@cdc/waffle-chart": "^4.24.10",
30
+ "@cdc/chart": "^4.24.12-2",
31
+ "@cdc/core": "^4.24.12-2",
32
+ "@cdc/data-bite": "^4.24.12-2",
33
+ "@cdc/filtered-text": "^4.24.12-2",
34
+ "@cdc/map": "^4.24.12-2",
35
+ "@cdc/markup-include": "^4.24.12-2",
36
+ "@cdc/waffle-chart": "^4.24.12-2",
37
37
  "html-react-parser": "^3.0.8",
38
38
  "js-base64": "^2.5.2",
39
39
  "papaparse": "^5.3.0",
@@ -49,5 +49,5 @@
49
49
  "react": "^18.2.0",
50
50
  "react-dom": "^18.2.0"
51
51
  },
52
- "gitHead": "a4d88d1bc91f596e1b0307d8e25c57ad8c668b75"
52
+ "gitHead": "a60edf1148396309eb473ac9f65426ee40797ddf"
53
53
  }
@@ -3,7 +3,6 @@ import CdcDashboard from './CdcDashboardComponent'
3
3
  import { MultiDashboardConfig } from './types/MultiDashboard'
4
4
  import Loading from '@cdc/core/components/Loading'
5
5
  import defaults from './data/initial-state'
6
- import { processData } from './helpers/processData'
7
6
  import { getVizKeys } from './helpers/getVizKeys'
8
7
  import { processDataLegacy } from './helpers/processDataLegacy'
9
8
  import { WCMSProps } from '@cdc/core/types/WCMSProps'
@@ -13,40 +12,47 @@ import { InitialState } from './types/InitialState'
13
12
  import { DashboardConfig } from './types/DashboardConfig'
14
13
  import { coveUpdateWorker } from '@cdc/core/helpers/coveUpdateWorker'
15
14
  import _ from 'lodash'
16
- import { hasDashboardApplyBehavior } from './helpers/hasDashboardApplyBehavior'
15
+ import { getQueryParams } from '@cdc/core/helpers/queryStringUtils'
17
16
 
18
17
  type MultiDashboardProps = Omit<WCMSProps, 'configUrl'> & {
19
18
  configUrl?: string
20
19
  config?: MultiDashboardConfig
21
20
  }
22
21
 
23
- const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, config: editorConfig, isEditor, isDebug }) => {
22
+ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({
23
+ configUrl,
24
+ config: editorConfig,
25
+ isEditor,
26
+ isDebug
27
+ }) => {
24
28
  const [initial, setInitial] = useState<InitialState>(undefined)
25
29
 
26
- const getSelectedConfig = (config: MultiDashboardConfig, selectedConfig?: string): number | null => {
30
+ const getSelectedConfig = (config: MultiDashboardConfig): number | null => {
27
31
  if (!config.multiDashboards) return null
28
- // TODO: if query parameter select based on query parameter
29
- if (selectedConfig) {
30
- const foundConfig = Object.values(config.multiDashboards).findIndex(({ label }) => {
31
- return label === selectedConfig
32
- })
33
- if (foundConfig > -1) return foundConfig
32
+ // if query parameter, select based on query parameter
33
+ const selectedConfig = getQueryParams()['cove-tab']
34
+ if (selectedConfig !== undefined && Number(selectedConfig) < config.multiDashboards.length) {
35
+ return Number(selectedConfig)
34
36
  }
35
37
  // else select the first available
36
38
  return 0
37
39
  }
38
40
 
39
- const formatInitialState = (newConfig: MultiDashboardConfig | DashboardConfig, datasets: Record<string, Object[]>) => {
41
+ const formatInitialState = (
42
+ newConfig: MultiDashboardConfig | DashboardConfig,
43
+ datasets: Record<string, Object[]>
44
+ ) => {
40
45
  const [config, filteredData] = getUpdateConfig(initialState)(newConfig, datasets)
41
46
  const versionedConfig = coveUpdateWorker(config)
42
47
  return { ...initialState, config: versionedConfig, filteredData, data: datasets }
43
48
  }
44
49
 
45
- const loadConfig = async (selectedConfig?: string) => {
50
+ const loadConfig = async () => {
46
51
  const _config: MultiDashboardConfig = editorConfig || (await (await fetch(configUrl)).json())
47
- const selected = getSelectedConfig(_config, selectedConfig)
52
+ const selected = getSelectedConfig(_config)
48
53
 
49
- const { newConfig, datasets } = selected !== null ? await loadMultiDashboard(_config, selected) : await loadSingleDashboard(_config)
54
+ const { newConfig, datasets } =
55
+ selected !== null ? await loadMultiDashboard(_config, selected) : await loadSingleDashboard(_config)
50
56
  setInitial(formatInitialState(newConfig, datasets))
51
57
  }
52
58
 
@@ -54,18 +60,13 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, confi
54
60
  loadConfig()
55
61
  }, [])
56
62
 
57
- const loadData = async (initialConfig: DashboardConfig | MultiDashboardConfig) => {
63
+ const prepareDatasets = (initialConfig: DashboardConfig | MultiDashboardConfig) => {
58
64
  let newConfig = { ...initialConfig }
59
- let datasets: Record<string, Object[]> = {}
60
- await Promise.all(
61
- Object.keys(initialConfig.datasets).map(async key => {
62
- const legacySupportApply = (initialConfig as any).filterBehavior === 'Apply Button'
63
- const hasApplyBehavior = hasDashboardApplyBehavior(initialConfig.visualizations) || legacySupportApply
64
- const data = await processData(initialConfig.datasets[key], !hasApplyBehavior)
65
- datasets[key] = data || []
66
- })
67
- )
68
-
65
+ const datasets: Record<string, Object[]> = Object.keys(initialConfig.datasets).reduce((acc, key) => {
66
+ const dataset = initialConfig.datasets[key]
67
+ acc[key] = dataset.formattedData || dataset.data
68
+ return acc
69
+ }, {})
69
70
  getVizKeys(newConfig).forEach(vizKey => {
70
71
  const formattedData = datasets[newConfig.visualizations[vizKey].dataKey]
71
72
  if (formattedData) {
@@ -84,7 +85,7 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, confi
84
85
  let newConfig = { ...defaults, ...config } as DashboardConfig
85
86
 
86
87
  if (config.datasets) {
87
- return await loadData(newConfig)
88
+ return prepareDatasets(newConfig)
88
89
  } else {
89
90
  const dataKey = newConfig.dataFileName || 'backwards-compatibility'
90
91
  const data = await processDataLegacy(config)
@@ -101,7 +102,14 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, confi
101
102
  newConfig.visualizations[vizKey] = { ...newConfig.visualizations[vizKey], ...newData }
102
103
  })
103
104
 
104
- const blankFields = { data: [], dataUrl: '', dataFileName: '', dataFileSourceType: '', dataDescription: {}, formattedData: [] }
105
+ const blankFields = {
106
+ data: [],
107
+ dataUrl: '',
108
+ dataFileName: '',
109
+ dataFileSourceType: '',
110
+ dataDescription: {},
111
+ formattedData: []
112
+ }
105
113
  newConfig = { ...newConfig, ...blankFields }
106
114
 
107
115
  if (newConfig.dashboard.filters) {
@@ -122,8 +130,14 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, confi
122
130
 
123
131
  const loadMultiDashboard = async (multiConfig: MultiDashboardConfig, selectedConfig: number) => {
124
132
  const selectedDashboard = multiConfig.multiDashboards[selectedConfig]
125
- let newConfig = { ...defaults, ...multiConfig, ...selectedDashboard, multiDashboards: multiConfig.multiDashboards, activeDashboard: selectedConfig } as MultiDashboardConfig
126
- return await loadData(newConfig)
133
+ const newConfig = {
134
+ ...defaults,
135
+ ...multiConfig,
136
+ ...selectedDashboard,
137
+ multiDashboards: multiConfig.multiDashboards,
138
+ activeDashboard: selectedConfig
139
+ } as MultiDashboardConfig
140
+ return prepareDatasets(newConfig)
127
141
  }
128
142
 
129
143
  if (!initial) return <Loading />
@@ -35,6 +35,7 @@ import './scss/main.scss'
35
35
 
36
36
  import VisualizationsPanel from './components/VisualizationsPanel'
37
37
  import dashboardReducer from './store/dashboard.reducer'
38
+ import errorMessagesReducer from './store/errorMessage/errorMessage.reducer'
38
39
  import { filterData } from './helpers/filterData'
39
40
  import { getVizKeys } from './helpers/getVizKeys'
40
41
  import Title from '@cdc/core/components/ui/Title'
@@ -42,13 +43,12 @@ import { type TableConfig } from '@cdc/core/components/DataTable/types/TableConf
42
43
 
43
44
  // types
44
45
  import { type SharedFilter } from './types/SharedFilter'
45
- import { type APIFilter } from './types/APIFilter'
46
46
  import { type WCMSProps } from '@cdc/core/types/WCMSProps'
47
47
  import { type InitialState } from './types/InitialState'
48
48
  import MultiTabs from './components/MultiConfigTabs'
49
49
  import _ from 'lodash'
50
50
  import EditorContext from '../../editor/src/ConfigContext'
51
- import { APIFilterDropdowns, DropdownOptions } from './components/DashboardFilters'
51
+ import { APIFilterDropdowns } from './components/DashboardFilters'
52
52
  import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
53
53
  import { ViewPort } from '@cdc/core/types/ViewPort'
54
54
  import VisualizationRow from './components/VisualizationRow'
@@ -62,9 +62,10 @@ import { addValuesToDashboardFilters } from './helpers/addValuesToDashboardFilte
62
62
  import { DashboardFilters } from './types/DashboardFilters'
63
63
  import DashboardSharedFilters from './components/DashboardFilters'
64
64
  import ExpandCollapseButtons from './components/ExpandCollapseButtons'
65
- import { hasDashboardApplyBehavior } from './helpers/hasDashboardApplyBehavior'
66
65
  import { loadAPIFiltersFactory } from './helpers/loadAPIFilters'
67
66
  import Loader from '@cdc/core/components/Loader'
67
+ import Alert from '@cdc/core/components/Alert'
68
+ import { shouldLoadAllFilters } from './helpers/shouldLoadAllFilters'
68
69
 
69
70
  type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
70
71
  initialState: InitialState
@@ -72,6 +73,7 @@ type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
72
73
 
73
74
  export default function CdcDashboard({ initialState, isEditor = false, isDebug = false }: DashboardProps) {
74
75
  const [state, dispatch] = useReducer(dashboardReducer, initialState)
76
+ const [errorMessages, dispatchErrorMessages] = useReducer(errorMessagesReducer, [])
75
77
  const editorContext = useContext(EditorContext)
76
78
  const [apiFilterDropdowns, setAPIFilterDropdowns] = useState<APIFilterDropdowns>({})
77
79
  const [currentViewport, setCurrentViewport] = useState<ViewPort>('lg')
@@ -82,7 +84,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
82
84
  const isPreview = state.tabSelected === 'Dashboard Preview'
83
85
 
84
86
  const inNoDataState = useMemo(() => {
85
- const vals = Object.values(state.data)
87
+ const vals = reloadURLHelpers.getDatasetKeys(state.config).map(key => state.data[key])
86
88
  if (!vals.length) return true
87
89
  return vals.some(val => val === undefined)
88
90
  }, [state.data])
@@ -97,7 +99,12 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
97
99
  .reduce((acc, viz: DashboardFilters) => (viz.autoLoad ? [...acc, ...viz.sharedFilterIndexes] : acc), [])
98
100
  }, [state.config.visualizations])
99
101
 
100
- const loadAPIFilters = loadAPIFiltersFactory(dispatch, setAPIFilterDropdowns, autoLoadFilterIndexes)
102
+ const loadAPIFilters = loadAPIFiltersFactory(
103
+ dispatch,
104
+ dispatchErrorMessages,
105
+ setAPIFilterDropdowns,
106
+ autoLoadFilterIndexes
107
+ )
101
108
 
102
109
  const reloadURLData = async (newFilters?: SharedFilter[]) => {
103
110
  const config = _.cloneDeep(state.config)
@@ -121,7 +128,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
121
128
  filters.forEach(filter => {
122
129
  if (
123
130
  filter.type === 'urlfilter' &&
124
- reloadURLHelpers.filterUsedByDataUrl(filter, datasetKey, config.visualizations)
131
+ reloadURLHelpers.filterUsedByDataUrl(filter, datasetKey, config.visualizations, config.rows)
125
132
  ) {
126
133
  if (filter.filterBy === 'File Name') {
127
134
  newFileName = reloadURLHelpers.getNewFileName(newFileName, filter, datasetKey)
@@ -138,10 +145,9 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
138
145
  if (!!filter.setByQueryParameter) {
139
146
  const windowQueryParams = Object.fromEntries(new URLSearchParams(window.location.search))
140
147
  const filterValue = windowQueryParams[filter.setByQueryParameter]
141
- if (filter.apiFilter) {
142
- updatedQSParams[filter.apiFilter.valueSelector] = filterValue
143
- } else {
144
- updatedQSParams[filter.setByQueryParameter] = filterValue
148
+ const queryParam = filter.apiFilter?.valueSelector || filter.setByQueryParameter
149
+ if (filterValue) {
150
+ updatedQSParams[queryParam] = filterValue
145
151
  }
146
152
  }
147
153
 
@@ -154,7 +160,9 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
154
160
  }
155
161
  })
156
162
 
157
- if (!!newFilters || reloadURLHelpers.isUpdateNeeded(filters, currentQSParams, updatedQSParams)) {
163
+ const dataNeedsUpdate = reloadURLHelpers.isUpdateNeeded(filters, currentQSParams, updatedQSParams)
164
+
165
+ if (!!newFilters || dataNeedsUpdate) {
158
166
  dataWasFetched = true
159
167
  const dataUrlFinal = reloadURLHelpers.getDataURL(
160
168
  { ...currentQSParams, ...updatedQSParams },
@@ -163,34 +171,60 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
163
171
  )
164
172
 
165
173
  setAPILoading(true)
166
- await fetchRemoteData(dataUrlFinal).then(responseData => {
167
- let data: any[] = responseData
168
- if (responseData && dataset.dataDescription) {
169
- try {
170
- data = transform.autoStandardize(data)
171
- data = transform.developerStandardize(data, dataset.dataDescription)
172
- } catch (e) {
173
- //Data not able to be standardized, leave as is
174
+ await fetchRemoteData(dataUrlFinal)
175
+ .then(responseData => {
176
+ let data: any[] = responseData
177
+ if (responseData && dataset.dataDescription) {
178
+ try {
179
+ data = transform.autoStandardize(data)
180
+ data = transform.developerStandardize(data, dataset.dataDescription)
181
+ } catch (e) {
182
+ //Data not able to be standardized, leave as is
183
+ console.error('Error standardizing data:', e)
184
+ }
174
185
  }
175
- }
176
- newDatasets[datasetKey].data = data
177
- newDatasets[datasetKey].runtimeDataUrl = dataUrlFinal
178
- newData[datasetKey] = data
179
- })
186
+ newDatasets[datasetKey].data = data
187
+ newDatasets[datasetKey].runtimeDataUrl = dataUrlFinal
188
+ newData[datasetKey] = data
189
+ })
190
+ .catch(e => {
191
+ console.error(e)
192
+ dispatchErrorMessages({
193
+ type: 'ADD_ERROR_MESSAGE',
194
+ payload: 'There was a problem returning data. Please try again.'
195
+ })
196
+ newDatasets[datasetKey].data = []
197
+ newDatasets[datasetKey].runtimeDataUrl = dataUrlFinal
198
+ newData[datasetKey] = []
199
+ })
180
200
  }
181
201
  }
182
202
  }
183
203
 
184
- if (dataWasFetched) {
185
- dispatch({ type: 'SET_DATA', payload: newData })
186
- const filtersWithNewValues = addValuesToDashboardFilters(filters, newData)
187
- const dashboardConfig = newFilters
188
- ? { ...config.dashboard, sharedFilters: filtersWithNewValues }
189
- : config.dashboard
204
+ const datasetsWithFiles = _.pickBy(newDatasets, dataset => !dataset.dataUrl)
205
+
206
+ if (dataWasFetched || Object.keys(datasetsWithFiles).length) {
207
+ const dataFiles = Object.keys(datasetsWithFiles).reduce((acc, key) => {
208
+ acc[key] = datasetsWithFiles[key].data
209
+ return acc
210
+ }, {})
211
+ const _newData = { ...newData, ...dataFiles }
212
+ dispatch({ type: 'SET_DATA', payload: _newData })
213
+ const dataFilterIndexes = config.dashboard.sharedFilters.reduce((acc, filter, index) => {
214
+ if (filter.type === 'datafilter') acc.push(index)
215
+ return acc
216
+ }, [])
217
+ const appliedFilterIndexes = Object.values(config.visualizations)
218
+ .filter(viz => viz.type === 'dashboardFilters')
219
+ .flatMap(viz => viz.sharedFilterIndexes)
220
+ .filter(index => !dataFilterIndexes.includes(index))
221
+ const filtersWithNewValues = addValuesToDashboardFilters(filters, _newData, appliedFilterIndexes)
222
+
223
+ const dashboardConfig = { ...config.dashboard, sharedFilters: filtersWithNewValues }
190
224
  const filteredData = getFilteredData(
191
225
  { ...state, config: { ...state.config, dashboard: dashboardConfig } },
192
226
  {},
193
- newData
227
+ _newData
194
228
  )
195
229
  dispatch({ type: 'SET_FILTERED_DATA', payload: filteredData })
196
230
  const visualizations = reloadURLHelpers.getVisualizationsWithFormattedData(config.visualizations, newData)
@@ -230,15 +264,16 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
230
264
  }
231
265
 
232
266
  useEffect(() => {
233
- if (isEditor && !isPreview) return
234
267
  const { config } = state
235
- if (!hasDashboardApplyBehavior(config.visualizations)) {
236
- reloadURLData()
237
- }
238
-
268
+ const loadAllFilters = shouldLoadAllFilters(config, isEditor && !isPreview)
239
269
  const sharedFiltersWithValues = addValuesToDashboardFilters(config.dashboard.sharedFilters, state.data)
240
- loadAPIFilters(sharedFiltersWithValues, apiFilterDropdowns)
241
- updateFilteredData(sharedFiltersWithValues)
270
+
271
+ loadAPIFilters(sharedFiltersWithValues, apiFilterDropdowns, loadAllFilters).then(newFilters => {
272
+ const allValuesSelected = newFilters.every(filter => {
273
+ return filter.type === 'datafilter' || filter.active
274
+ })
275
+ if (allValuesSelected) reloadURLData(newFilters)
276
+ })
242
277
  }, [isEditor, isPreview, state.config?.activeDashboard])
243
278
 
244
279
  const updateChildConfig = (visualizationKey, newConfig) => {
@@ -262,13 +297,6 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
262
297
  }
263
298
  }
264
299
 
265
- const updateFilteredData = (sharedFilters = undefined) => {
266
- const clonedState = _.cloneDeep(state)
267
- if (sharedFilters) clonedState.config.dashboard.sharedFilters = sharedFilters
268
- const newFilteredData = getFilteredData(clonedState)
269
- dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
270
- }
271
-
272
300
  const resizeObserver = new ResizeObserver(entries => {
273
301
  for (let entry of entries) {
274
302
  let newViewport = getViewport(entry.contentRect.width)
@@ -421,8 +449,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
421
449
  )
422
450
  break
423
451
  case 'dashboardFilters':
424
- const hideFilter = visualizationConfig.autoLoad && inNoDataState
425
- body = !hideFilter ? (
452
+ body = (
426
453
  <>
427
454
  <Header visualizationKey={visualizationKey} subEditor='Filter Dropdowns' />
428
455
  <DashboardSharedFilters
@@ -432,8 +459,6 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
432
459
  setConfig={_updateConfig}
433
460
  />
434
461
  </>
435
- ) : (
436
- <></>
437
462
  )
438
463
  break
439
464
  case 'table':
@@ -453,11 +478,12 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
453
478
  body = (
454
479
  <>
455
480
  <Header visualizationKey={visualizationKey} subEditor='Footnotes' />
481
+ {/* Datasets are passed in just for reference and need to be removed */}
456
482
  <FootnotesStandAlone
457
483
  visualizationKey={visualizationKey}
458
484
  config={{ ...visualizationConfig, datasets: state.config.datasets }}
459
485
  isEditor={true}
460
- updateConfig={_updateConfig}
486
+ updateConfig={conf => _updateConfig(_.omit(conf, 'datasets'))}
461
487
  />
462
488
  </>
463
489
  )
@@ -492,6 +518,14 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
492
518
  {isEditor && <Header />}
493
519
  {apiLoading && <Loader fullScreen={true} />}
494
520
  <MultiTabs isEditor={isEditor && !isPreview} />
521
+ {errorMessages.map((message, index) => (
522
+ <Alert
523
+ type='danger'
524
+ onDismiss={() => dispatchErrorMessages({ type: 'DISMISS_ERROR_MESSAGE', payload: index })}
525
+ message={message}
526
+ autoDismiss={true}
527
+ />
528
+ ))}
495
529
  <Layout.Responsive isEditor={isEditor}>
496
530
  <div className={`cdc-dashboard-inner-container${isEditor ? ' is-editor' : ''}`}>
497
531
  <Title
@@ -518,7 +552,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
518
552
  return (
519
553
  <>
520
554
  {/* Expand/Collapse All */}
521
- {row.expandCollapseAllButtons === true && (
555
+ {!inNoDataState && row.expandCollapseAllButtons === true && (
522
556
  <ExpandCollapseButtons setAllExpanded={setAllExpanded} />
523
557
  )}
524
558
  {Object.keys(dataGroups).map(groupName => {
@@ -535,6 +569,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
535
569
  updateChildConfig={updateChildConfig}
536
570
  apiFilterDropdowns={apiFilterDropdowns}
537
571
  currentViewport={currentViewport}
572
+ inNoDataState={inNoDataState}
538
573
  />
539
574
  )
540
575
  })}
@@ -552,11 +587,14 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
552
587
  updateChildConfig={updateChildConfig}
553
588
  apiFilterDropdowns={apiFilterDropdowns}
554
589
  currentViewport={currentViewport}
590
+ inNoDataState={inNoDataState}
555
591
  />
556
592
  )
557
593
  }
558
594
  })}
559
595
 
596
+ {inNoDataState ? <div className='mt-5'>Please complete your selection to continue.</div> : <></>}
597
+
560
598
  {/* Image or PDF Inserts */}
561
599
  <section className='download-buttons'>
562
600
  {config.table?.downloadImageButton && (
@@ -667,7 +705,8 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
667
705
  isDebug,
668
706
  loadAPIFilters,
669
707
  setAPIFilterDropdowns,
670
- reloadURLData
708
+ reloadURLData,
709
+ setAPILoading
671
710
  }}
672
711
  >
673
712
  <DashboardDispatchContext.Provider value={dispatch}>
@@ -17,6 +17,7 @@ type ConfigCTX = DashboardState & {
17
17
  recursiveLimit?: number
18
18
  ) => Promise<SharedFilter[]>
19
19
  setAPIFilterDropdowns: (dropdowns: APIFilterDropdowns) => void
20
+ setAPILoading: (loading: boolean) => void
20
21
  }
21
22
 
22
23
  const firstTab: Tab = 'Dashboard Description'
@@ -33,6 +34,7 @@ const initialContext: ConfigCTX = {
33
34
  outerContainerRef: () => {},
34
35
  setParentConfig: () => {},
35
36
  setAPIFilterDropdowns: () => {},
37
+ setAPILoading: () => {},
36
38
  reloadURLData: () => {},
37
39
  loadAPIFilters: () => Promise.resolve([]),
38
40
  isDebug: false,
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'
2
2
  import { faker } from '@faker-js/faker'
3
3
  import APIFiltersMapData from './_mock/api-filter-map.json'
4
4
  import APIFiltersChartData from './_mock/api-filter-chart.json'
5
+ import APIFilterErrorConfig from './_mock/api-filter-error.json'
5
6
  import ExampleConfig_1 from './_mock/dashboard-gallery.json'
6
7
  import ExampleConfig_2 from './_mock/dashboard-2.json'
7
8
  import ExampleConfig_3 from './_mock/dashboard_no_filter.json'
@@ -66,6 +67,13 @@ export const Dashboard_Filters: Story = {
66
67
  }
67
68
  }
68
69
 
70
+ export const API_Filter_Error: Story = {
71
+ args: {
72
+ config: APIFilterErrorConfig,
73
+ isEditor: false
74
+ }
75
+ }
76
+
69
77
  export const StandAloneTable: Story = {
70
78
  args: {
71
79
  config: StandaloneTable,
@@ -0,0 +1,55 @@
1
+ {
2
+ "dashboard": {
3
+ "theme": "theme-blue",
4
+ "sharedFilters": [
5
+ {
6
+ "key": "New Dashboard Filter 1",
7
+ "showDropdown": true,
8
+ "type": "urlfilter",
9
+ "apiFilter": {
10
+ "apiEndpoint": "https://nccd-cove-public-api.apps.ecpaas-dev.cdc.gov/od-public?$datakey=brfss_prevalence_cove_explore_by_t",
11
+ "valueSelector": "",
12
+ "textSelector": ""
13
+ },
14
+ "tier": 1
15
+ }
16
+ ]
17
+ },
18
+ "rows": [{ "columns": [{ "width": 12, "widget": "dashboardFilters1730831317688" }, {}, {}] }],
19
+ "visualizations": {
20
+ "dashboardFilters1730831317688": {
21
+ "filters": [],
22
+ "filterBehavior": "Filter Change",
23
+ "newViz": true,
24
+ "openModal": true,
25
+ "uid": "dashboardFilters1730831317688",
26
+ "type": "dashboardFilters",
27
+ "sharedFilterIndexes": [0],
28
+ "visualizationType": "dashboardFilters"
29
+ }
30
+ },
31
+ "table": {
32
+ "label": "Data Table",
33
+ "show": true,
34
+ "showDownloadUrl": false,
35
+ "showDownloadLinkBelow": true,
36
+ "showVertical": true
37
+ },
38
+ "newViz": true,
39
+ "datasets": {
40
+ "https://nccd-cove-public-api.apps.ecpaas-dev.cdc.gov/od-public?$datakey=brfss_prevalence_cove_explore_by_topic": {
41
+ "dataFileSize": 35061,
42
+ "dataFileName": "https://nccd-cove-public-api.apps.ecpaas-dev.cdc.gov/od-public?$datakey=brfss_prevalence_cove_explore_by_topic",
43
+ "dataFileSourceType": "url",
44
+ "dataFileFormat": "JSON",
45
+ "preview": true,
46
+ "dataUrl": "https://nccd-cove-public-api.apps.ecpaas-dev.cdc.gov/od-public?$datakey=brfss_prevalence_cove_explore_by_topic"
47
+ }
48
+ },
49
+ "isResponsiveTicks": false,
50
+ "type": "dashboard",
51
+ "barThickness": "0.37",
52
+ "xAxis": { "type": "categorical", "size": 75, "maxTickRotation": 45, "labelOffset": 0 },
53
+ "runtime": {},
54
+ "version": "4.24.10"
55
+ }
@@ -30,7 +30,12 @@
30
30
  "valueColumns": ["age", "color"]
31
31
  }
32
32
  },
33
- "columns": {},
33
+ "columns": {
34
+ "other": {
35
+ "name": "other",
36
+ "dataTable": false
37
+ }
38
+ },
34
39
  "dataFormat": {},
35
40
  "visualizationType": "table",
36
41
  "dataDescription": {
@@ -59,10 +64,10 @@
59
64
  "datasets": {
60
65
  "valid-data-chart.csv": {
61
66
  "data": [
62
- { "name": "John", "age": 25, "color": "blue", "city": "New York" },
63
- { "name": "Jane", "age": 27, "color": "red", "city": "New York" },
64
- { "name": "Jane", "age": 30, "color": "yellow", "city": "San Francisco" },
65
- { "name": "John", "age": 31, "color": "green", "city": "San Francisco" }
67
+ { "name": "John", "age": 25, "color": "blue", "other": "no", "city": "New York" },
68
+ { "name": "Jane", "age": 27, "color": "red", "other": "yes", "city": "New York" },
69
+ { "name": "Jane", "age": 30, "color": "yellow", "other": "no", "city": "San Francisco" },
70
+ { "name": "John", "age": 31, "color": "green", "other": "yes", "city": "San Francisco" }
66
71
  ],
67
72
  "dataFileSize": 178,
68
73
  "dataFileName": "valid-data-chart.csv",
@@ -8,10 +8,16 @@ type CollapsableVizRow = {
8
8
  groupName: string
9
9
  currentViewport: string
10
10
  }
11
- const CollapsibleVisualizationRow: React.FC<CollapsableVizRow> = ({ allExpanded, fontSize, groupName, currentViewport, children }) => {
11
+ const CollapsibleVisualizationRow: React.FC<CollapsableVizRow> = ({
12
+ allExpanded,
13
+ fontSize,
14
+ groupName,
15
+ currentViewport,
16
+ children
17
+ }) => {
12
18
  const [isExpanded, setIsExpanded] = useState(allExpanded)
13
19
  const fontSizes = { small: 16, medium: 18, large: 20 }
14
- const titleFontSize = ['sm', 'xs', 'xxs'].includes(currentViewport) ? '13px' : `${fontSizes[fontSize]}px`
20
+ const titleFontSize = ['xs', 'xxs'].includes(currentViewport) ? '13px' : `${fontSizes[fontSize]}px`
15
21
 
16
22
  useEffect(() => {
17
23
  setIsExpanded(allExpanded)