@cdc/dashboard 1.1.2 → 1.1.4

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.
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useCallback } from 'react';
1
+ import React, { useState, useEffect, useCallback } from 'react'
2
2
 
3
3
  // IE11
4
4
  import 'core-js/stable'
@@ -8,44 +8,56 @@ import ResizeObserver from 'resize-observer-polyfill'
8
8
  import { DndProvider } from 'react-dnd'
9
9
  import { HTML5Backend } from 'react-dnd-html5-backend'
10
10
 
11
- import parse from 'html-react-parser';
11
+ import parse from 'html-react-parser'
12
12
 
13
- import Loading from '@cdc/core/components/Loading';
14
- import DataTransform from '@cdc/core/components/DataTransform';
15
- import getViewport from '@cdc/core/helpers/getViewport';
13
+ import Loading from '@cdc/core/components/Loading'
14
+ import DataTransform from '@cdc/core/components/DataTransform'
15
+ import getViewport from '@cdc/core/helpers/getViewport'
16
16
 
17
- import CdcMap from '@cdc/map';
18
- import CdcChart from '@cdc/chart';
19
- import CdcDataBite from '@cdc/data-bite';
17
+ import CdcMap from '@cdc/map'
18
+ import CdcChart from '@cdc/chart'
19
+ import CdcDataBite from '@cdc/data-bite'
20
+ import CdcWaffleChart from '@cdc/waffle-chart'
21
+ import CdcMarkupInclude from '@cdc/markup-include'
20
22
 
21
- import EditorPanel from './components/EditorPanel';
22
- import Grid from './components/Grid';
23
- import Header from './components/Header';
24
- import Context from './context';
25
- import defaults from './data/initial-state';
26
- import Widget from './components/Widget';
27
- import DataTable from './components/DataTable';
23
+ import EditorPanel from './components/EditorPanel'
24
+ import Grid from './components/Grid'
25
+ import Header from './components/Header'
26
+ import Context from './context'
27
+ import defaults from './data/initial-state'
28
+ import Widget from './components/Widget'
29
+ import DataTable from './components/DataTable'
28
30
 
29
- import './scss/main.scss';
31
+ import Papa from 'papaparse'
32
+
33
+ import './scss/main.scss'
34
+
35
+ import { publish } from '@cdc/core/helpers/events'
30
36
 
31
37
  const addVisualization = (type, subType) => {
32
38
  let newVisualizationConfig = {
33
39
  newViz: true,
34
40
  uid: type + Date.now(),
35
41
  type
36
- };
42
+ }
37
43
 
38
- switch(type) {
44
+ switch (type) {
39
45
  case 'chart':
40
- newVisualizationConfig.visualizationType = subType;
41
- break;
46
+ newVisualizationConfig.visualizationType = subType
47
+ break
42
48
  case 'map':
43
- newVisualizationConfig.general = {};
44
- newVisualizationConfig.general.geoType = subType;
45
- break;
49
+ newVisualizationConfig.general = {}
50
+ newVisualizationConfig.general.geoType = subType
51
+ break
46
52
  case 'data-bite':
47
- newVisualizationConfig.visualizationType = type;
48
- break;
53
+ newVisualizationConfig.visualizationType = type
54
+ break
55
+ case 'waffle-chart':
56
+ newVisualizationConfig.visualizationType = type
57
+ break
58
+ case 'markup-include':
59
+ newVisualizationConfig.visualizationType = type
60
+ break
49
61
  }
50
62
 
51
63
  return newVisualizationConfig
@@ -53,176 +65,255 @@ const addVisualization = (type, subType) => {
53
65
 
54
66
  const VisualizationsPanel = () => (
55
67
  <div className="visualizations-panel">
56
- <p style={{fontSize: '14px'}}>Click and drag an item onto the grid to add it to your dashboard.</p>
68
+ <p style={{ fontSize: '14px' }}>Click and drag an item onto the grid to add it to your dashboard.</p>
57
69
  <span className="subheading-3">Chart</span>
58
70
  <div className="drag-grid">
59
- <Widget addVisualization={() => addVisualization('chart', 'Bar')} type="Bar" />
60
- <Widget addVisualization={() => addVisualization('chart', 'Line')} type="Line" />
61
- <Widget addVisualization={() => addVisualization('chart', 'Pie')} type="Pie" />
71
+ <Widget addVisualization={() => addVisualization('chart', 'Bar')} type="Bar"/>
72
+ <Widget addVisualization={() => addVisualization('chart', 'Line')} type="Line"/>
73
+ <Widget addVisualization={() => addVisualization('chart', 'Pie')} type="Pie"/>
62
74
  </div>
63
75
  <span className="subheading-3">Map</span>
64
76
  <div className="drag-grid">
65
- <Widget addVisualization={() => addVisualization('map', 'us')} type="us" />
66
- <Widget addVisualization={() => addVisualization('map', 'world')} type="world" />
77
+ <Widget addVisualization={() => addVisualization('map', 'us')} type="us"/>
78
+ <Widget addVisualization={() => addVisualization('map', 'world')} type="world"/>
79
+ <Widget addVisualization={() => addVisualization('map', 'single-state')} type="single-state"/>
67
80
  </div>
68
81
  <span className="subheading-3">Misc.</span>
69
82
  <div className="drag-grid">
70
- <Widget addVisualization={() => addVisualization('data-bite', '')} type="data-bite" />
83
+ <Widget addVisualization={() => addVisualization('data-bite', '')} type="data-bite"/>
84
+ <Widget addVisualization={() => addVisualization('waffle-chart', '')} type="waffle-chart"/>
85
+ <Widget addVisualization={() => addVisualization('markup-include', '')} type="markup-include"/>
71
86
  </div>
72
87
  </div>
73
88
  )
74
89
 
75
90
  export default function CdcDashboard(
76
- { configUrl = '', config: configObj = undefined, isEditor = false, setConfig: setParentConfig }
91
+ { configUrl = '', config: configObj = undefined, isEditor = false, setConfig: setParentConfig, hostname }
77
92
  ) {
78
93
 
79
- const transform = new DataTransform();
94
+ const transform = new DataTransform()
80
95
 
81
- const [config, setConfig] = useState(configObj ?? {});
96
+ const [ config, setConfig ] = useState(configObj)
82
97
 
83
- const [data, setData] = useState([]);
98
+ const [ data, setData ] = useState([])
84
99
 
85
- const [filteredData, setFilteredData] = useState();
100
+ const [ filteredData, setFilteredData ] = useState()
86
101
 
87
- const [loading, setLoading] = useState(true);
102
+ const [ loading, setLoading ] = useState(true)
88
103
 
89
- const [preview, setPreview] = useState(false);
104
+ const [ preview, setPreview ] = useState(false)
90
105
 
91
- const [currentViewport, setCurrentViewport] = useState('lg')
106
+ const [ currentViewport, setCurrentViewport ] = useState('lg')
92
107
 
93
- const { title, description } = config.dashboard || config;
108
+ const [ coveLoadedHasRan, setCoveLoadedHasRan ] = useState(false)
94
109
 
95
- const loadConfig = async () => {
96
- let response = configObj || await (await fetch(configUrl)).json();
110
+ const [ container, setContainer ] = useState()
97
111
 
98
- // If data is included through a URL, fetch that and store
99
- let data = response.formattedData || response.data || {}
112
+ const { title, description } = config ? (config.dashboard || config) : {}
113
+
114
+ // Supports JSON or CSV
115
+ const fetchRemoteData = async (url) => {
116
+ try {
117
+ const urlObj = new URL(url)
118
+ const regex = /(?:\.([^.]+))?$/
100
119
 
101
- if(response.dataUrl) {
102
- const dataString = await fetch(response.dataUrl);
120
+ let data = []
103
121
 
104
- data = await dataString.json();
122
+ const ext = (regex.exec(urlObj.pathname)[1])
123
+ if ('csv' === ext) {
124
+ data = await fetch(url)
125
+ .then(response => response.text())
126
+ .then(responseText => {
127
+ const parsedCsv = Papa.parse(responseText, {
128
+ header: true,
129
+ dynamicTyping: true,
130
+ skipEmptyLines: true
131
+ })
132
+ return parsedCsv.data
133
+ })
134
+ }
135
+
136
+ if ('json' === ext) {
137
+ data = await fetch(url)
138
+ .then(response => response.json())
139
+ }
105
140
 
141
+ return data
142
+ } catch {
143
+ // If we can't parse it, still attempt to fetch it
106
144
  try {
107
- data = transform.autoStandardize(data);
108
- data = transform.developerStandardize(data, response.dataDescription);
109
- } catch(e) {
110
- //Data not able to be standardized, leave as is
145
+ let response = await (await fetch(configUrl)).json()
146
+ return response
147
+ } catch {
148
+ console.error(`Cannot parse URL: ${url}`)
149
+ }
150
+ }
151
+ }
152
+
153
+ const cacheBustingString = () => {
154
+ const round = 1000 * 60 * 15;
155
+ const date = new Date();
156
+ return new Date(date.getTime() - (date.getTime() % round)).toISOString();
157
+ };
158
+
159
+ const loadConfig = async (configObj) => {
160
+ // Set loading flag
161
+ if (!loading) setLoading(true)
162
+
163
+ let newState = configObj || await (await fetch(configUrl)).json()
164
+
165
+ // If a dataUrl property exists, always pull from that.
166
+ if (newState.dataUrl) {
167
+
168
+ if (newState.dataUrl[0] === '/') {
169
+ newState.dataUrl = 'https://' + hostname + newState.dataUrl
170
+ }
171
+
172
+ let newData = await fetchRemoteData(newState.dataUrl + `?v=${cacheBustingString()}`)
173
+
174
+ if (newData && newState.dataDescription) {
175
+ newData = transform.autoStandardize(newData)
176
+ newData = transform.developerStandardize(newData, newState.dataDescription)
177
+ }
178
+
179
+ if (newData) {
180
+ newState.data = newData
111
181
  }
112
182
  }
113
183
 
114
- setData(data);
184
+ // If data is included through a URL, fetch that and store
185
+ let data = newState.formattedData || newState.data || {}
186
+
187
+ setData(data)
115
188
 
116
- let newConfig = {...defaults, ...response}
189
+ let newConfig = { ...defaults, ...newState }
117
190
 
118
- updateConfig(newConfig, data);
191
+ updateConfig(newConfig, data)
119
192
 
120
- setLoading(false);
193
+ setLoading(false)
121
194
  }
122
195
 
123
196
  const filterData = (filters, data) => {
124
- let filteredData = [];
197
+ let filteredData = []
125
198
 
126
199
  data.forEach((row) => {
127
- let add = true;
200
+ let add = true
128
201
 
129
202
  filters.forEach((filter) => {
130
- if(row[filter.columnName] !== filter.active) {
131
- add = false;
203
+ if (row[filter.columnName] !== filter.active) {
204
+ add = false
132
205
  }
133
- });
206
+ })
134
207
 
135
- if(add) filteredData.push(row);
136
- });
208
+ if (add) filteredData.push(row)
209
+ })
137
210
 
138
- return filteredData;
211
+ return filteredData
139
212
  }
140
213
 
141
214
  // Gets filer values from dataset
142
215
  const generateValuesForFilter = (columnName, data = this.state.data) => {
143
- const values = [];
216
+ const values = []
144
217
 
145
- data.forEach( (row) => {
146
- const value = row[columnName]
147
- if(value && false === values.includes(value)) {
148
- values.push(value)
149
- }
150
- });
218
+ data.forEach((row) => {
219
+ const value = row[columnName]
220
+ if (value && false === values.includes(value)) {
221
+ values.push(value)
222
+ }
223
+ })
151
224
 
152
- return values;
225
+ return values
153
226
  }
154
227
 
155
- const updateConfig = (newConfig, dataOverride = null) => {
228
+ function isEmpty(obj) {
229
+ return Object.keys(obj).length === 0;
230
+ }
231
+
232
+ const updateConfig = (newConfig, dataOverride = null) => {
156
233
  // After data is grabbed, loop through and generate filter column values if there are any
157
234
  if (newConfig.dashboard.filters) {
158
- const filterList = [];
235
+ const filterList = []
159
236
 
160
237
  newConfig.dashboard.filters.forEach((filter) => {
161
- filterList.push(filter.columnName);
162
- });
238
+ filterList.push(filter.columnName)
239
+ })
163
240
 
164
241
  filterList.forEach((filter, index) => {
165
- const filterValues = generateValuesForFilter(filter, (dataOverride || data));
242
+ const filterValues = generateValuesForFilter(filter, (dataOverride || data))
243
+
244
+ if (newConfig.dashboard.filters[index].order === 'asc') {
245
+ filterValues.sort()
246
+ }
247
+ if (newConfig.dashboard.filters[index].order === 'desc') {
248
+ filterValues.sort().reverse()
249
+ }
166
250
 
167
- newConfig.dashboard.filters[index].values = filterValues;
251
+ newConfig.dashboard.filters[index].values = filterValues
168
252
 
169
- // Initial filter should be active
170
- newConfig.dashboard.filters[index].active = filterValues[0];
171
- });
253
+ // Initial filter should be active
254
+ newConfig.dashboard.filters[index].active = filterValues[0]
255
+ })
172
256
 
173
- setFilteredData(filterData(newConfig.dashboard.filters, (dataOverride || data)));
257
+ setFilteredData(filterData(newConfig.dashboard.filters, (dataOverride || data)))
174
258
  }
175
259
 
176
260
  //Enforce default values that need to be calculated at runtime
177
- newConfig.runtime = {};
261
+ newConfig.runtime = {}
178
262
 
179
- setConfig(newConfig);
180
- };
263
+ setConfig(newConfig)
264
+ }
181
265
 
182
266
  // Load data when component first mounts
183
267
  useEffect(() => {
184
- loadConfig();
185
- }, []);
268
+ loadConfig(config)
269
+ }, [])
186
270
 
187
271
  // Pass up to <CdcEditor /> if it exists when config state changes
188
272
  useEffect(() => {
189
- if(setParentConfig && isEditor) {
190
- setParentConfig(config);
273
+ if (setParentConfig && isEditor) {
274
+ setParentConfig(config)
191
275
  }
192
- }, [config])
276
+ }, [ config ])
277
+
278
+ useEffect(() => {
279
+ if (config && !coveLoadedHasRan && container) {
280
+ publish('cove_loaded', { config: config })
281
+ setCoveLoadedHasRan(true)
282
+ }
283
+ }, [config, container]);
193
284
 
194
285
  const updateChildConfig = (visualizationKey, newConfig) => {
195
- let updatedConfig = {...config}
286
+ let updatedConfig = { ...config }
196
287
 
197
- updatedConfig.visualizations[visualizationKey] = newConfig;
198
- setConfig(updatedConfig);
199
- };
288
+ updatedConfig.visualizations[visualizationKey] = newConfig
289
+ setConfig(updatedConfig)
290
+ }
200
291
 
201
292
  const Filters = () => {
202
293
  const changeFilterActive = (index, value) => {
203
- let dashboardConfig = {...config.dashboard};
294
+ let dashboardConfig = { ...config.dashboard }
204
295
 
205
- dashboardConfig.filters[index].active = value;
296
+ dashboardConfig.filters[index].active = value
206
297
 
207
- setConfig({...config, dashboard: dashboardConfig});
298
+ setConfig({ ...config, dashboard: dashboardConfig })
208
299
 
209
- setFilteredData(filterData(dashboardConfig.filters, data));
210
- };
300
+ setFilteredData(filterData(dashboardConfig.filters, data))
301
+ }
211
302
 
212
303
  const announceChange = (text) => {
213
304
 
214
- };
305
+ }
215
306
 
216
307
  return config.dashboard.filters.map((singleFilter, index) => {
217
- const values = [];
308
+ const values = []
218
309
 
219
310
  singleFilter.values.forEach((filterOption, index) => {
220
311
  values.push(<option
221
312
  key={index}
222
313
  value={filterOption}
223
314
  >{filterOption}
224
- </option>);
225
- });
315
+ </option>)
316
+ })
226
317
 
227
318
  return (
228
319
  <section className="dashboard-filters-section" key={index}>
@@ -233,50 +324,52 @@ export default function CdcDashboard(
233
324
  data-index="0"
234
325
  value={singleFilter.active}
235
326
  onChange={(val) => {
236
- changeFilterActive(index, val.target.value);
237
- announceChange(`Filter ${singleFilter.label} value has been changed to ${val.target.value}, please reference the data table to see updated values.`);
327
+ changeFilterActive(index, val.target.value)
328
+ announceChange(`Filter ${singleFilter.label} value has been changed to ${val.target.value}, please reference the data table to see updated values.`)
238
329
  }}
239
330
  >
240
331
  {values}
241
332
  </select>
242
333
  </section>
243
- );
244
- });;
334
+ )
335
+ })
336
+
245
337
  }
246
338
 
247
339
  const resizeObserver = new ResizeObserver(entries => {
248
340
  for (let entry of entries) {
249
- let newViewport = getViewport(entry.contentRect.width)
341
+ let newViewport = getViewport(entry.contentRect.width)
250
342
 
251
- setCurrentViewport(newViewport)
343
+ setCurrentViewport(newViewport)
252
344
  }
253
- });
345
+ })
254
346
 
255
347
  const outerContainerRef = useCallback(node => {
256
348
  if (node !== null) {
257
- resizeObserver.observe(node);
349
+ resizeObserver.observe(node)
258
350
  }
259
- },[]);
351
+ setContainer(node)
352
+ }, [])
260
353
 
261
354
  // Prevent render if loading
262
- if(loading) return <Loading />
355
+ if (loading) return <Loading/>
263
356
 
264
357
  let body = null
265
358
 
266
359
  // Editor mode
267
- if(isEditor && !preview) {
268
- let subVisualizationEditing = false;
360
+ if (isEditor && !preview) {
361
+ let subVisualizationEditing = false
269
362
 
270
363
  Object.keys(config.visualizations).forEach(visualizationKey => {
271
- let visualizationConfig = config.visualizations[visualizationKey];
364
+ let visualizationConfig = config.visualizations[visualizationKey]
272
365
 
273
- visualizationConfig.data = filteredData || data;
366
+ visualizationConfig.data = filteredData || data
274
367
 
275
- if(visualizationConfig.editing) {
276
- subVisualizationEditing = true;
368
+ if (visualizationConfig.editing) {
369
+ subVisualizationEditing = true
277
370
 
278
371
  const back = () => {
279
- const newConfig = {...config}
372
+ const newConfig = { ...config }
280
373
 
281
374
  delete newConfig.visualizations[visualizationKey].editing
282
375
 
@@ -285,28 +378,34 @@ export default function CdcDashboard(
285
378
 
286
379
  const updateConfig = (newConfig) => updateChildConfig(visualizationKey, newConfig)
287
380
 
288
- switch(visualizationConfig.type){
381
+ switch (visualizationConfig.type) {
289
382
  case 'chart':
290
- body = <><Header back={back} subEditor="Chart" /><CdcChart key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true} /></>;
291
- break;
292
- case 'map':
293
- body = <><Header back={back} subEditor="Map" /><CdcMap key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true} /></>;
294
- break;
383
+ body = <><Header back={back} subEditor="Chart"/><CdcChart key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
384
+ break
385
+ case 'map':
386
+ body = <><Header back={back} subEditor="Map"/><CdcMap key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
387
+ break
295
388
  case 'data-bite':
296
- visualizationConfig = {...visualizationConfig, newViz: true}
297
- body = <><Header back={back} subEditor="Data Bite" /><CdcDataBite key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true} /></>
298
- break;
389
+ visualizationConfig = { ...visualizationConfig, newViz: true }
390
+ body = <><Header back={back} subEditor="Data Bite"/><CdcDataBite key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
391
+ break
392
+ case 'waffle-chart':
393
+ body = <><Header back={back} subEditor="Waffle Chart"/><CdcWaffleChart key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
394
+ break
395
+ case 'markup-include':
396
+ body = <><Header back={back} subEditor="Markup Include"/><CdcMarkupInclude key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
397
+ break
299
398
  }
300
399
  }
301
- });
400
+ })
302
401
 
303
- if(!subVisualizationEditing){
402
+ if (!subVisualizationEditing) {
304
403
  body = (
305
404
  <DndProvider backend={HTML5Backend}>
306
- <Header preview={preview} setPreview={setPreview} />
405
+ <Header preview={preview} setPreview={setPreview}/>
307
406
  <div className="layout-container">
308
- <VisualizationsPanel />
309
- <Grid />
407
+ <VisualizationsPanel/>
408
+ <Grid/>
310
409
  </div>
311
410
  </DndProvider>
312
411
  )
@@ -314,42 +413,54 @@ export default function CdcDashboard(
314
413
  } else {
315
414
  body = (
316
415
  <>
317
- {isEditor && <Header preview={preview} setPreview={setPreview} />}
318
- {isEditor && <EditorPanel />}
416
+ {isEditor && <Header preview={preview} setPreview={setPreview}/>}
417
+ {isEditor && <EditorPanel/>}
319
418
  <div className="cdc-dashboard-inner-container">
320
419
  {/* Title */}
321
420
  {title && <div role="heading" className={`dashboard-title ${config.dashboard.theme ?? 'theme-blue'}`}>{title}</div>}
322
421
 
323
422
  {/* Filters */}
324
- {config.dashboard.filters && <Filters />}
423
+ {config.dashboard.filters && <Filters/>}
325
424
 
326
425
  {/* Visualizations */}
327
- {config.rows && config.rows.map(row => {
426
+ {config.rows && config.rows.map((row, index) => {
328
427
  // Empty check
329
- if(row.filter(col => col.widget).length === 0) return null
428
+ if (row.filter(col => col.widget).length === 0) return null
330
429
 
331
430
  return (
332
- <div className="dashboard-row">
333
- {row.map(col => {
334
- if(col.width) {
335
- if(!col.widget) return <div className={`dashboard-col dashboard-col-${col.width}`}></div>
336
-
337
- let visualizationConfig = config.visualizations[col.widget];
338
-
339
- visualizationConfig.data = filteredData || data;
340
-
341
- return <div className={`dashboard-col dashboard-col-${col.width}`}>
342
- {visualizationConfig.type === 'chart' && <CdcChart key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {updateChildConfig(col.widget, newConfig)}} isDashboard={true} />}
343
- {visualizationConfig.type === 'map' && <CdcMap key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {updateChildConfig(col.widget, newConfig)}} isDashboard={true} />}
344
- {visualizationConfig.type === 'data-bite' && <CdcDataBite key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {updateChildConfig(col.widget, newConfig)}} isDashboard={true} />}
431
+ <div className="dashboard-row" key={`row__${index}`}>
432
+ {row.map((col, index) => {
433
+ if (col.width) {
434
+ if (!col.widget) return <div className={`dashboard-col dashboard-col-${col.width}`}></div>
435
+
436
+ let visualizationConfig = config.visualizations[col.widget]
437
+
438
+ visualizationConfig.data = filteredData || data
439
+
440
+ return <div className={`dashboard-col dashboard-col-${col.width}`} key={`vis__${index}`}>
441
+ {visualizationConfig.type === 'chart' && <CdcChart key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
442
+ updateChildConfig(col.widget, newConfig)
443
+ }} isDashboard={true}/>}
444
+ {visualizationConfig.type === 'map' && <CdcMap key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
445
+ updateChildConfig(col.widget, newConfig)
446
+ }} isDashboard={true}/>}
447
+ {visualizationConfig.type === 'data-bite' && <CdcDataBite key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
448
+ updateChildConfig(col.widget, newConfig)
449
+ }} isDashboard={true}/>}
450
+ {visualizationConfig.type === 'waffle-chart' && <CdcWaffleChart key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
451
+ updateChildConfig(col.widget, newConfig)
452
+ }} isDashboard={true}/>}
453
+ {visualizationConfig.type === 'markup-include' && <CdcMarkupInclude key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
454
+ updateChildConfig(col.widget, newConfig)
455
+ }} isDashboard={true}/>}
345
456
  </div>
346
457
  }
347
458
  })}
348
- </div>);
459
+ </div>)
349
460
  })}
350
461
 
351
462
  {/* Data Table */}
352
- {config.table.show && <DataTable />}
463
+ {config.table.show && <DataTable/>}
353
464
 
354
465
  {/* Description */}
355
466
  {description && <div className="subtext">{parse(description)}</div>}
@@ -369,12 +480,12 @@ export default function CdcDashboard(
369
480
  setParentConfig,
370
481
  setPreview
371
482
  }
372
-
483
+
373
484
  return (
374
485
  <Context.Provider value={contextValues}>
375
486
  <div className={`cdc-open-viz-module type-dashboard ${currentViewport}`} ref={outerContainerRef}>
376
487
  {body}
377
488
  </div>
378
489
  </Context.Provider>
379
- );
490
+ )
380
491
  }