@cdc/dashboard 4.23.4 → 4.23.6
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.
- package/dist/cdcdashboard.js +84843 -83910
- package/examples/shared-filters.json +542 -0
- package/index.html +25 -26
- package/package.json +9 -9
- package/src/CdcDashboard.jsx +137 -29
- package/src/components/DataTable.tsx +4 -4
- package/src/components/Header.jsx +212 -86
- package/src/data/initial-state.js +2 -1
package/src/CdcDashboard.jsx
CHANGED
|
@@ -11,7 +11,6 @@ import { HTML5Backend } from 'react-dnd-html5-backend'
|
|
|
11
11
|
import parse from 'html-react-parser'
|
|
12
12
|
|
|
13
13
|
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
14
|
-
import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
|
|
15
14
|
import { GlobalContextProvider } from '@cdc/core/components/GlobalContext'
|
|
16
15
|
import ConfigContext from './ConfigContext'
|
|
17
16
|
|
|
@@ -32,10 +31,11 @@ import Header from './components/Header'
|
|
|
32
31
|
import defaults from './data/initial-state'
|
|
33
32
|
import Widget from './components/Widget'
|
|
34
33
|
import DataTable from './components/DataTable'
|
|
35
|
-
import
|
|
34
|
+
import MediaControls from '@cdc/core/components/MediaControls'
|
|
36
35
|
|
|
37
36
|
import './scss/main.scss'
|
|
38
37
|
import '@cdc/core/styles/v2/main.scss'
|
|
38
|
+
import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
|
|
39
39
|
|
|
40
40
|
/* eslint-disable react-hooks/exhaustive-deps */
|
|
41
41
|
|
|
@@ -76,7 +76,7 @@ const addVisualization = (type, subType) => {
|
|
|
76
76
|
return newVisualizationConfig
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
const VisualizationsPanel = () => (
|
|
79
|
+
const VisualizationsPanel = ({ loadConfig, config }) => (
|
|
80
80
|
<div className='visualizations-panel'>
|
|
81
81
|
<p style={{ fontSize: '14px' }}>Click and drag an item onto the grid to add it to your dashboard.</p>
|
|
82
82
|
<span className='subheading-3'>Chart</span>
|
|
@@ -98,6 +98,8 @@ const VisualizationsPanel = () => (
|
|
|
98
98
|
<Widget addVisualization={() => addVisualization('markup-include', '')} type='markup-include' />
|
|
99
99
|
<Widget addVisualization={() => addVisualization('filtered-text', '')} type='filtered-text' />
|
|
100
100
|
</div>
|
|
101
|
+
<span className='subheading-3'>Advanced</span>
|
|
102
|
+
<AdvancedEditor loadConfig={loadConfig} state={config} />
|
|
101
103
|
</div>
|
|
102
104
|
)
|
|
103
105
|
|
|
@@ -115,25 +117,106 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
115
117
|
|
|
116
118
|
const transform = new DataTransform()
|
|
117
119
|
|
|
120
|
+
const getFormattedData = (data, dataDescription) => {
|
|
121
|
+
if (data && dataDescription) {
|
|
122
|
+
try {
|
|
123
|
+
let formattedData = transform.autoStandardize(data)
|
|
124
|
+
formattedData = transform.developerStandardize(data, dataDescription)
|
|
125
|
+
return formattedData
|
|
126
|
+
} catch (e) {
|
|
127
|
+
return data
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return data
|
|
132
|
+
}
|
|
133
|
+
|
|
118
134
|
const processData = async config => {
|
|
119
135
|
let dataset = config.formattedData || config.data
|
|
120
136
|
|
|
121
137
|
if (config.dataUrl) {
|
|
122
|
-
dataset = await fetchRemoteData(`${config.dataUrl}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
dataset = transform.autoStandardize(data)
|
|
127
|
-
dataset = transform.developerStandardize(data, config.dataDescription)
|
|
128
|
-
} catch (e) {
|
|
129
|
-
//Data not able to be standardized, leave as is
|
|
130
|
-
}
|
|
131
|
-
}
|
|
138
|
+
dataset = await fetchRemoteData(`${config.dataUrl}`)
|
|
139
|
+
|
|
140
|
+
dataset = getFormattedData(dataset, config.dataDescription)
|
|
132
141
|
}
|
|
133
142
|
|
|
134
143
|
return dataset
|
|
135
144
|
}
|
|
136
145
|
|
|
146
|
+
const reloadURLData = async () => {
|
|
147
|
+
if (config.datasets) {
|
|
148
|
+
let newData = { ...data }
|
|
149
|
+
let newDatasets = { ...config.datasets }
|
|
150
|
+
let datasetsNeedsUpdate = false
|
|
151
|
+
let datasetKeys = Object.keys(config.datasets)
|
|
152
|
+
for (let i = 0; i < datasetKeys.length; i++) {
|
|
153
|
+
const dataset = config.datasets[datasetKeys[i]]
|
|
154
|
+
if (dataset.dataUrl && config.dashboard && config.dashboard.sharedFilters) {
|
|
155
|
+
const dataUrl = new URL(dataset.runtimeDataUrl || dataset.dataUrl)
|
|
156
|
+
let qsParams = Object.fromEntries(new URLSearchParams(dataUrl.search))
|
|
157
|
+
|
|
158
|
+
let isUpdateNeeded = false
|
|
159
|
+
|
|
160
|
+
config.dashboard.sharedFilters.forEach(filter => {
|
|
161
|
+
if (filter.type === 'url' && qsParams[filter.queryParameter] !== decodeURIComponent(filter.active)) {
|
|
162
|
+
qsParams[filter.queryParameter] = filter.active
|
|
163
|
+
isUpdateNeeded = true
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
if (!isUpdateNeeded) return
|
|
168
|
+
|
|
169
|
+
let dataUrlFinal = `${dataUrl.origin}${dataUrl.pathname}${Object.keys(qsParams)
|
|
170
|
+
.map((param, i) => {
|
|
171
|
+
let qs = i === 0 ? '?' : '&'
|
|
172
|
+
qs += param + '='
|
|
173
|
+
qs += qsParams[param]
|
|
174
|
+
return qs
|
|
175
|
+
})
|
|
176
|
+
.join('')}`
|
|
177
|
+
|
|
178
|
+
let newDataset = await fetchRemoteData(`${dataUrlFinal}`)
|
|
179
|
+
|
|
180
|
+
if (newDataset && dataset.dataDescription) {
|
|
181
|
+
try {
|
|
182
|
+
newDataset = transform.autoStandardize(newDataset)
|
|
183
|
+
newDataset = transform.developerStandardize(newDataset, dataset.dataDescription)
|
|
184
|
+
} catch (e) {
|
|
185
|
+
//Data not able to be standardized, leave as is
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
newDatasets[datasetKeys[i]].runtimeDataUrl = dataUrlFinal
|
|
190
|
+
newData[datasetKeys[i]] = newDataset
|
|
191
|
+
datasetsNeedsUpdate = true
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (datasetsNeedsUpdate) {
|
|
196
|
+
setData(newData)
|
|
197
|
+
|
|
198
|
+
let newFilteredData = {}
|
|
199
|
+
let newConfig = { ...config }
|
|
200
|
+
Object.keys(config.visualizations).forEach(key => {
|
|
201
|
+
let dataKey = config.visualizations[key].dataKey
|
|
202
|
+
|
|
203
|
+
let applicableFilters = config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(key) !== -1)
|
|
204
|
+
if (applicableFilters.length > 0) {
|
|
205
|
+
newFilteredData[key] = filterData(applicableFilters, newData[dataKey])
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (newData[dataKey]) {
|
|
209
|
+
newConfig.visualizations[key].formattedData = newData[dataKey]
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
setFilteredData(newFilteredData)
|
|
213
|
+
|
|
214
|
+
newConfig.datasets = newDatasets
|
|
215
|
+
setConfig(newConfig)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
137
220
|
const loadConfig = async () => {
|
|
138
221
|
let response = configObj || (await (await fetch(configUrl)).json())
|
|
139
222
|
let newConfig = { ...defaults, ...response }
|
|
@@ -188,7 +271,6 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
188
271
|
}
|
|
189
272
|
|
|
190
273
|
setData(datasets)
|
|
191
|
-
|
|
192
274
|
updateConfig(newConfig, datasets)
|
|
193
275
|
setLoading(false)
|
|
194
276
|
}
|
|
@@ -201,7 +283,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
201
283
|
let add = true
|
|
202
284
|
|
|
203
285
|
filters.forEach(filter => {
|
|
204
|
-
if (row[filter.columnName]
|
|
286
|
+
if (filter.type !== 'url' && row[filter.columnName] != filter.active) {
|
|
205
287
|
add = false
|
|
206
288
|
}
|
|
207
289
|
})
|
|
@@ -230,7 +312,9 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
230
312
|
if (applicableFilters.length > 0) {
|
|
231
313
|
const visualization = newConfig.visualizations[visualizationKey]
|
|
232
314
|
|
|
233
|
-
|
|
315
|
+
const formattedData = visualization.dataDescription ? getFormattedData(data[visualization.dataKey] || visualization.data, visualization.dataDescription) : undefined
|
|
316
|
+
|
|
317
|
+
newFilteredData[visualizationKey] = filterData(applicableFilters, formattedData || data[visualization.dataKey])
|
|
234
318
|
}
|
|
235
319
|
})
|
|
236
320
|
|
|
@@ -291,7 +375,11 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
291
375
|
let applicableFilters = newConfig.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) !== -1)
|
|
292
376
|
|
|
293
377
|
if (applicableFilters.length > 0) {
|
|
294
|
-
|
|
378
|
+
const visualization = newConfig.visualizations[visualizationKey]
|
|
379
|
+
|
|
380
|
+
const formattedData = getFormattedData(newConfig.datasets[visualization.dataKey] && newConfig.datasets[visualization.dataKey].data ? newConfig.datasets[visualization.dataKey].data : visualization.data, visualization.dataDescription)
|
|
381
|
+
|
|
382
|
+
newFilteredData[visualizationKey] = filterData(applicableFilters, formattedData || visualization.data || (dataOverride || data)[visualization.dataKey])
|
|
295
383
|
}
|
|
296
384
|
})
|
|
297
385
|
}
|
|
@@ -315,6 +403,10 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
315
403
|
}
|
|
316
404
|
}, [config])
|
|
317
405
|
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
reloadURLData()
|
|
408
|
+
}, [JSON.stringify(config.dashboard ? config.dashboard.sharedFilters : undefined)])
|
|
409
|
+
|
|
318
410
|
const updateChildConfig = (visualizationKey, newConfig) => {
|
|
319
411
|
let updatedConfig = { ...config }
|
|
320
412
|
|
|
@@ -336,24 +428,39 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
336
428
|
Object.keys(config.visualizations).forEach(key => {
|
|
337
429
|
let applicableFilters = dashboardConfig.sharedFilters.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(key) !== -1)
|
|
338
430
|
if (applicableFilters.length > 0) {
|
|
339
|
-
|
|
431
|
+
const visualization = config.visualizations[key]
|
|
432
|
+
|
|
433
|
+
const formattedData = visualization.dataDescription ? getFormattedData(data[config.visualizations[key].dataKey] || visualization.data, visualization.dataDescription) : undefined
|
|
434
|
+
|
|
435
|
+
newFilteredData[key] = filterData(applicableFilters, formattedData || data[config.visualizations[key].dataKey])
|
|
340
436
|
}
|
|
341
437
|
})
|
|
342
438
|
|
|
343
439
|
setFilteredData(newFilteredData)
|
|
440
|
+
if (dashboardConfig.sharedFilters[index].active === dashboardConfig.sharedFilters[index].resetLabel) {
|
|
441
|
+
setFilteredData(data)
|
|
442
|
+
}
|
|
344
443
|
}
|
|
345
444
|
|
|
346
445
|
const announceChange = text => {}
|
|
347
446
|
|
|
348
447
|
return config.dashboard.sharedFilters.map((singleFilter, index) => {
|
|
349
|
-
if (!singleFilter.showDropdown) return <></>
|
|
448
|
+
if (singleFilter.type !== 'url' && !singleFilter.showDropdown) return <></>
|
|
350
449
|
|
|
351
450
|
const values = []
|
|
352
451
|
|
|
452
|
+
if (singleFilter.resetLabel) {
|
|
453
|
+
values.push(
|
|
454
|
+
<option key={`${singleFilter.resetLabel}-option-${index}`} value={singleFilter.resetLabel}>
|
|
455
|
+
{singleFilter.resetLabel}
|
|
456
|
+
</option>
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
|
|
353
460
|
singleFilter.values.forEach((filterOption, index) => {
|
|
354
461
|
values.push(
|
|
355
462
|
<option key={`${singleFilter.key}-option-${index}`} value={filterOption}>
|
|
356
|
-
{filterOption}
|
|
463
|
+
{singleFilter.labels ? singleFilter.labels[filterOption] || filterOption : filterOption}
|
|
357
464
|
</option>
|
|
358
465
|
)
|
|
359
466
|
})
|
|
@@ -444,7 +551,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
444
551
|
body = (
|
|
445
552
|
<>
|
|
446
553
|
<Header tabSelected={tabSelected} setTabSelected={setTabSelected} back={back} subEditor='Chart' />
|
|
447
|
-
<CdcChart key={visualizationKey} config={visualizationConfig} isEditor={true} isDebug={isDebug} setConfig={updateConfig} setSharedFilter={setsSharedFilter ? setSharedFilter : undefined} isDashboard={true} />
|
|
554
|
+
<CdcChart key={visualizationKey} config={visualizationConfig} isEditor={true} isDebug={isDebug} setConfig={updateConfig} setSharedFilter={setsSharedFilter ? setSharedFilter : undefined} setSharedFilterValue={setSharedFilterValue} dashboardConfig={config} isDashboard={true} />
|
|
448
555
|
</>
|
|
449
556
|
)
|
|
450
557
|
break
|
|
@@ -501,7 +608,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
501
608
|
<DndProvider backend={HTML5Backend}>
|
|
502
609
|
<Header tabSelected={tabSelected} setTabSelected={setTabSelected} preview={preview} setPreview={setPreview} />
|
|
503
610
|
<div className='layout-container'>
|
|
504
|
-
<VisualizationsPanel />
|
|
611
|
+
<VisualizationsPanel loadConfig={loadConfig} config={config} />
|
|
505
612
|
<Grid />
|
|
506
613
|
</div>
|
|
507
614
|
</DndProvider>
|
|
@@ -572,13 +679,14 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
572
679
|
<CdcChart
|
|
573
680
|
key={col.widget}
|
|
574
681
|
config={visualizationConfig}
|
|
682
|
+
dashboardConfig={config}
|
|
575
683
|
isEditor={false}
|
|
576
684
|
setConfig={newConfig => {
|
|
577
685
|
updateChildConfig(col.widget, newConfig)
|
|
578
686
|
}}
|
|
579
687
|
setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
|
|
580
688
|
isDashboard={true}
|
|
581
|
-
link={config.table && config.table.show && config.datasets && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
|
|
689
|
+
link={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
|
|
582
690
|
/>
|
|
583
691
|
)}
|
|
584
692
|
{visualizationConfig.type === 'map' && (
|
|
@@ -592,7 +700,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
592
700
|
setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
|
|
593
701
|
setSharedFilterValue={setSharedFilterValue}
|
|
594
702
|
isDashboard={true}
|
|
595
|
-
link={config.table && config.table.show && config.datasets && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
|
|
703
|
+
link={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
|
|
596
704
|
/>
|
|
597
705
|
)}
|
|
598
706
|
{visualizationConfig.type === 'data-bite' && (
|
|
@@ -615,7 +723,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
615
723
|
updateChildConfig(col.widget, newConfig)
|
|
616
724
|
}}
|
|
617
725
|
isDashboard={true}
|
|
618
|
-
link={config.table && config.table.show && config.datasets && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
|
|
726
|
+
link={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
|
|
619
727
|
/>
|
|
620
728
|
)}
|
|
621
729
|
{visualizationConfig.type === 'markup-include' && (
|
|
@@ -652,8 +760,8 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
652
760
|
|
|
653
761
|
{/* Image or PDF Inserts */}
|
|
654
762
|
<section className='download-buttons'>
|
|
655
|
-
{config.table.downloadImageButton && <
|
|
656
|
-
{config.table.downloadPdfButton && <
|
|
763
|
+
{config.table.downloadImageButton && <MediaControls.Button title='Download Dashboard as Image' type='image' state={config} text='Download Dashboard Image' elementToCapture={imageId} />}
|
|
764
|
+
{config.table.downloadPdfButton && <MediaControls.Button title='Download Dashboard as PDF' type='pdf' state={config} text='Download Dashboard PDF' elementToCapture={imageId} />}
|
|
657
765
|
</section>
|
|
658
766
|
|
|
659
767
|
{/* Data Table */}
|
|
@@ -678,7 +786,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
678
786
|
config.dashboard.sharedFilters.forEach(sharedFilter => {
|
|
679
787
|
let allMatch = true
|
|
680
788
|
vizKeysUsingDataset.forEach(visualizationKey => {
|
|
681
|
-
if (sharedFilter.usedBy.indexOf(visualizationKey) === -1) {
|
|
789
|
+
if (sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) === -1) {
|
|
682
790
|
allMatch = false
|
|
683
791
|
}
|
|
684
792
|
})
|
|
@@ -697,7 +805,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
|
|
|
697
805
|
|
|
698
806
|
return (
|
|
699
807
|
<div className='multi-table-container' id={`data-table-${datasetKey}`} key={`data-table-${datasetKey}`}>
|
|
700
|
-
<DataTable data={filteredTableData || config.datasets[datasetKey].data} downloadData={config.datasets[datasetKey].data} dataFileSourceType={dataFileSourceType} datasetKey={datasetKey} config={config} imageRef={imageId}></DataTable>
|
|
808
|
+
<DataTable data={filteredTableData || config.datasets[datasetKey].data || []} downloadData={config.datasets[datasetKey].data} dataFileSourceType={dataFileSourceType} datasetKey={datasetKey} config={config} imageRef={imageId}></DataTable>
|
|
701
809
|
</div>
|
|
702
810
|
)
|
|
703
811
|
})}
|
|
@@ -2,7 +2,7 @@ import React, { useEffect, useState, useMemo, memo } from 'react'
|
|
|
2
2
|
import { useTable, useSortBy, useResizeColumns, useBlockLayout } from 'react-table'
|
|
3
3
|
import Papa from 'papaparse'
|
|
4
4
|
import { Base64 } from 'js-base64'
|
|
5
|
-
import
|
|
5
|
+
import MediaControls from '@cdc/core/components/MediaControls'
|
|
6
6
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
7
7
|
|
|
8
8
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
@@ -94,7 +94,7 @@ export default function DataTable(props) {
|
|
|
94
94
|
|
|
95
95
|
return (
|
|
96
96
|
<ErrorBoundary component='DataTable'>
|
|
97
|
-
<
|
|
97
|
+
<MediaControls.Section classes={['download-links']}>
|
|
98
98
|
{config.table.showDownloadUrl && dataFileSourceType === 'url' && (
|
|
99
99
|
<a className='dashboard-download-link' href={config.datasets[datasetKey].dataFileName} title='Link to View Dataset' target='_blank'>
|
|
100
100
|
{' '}
|
|
@@ -103,7 +103,7 @@ export default function DataTable(props) {
|
|
|
103
103
|
</a>
|
|
104
104
|
)}
|
|
105
105
|
{config.table.download && <DownloadButton data={data} />}
|
|
106
|
-
</
|
|
106
|
+
</MediaControls.Section>
|
|
107
107
|
{config.table.show && (
|
|
108
108
|
<section className={`data-table-container`} aria-label={accessibilityLabel}>
|
|
109
109
|
<div
|
|
@@ -119,7 +119,7 @@ export default function DataTable(props) {
|
|
|
119
119
|
}
|
|
120
120
|
}}
|
|
121
121
|
>
|
|
122
|
-
<Icon display={tableExpanded ? 'minus' : 'plus'} base/>
|
|
122
|
+
<Icon display={tableExpanded ? 'minus' : 'plus'} base />
|
|
123
123
|
{config.table.label}
|
|
124
124
|
{datasetKey ? `: ${datasetKey}` : ''}
|
|
125
125
|
</div>
|