@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
|
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useContext } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import ConfigContext from '../ConfigContext'
|
|
4
4
|
|
|
5
|
+
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
5
6
|
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
6
7
|
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
7
8
|
import Modal from '@cdc/core/components/ui/Modal'
|
|
@@ -13,6 +14,8 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
13
14
|
|
|
14
15
|
const [columns, setColumns] = useState([])
|
|
15
16
|
|
|
17
|
+
const transform = new DataTransform()
|
|
18
|
+
|
|
16
19
|
const changeConfigValue = (parentObj, key, value) => {
|
|
17
20
|
let newConfig = { ...config }
|
|
18
21
|
if (!newConfig[parentObj]) newConfig[parentObj] = {}
|
|
@@ -45,6 +48,13 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
45
48
|
|
|
46
49
|
dashboardConfig.sharedFilters.splice(index, 1)
|
|
47
50
|
|
|
51
|
+
// Ensures URL filters refresh after filter removal
|
|
52
|
+
if (dashboardConfig.datasets) {
|
|
53
|
+
Object.keys(dashboardConfig.datasets).forEach(datasetKey => {
|
|
54
|
+
delete dashboardConfig.datasets[datasetKey].runtimeDataUrl
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
48
58
|
updateConfig({ ...config, dashboard: dashboardConfig })
|
|
49
59
|
|
|
50
60
|
overlay?.actions.toggleOverlay()
|
|
@@ -90,7 +100,7 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
90
100
|
if (config.datasets[dataKeys[i]].dataDescription) {
|
|
91
101
|
try {
|
|
92
102
|
config.datasets[dataKeys[i]].data = transform.autoStandardize(config.datasets[dataKeys[i]].data)
|
|
93
|
-
config.datasets[dataKeys[i]].data = transform.developerStandardize(config.datasets[dataKeys[i]].data, config.datasets[
|
|
103
|
+
config.datasets[dataKeys[i]].data = transform.developerStandardize(config.datasets[dataKeys[i]].data, config.datasets[dataKeys[i]].dataDescription)
|
|
94
104
|
} catch (e) {
|
|
95
105
|
//Data not able to be standardized, leave as is
|
|
96
106
|
}
|
|
@@ -98,7 +108,7 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
98
108
|
}
|
|
99
109
|
|
|
100
110
|
if (config.datasets[dataKeys[i]].data) {
|
|
101
|
-
config.datasets[dataKeys[i]].data.
|
|
111
|
+
config.datasets[dataKeys[i]].data.forEach(row => {
|
|
102
112
|
Object.keys(row).forEach(columnName => (columns[columnName] = true))
|
|
103
113
|
})
|
|
104
114
|
}
|
|
@@ -128,6 +138,14 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
128
138
|
overlay?.actions.openOverlay(filterModal(newFilter, index))
|
|
129
139
|
}
|
|
130
140
|
|
|
141
|
+
const updateFilterPropByFunction = (index, func) => {
|
|
142
|
+
let newFilter = { ...filter }
|
|
143
|
+
|
|
144
|
+
newFilter = func(newFilter)
|
|
145
|
+
|
|
146
|
+
overlay?.actions.openOverlay(filterModal(newFilter, index))
|
|
147
|
+
}
|
|
148
|
+
|
|
131
149
|
const addFilterUsedBy = (filter, index, value) => {
|
|
132
150
|
if (!filter.usedBy) filter.usedBy = []
|
|
133
151
|
filter.usedBy.push(value)
|
|
@@ -142,6 +160,46 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
142
160
|
}
|
|
143
161
|
}
|
|
144
162
|
|
|
163
|
+
const updateLabel = (e, value) => {
|
|
164
|
+
let newLabels = filter.labels || {}
|
|
165
|
+
|
|
166
|
+
newLabels[value] = e.target.value
|
|
167
|
+
|
|
168
|
+
updateFilterProp('labels', index, newLabels)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const removeValue = valueIndex => {
|
|
172
|
+
let newLabels = filter.labels || {}
|
|
173
|
+
let newValues = filter.values || []
|
|
174
|
+
|
|
175
|
+
delete newLabels[filter.values[valueIndex]]
|
|
176
|
+
newValues.splice(valueIndex, 1)
|
|
177
|
+
|
|
178
|
+
updateFilterPropByFunction(index, newFilter => {
|
|
179
|
+
newFilter.labels = newLabels
|
|
180
|
+
newFilter.orderedValue = newValues
|
|
181
|
+
return newFilter
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const addNewValue = e => {
|
|
186
|
+
e.preventDefault()
|
|
187
|
+
if (!filter.values || filter.values.indexOf(e.target[0].value) === -1) {
|
|
188
|
+
let newValues = filter.values || []
|
|
189
|
+
newValues.push(e.target[0].value)
|
|
190
|
+
|
|
191
|
+
updateFilterPropByFunction(index, newFilter => {
|
|
192
|
+
newFilter.values = newValues
|
|
193
|
+
if (!newFilter.active) {
|
|
194
|
+
newFilter.active = e.target[0].value
|
|
195
|
+
}
|
|
196
|
+
return newFilter
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
e.target[0].value = ''
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
145
203
|
return (
|
|
146
204
|
<Modal>
|
|
147
205
|
<Modal.Content>
|
|
@@ -157,81 +215,149 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
157
215
|
Remove Filter
|
|
158
216
|
</button>
|
|
159
217
|
<label>
|
|
160
|
-
<span className='edit-label column-heading'>Filter
|
|
161
|
-
<select
|
|
162
|
-
value=
|
|
163
|
-
|
|
164
|
-
updateFilterProp('columnName', index, e.target.value)
|
|
165
|
-
}}
|
|
166
|
-
>
|
|
167
|
-
<option value=''>- Select Option -</option>
|
|
168
|
-
{columns.map(dataKey => (
|
|
169
|
-
<option value={dataKey} key={`filter-column-select-item-${dataKey}`}>
|
|
170
|
-
{dataKey}
|
|
171
|
-
</option>
|
|
172
|
-
))}
|
|
173
|
-
</select>
|
|
174
|
-
</label>
|
|
175
|
-
<label>
|
|
176
|
-
<span className='edit-label column-heading'>Label: </span>
|
|
177
|
-
<input
|
|
178
|
-
type='text'
|
|
179
|
-
value={filter.key}
|
|
180
|
-
onChange={e => {
|
|
181
|
-
updateFilterProp('key', index, e.target.value)
|
|
182
|
-
}}
|
|
183
|
-
/>
|
|
184
|
-
</label>
|
|
185
|
-
<label>
|
|
186
|
-
<span className='edit-label column-heading'>Show Dropdown</span>
|
|
187
|
-
<input
|
|
188
|
-
type='checkbox'
|
|
189
|
-
defaultChecked={filter.showDropdown === true}
|
|
190
|
-
onChange={e => {
|
|
191
|
-
updateFilterProp('showDropdown', index, !filter.showDropdown)
|
|
192
|
-
}}
|
|
193
|
-
/>
|
|
194
|
-
</label>
|
|
195
|
-
<label>
|
|
196
|
-
<span className='edit-label column-heading'>Set By: </span>
|
|
197
|
-
<select value={filter.setBy} onChange={e => updateFilterProp('setBy', index, e.target.value)}>
|
|
198
|
-
<option value=''>- Select Option -</option>
|
|
199
|
-
{Object.keys(config.visualizations).map(vizKey => (
|
|
200
|
-
<option value={vizKey} key={`set-by-select-item-${vizKey}`}>
|
|
201
|
-
{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}
|
|
202
|
-
</option>
|
|
203
|
-
))}
|
|
204
|
-
</select>
|
|
205
|
-
</label>
|
|
206
|
-
<label>
|
|
207
|
-
<span className='edit-label column-heading'>Used By:</span>
|
|
208
|
-
<ul>
|
|
209
|
-
{filter.usedBy &&
|
|
210
|
-
filter.usedBy.map(vizKey => (
|
|
211
|
-
<li key={`used-by-list-item-${vizKey}`}>
|
|
212
|
-
<span>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}</span>{' '}
|
|
213
|
-
<button
|
|
214
|
-
onClick={e => {
|
|
215
|
-
e.preventDefault()
|
|
216
|
-
removeFilterUsedBy(filter, index, vizKey)
|
|
217
|
-
}}
|
|
218
|
-
>
|
|
219
|
-
X
|
|
220
|
-
</button>
|
|
221
|
-
</li>
|
|
222
|
-
))}
|
|
223
|
-
</ul>
|
|
224
|
-
<select onChange={e => addFilterUsedBy(filter, index, e.target.value)}>
|
|
225
|
-
<option value=''>- Select Option -</option>
|
|
226
|
-
{Object.keys(config.visualizations)
|
|
227
|
-
.filter(vizKey => filter.setBy !== vizKey && (!filter.usedBy || filter.usedBy.indexOf(vizKey) === -1) && !config.visualizations[vizKey].usesSharedFilter)
|
|
228
|
-
.map(vizKey => (
|
|
229
|
-
<option value={vizKey} key={`used-by-select-item-${vizKey}`}>
|
|
230
|
-
{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}
|
|
231
|
-
</option>
|
|
232
|
-
))}
|
|
218
|
+
<span className='edit-label column-heading'>Filter Type:</span>
|
|
219
|
+
<select defaultValue={filter.type || 'data'} onChange={e => updateFilterProp('type', index, e.target.value)}>
|
|
220
|
+
<option value='url'>URL</option>
|
|
221
|
+
<option value='data'>Data</option>
|
|
233
222
|
</select>
|
|
234
223
|
</label>
|
|
224
|
+
{filter.type === 'url' && (
|
|
225
|
+
<>
|
|
226
|
+
<label>
|
|
227
|
+
<span className='edit-label column-heading'>Label: </span>
|
|
228
|
+
<input
|
|
229
|
+
type='text'
|
|
230
|
+
value={filter.key}
|
|
231
|
+
onChange={e => {
|
|
232
|
+
updateFilterProp('key', index, e.target.value)
|
|
233
|
+
}}
|
|
234
|
+
/>
|
|
235
|
+
</label>
|
|
236
|
+
<label>
|
|
237
|
+
<span className='edit-label column-heading'>URL to Filter: </span>
|
|
238
|
+
<select defaultValue={filter.datasetKey || ''} onChange={e => updateFilterProp('datasetKey', index, e.target.value)}>
|
|
239
|
+
<option value=''>- Select Option -</option>
|
|
240
|
+
{Object.keys(config.datasets).map(datasetKey => {
|
|
241
|
+
if (config.datasets[datasetKey].dataUrl) {
|
|
242
|
+
return (
|
|
243
|
+
<option key={datasetKey} value={datasetKey}>
|
|
244
|
+
{config.datasets[datasetKey].dataUrl.substring(0, 50)}
|
|
245
|
+
</option>
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
return <React.Fragment key={datasetKey}></React.Fragment>
|
|
249
|
+
})}
|
|
250
|
+
</select>
|
|
251
|
+
</label>
|
|
252
|
+
<label>
|
|
253
|
+
<span className='edit-label column-heading'>Query string parameter</span> <input type='text' defaultValue={filter.queryParameter} onChange={e => updateFilterProp('queryParameter', index, e.target.value)} />
|
|
254
|
+
</label>
|
|
255
|
+
<span className='edit-label column-heading'>Values</span>{' '}
|
|
256
|
+
<ul className='value-list'>
|
|
257
|
+
{filter.values &&
|
|
258
|
+
filter.values.map((value, valueIndex) => (
|
|
259
|
+
<li>
|
|
260
|
+
{value}
|
|
261
|
+
<input type='text' value={filter.labels ? filter.labels[value] : undefined} onChange={e => updateLabel(e, value)} />
|
|
262
|
+
<button onClick={() => removeValue(valueIndex)}>X</button>
|
|
263
|
+
</li>
|
|
264
|
+
))}
|
|
265
|
+
</ul>
|
|
266
|
+
<form onSubmit={addNewValue}>
|
|
267
|
+
<input type='text' /> <button type='submit'>Add New Value</button>
|
|
268
|
+
</form>
|
|
269
|
+
</>
|
|
270
|
+
)}
|
|
271
|
+
{filter.type !== 'url' && (
|
|
272
|
+
<>
|
|
273
|
+
<label>
|
|
274
|
+
<span className='edit-label column-heading'>Filter: </span>
|
|
275
|
+
<select
|
|
276
|
+
value={filter.columnName}
|
|
277
|
+
onChange={e => {
|
|
278
|
+
updateFilterProp('columnName', index, e.target.value)
|
|
279
|
+
}}
|
|
280
|
+
>
|
|
281
|
+
<option value=''>- Select Option -</option>
|
|
282
|
+
{columns.map(dataKey => (
|
|
283
|
+
<option value={dataKey} key={`filter-column-select-item-${dataKey}`}>
|
|
284
|
+
{dataKey}
|
|
285
|
+
</option>
|
|
286
|
+
))}
|
|
287
|
+
</select>
|
|
288
|
+
</label>
|
|
289
|
+
<label>
|
|
290
|
+
<span className='edit-label column-heading'>Label: </span>
|
|
291
|
+
<input
|
|
292
|
+
type='text'
|
|
293
|
+
value={filter.key}
|
|
294
|
+
onChange={e => {
|
|
295
|
+
updateFilterProp('key', index, e.target.value)
|
|
296
|
+
}}
|
|
297
|
+
/>
|
|
298
|
+
</label>
|
|
299
|
+
<label>
|
|
300
|
+
<span className='edit-label column-heading'>Show Dropdown</span>
|
|
301
|
+
<input
|
|
302
|
+
type='checkbox'
|
|
303
|
+
defaultChecked={filter.showDropdown === true}
|
|
304
|
+
onChange={e => {
|
|
305
|
+
updateFilterProp('showDropdown', index, !filter.showDropdown)
|
|
306
|
+
}}
|
|
307
|
+
/>
|
|
308
|
+
</label>
|
|
309
|
+
<label>
|
|
310
|
+
<span className='edit-label column-heading'>Set By: </span>
|
|
311
|
+
<select value={filter.setBy} onChange={e => updateFilterProp('setBy', index, e.target.value)}>
|
|
312
|
+
<option value=''>- Select Option -</option>
|
|
313
|
+
{Object.keys(config.visualizations).map(vizKey => (
|
|
314
|
+
<option value={vizKey} key={`set-by-select-item-${vizKey}`}>
|
|
315
|
+
{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}
|
|
316
|
+
</option>
|
|
317
|
+
))}
|
|
318
|
+
</select>
|
|
319
|
+
</label>
|
|
320
|
+
<label>
|
|
321
|
+
<span className='edit-label column-heading'>Used By:</span>
|
|
322
|
+
<ul>
|
|
323
|
+
{filter.usedBy &&
|
|
324
|
+
filter.usedBy.map(vizKey => (
|
|
325
|
+
<li key={`used-by-list-item-${vizKey}`}>
|
|
326
|
+
<span>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}</span>{' '}
|
|
327
|
+
<button
|
|
328
|
+
onClick={e => {
|
|
329
|
+
e.preventDefault()
|
|
330
|
+
removeFilterUsedBy(filter, index, vizKey)
|
|
331
|
+
}}
|
|
332
|
+
>
|
|
333
|
+
X
|
|
334
|
+
</button>
|
|
335
|
+
</li>
|
|
336
|
+
))}
|
|
337
|
+
</ul>
|
|
338
|
+
<select onChange={e => addFilterUsedBy(filter, index, e.target.value)}>
|
|
339
|
+
<option value=''>- Select Option -</option>
|
|
340
|
+
{Object.keys(config.visualizations)
|
|
341
|
+
.filter(vizKey => filter.setBy !== vizKey && (!filter.usedBy || filter.usedBy.indexOf(vizKey) === -1) && !config.visualizations[vizKey].usesSharedFilter)
|
|
342
|
+
.map(vizKey => (
|
|
343
|
+
<option value={vizKey} key={`used-by-select-item-${vizKey}`}>
|
|
344
|
+
{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}
|
|
345
|
+
</option>
|
|
346
|
+
))}
|
|
347
|
+
</select>
|
|
348
|
+
</label>
|
|
349
|
+
<label>
|
|
350
|
+
<span className='edit-label column-heading'>Reset Label: </span>
|
|
351
|
+
<input
|
|
352
|
+
type='text'
|
|
353
|
+
value={filter.resetLabel ? filter.resetLabel : ''}
|
|
354
|
+
onChange={e => {
|
|
355
|
+
updateFilterProp('resetLabel', index, e.target.value)
|
|
356
|
+
}}
|
|
357
|
+
/>
|
|
358
|
+
</label>
|
|
359
|
+
</>
|
|
360
|
+
)}
|
|
235
361
|
</fieldset>
|
|
236
362
|
<button type='button' className='btn btn-primary' style={{ display: 'inline-block', 'margin-right': '1em' }} onClick={overlay?.actions.toggleOverlay}>
|
|
237
363
|
Cancel
|
|
@@ -258,7 +384,7 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
258
384
|
</div>
|
|
259
385
|
)}
|
|
260
386
|
{!subEditor && (
|
|
261
|
-
<div className=
|
|
387
|
+
<div className='toggle-bar__wrapper'>
|
|
262
388
|
<ul className='toggle-bar'>
|
|
263
389
|
<li
|
|
264
390
|
className={tabSelected === 0 ? 'active' : 'inactive'}
|
|
@@ -317,17 +443,18 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
317
443
|
)}
|
|
318
444
|
{tabSelected === 2 && (
|
|
319
445
|
<>
|
|
320
|
-
|
|
321
|
-
<div className="wrap">
|
|
446
|
+
<div className='wrap'>
|
|
322
447
|
<label>
|
|
323
448
|
<input type='checkbox' defaultChecked={config.table.show} onChange={e => changeConfigValue('table', 'show', e.target.checked)} />
|
|
324
449
|
Show Data Table(s)
|
|
325
|
-
</label
|
|
450
|
+
</label>
|
|
451
|
+
<br />
|
|
326
452
|
|
|
327
453
|
<label>
|
|
328
454
|
<input type='checkbox' defaultChecked={config.table.expanded} onChange={e => changeConfigValue('table', 'expanded', e.target.checked)} />
|
|
329
455
|
Expanded by Default
|
|
330
|
-
</label
|
|
456
|
+
</label>
|
|
457
|
+
<br />
|
|
331
458
|
</div>
|
|
332
459
|
|
|
333
460
|
{/* <div className="wrap">
|
|
@@ -341,7 +468,7 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
341
468
|
</label>
|
|
342
469
|
</div> */}
|
|
343
470
|
|
|
344
|
-
<div className=
|
|
471
|
+
<div className='wrap'>
|
|
345
472
|
<label>
|
|
346
473
|
<input type='checkbox' defaultChecked={config.table.limitHeight} onChange={e => changeConfigValue('table', 'limitHeight', e.target.checked)} />
|
|
347
474
|
Limit Table Height
|
|
@@ -349,7 +476,7 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
349
476
|
{config.table.limitHeight && <input class='table-height-input' type='text' placeholder='Height (px)' defaultValue={config.table.height} onChange={e => changeConfigValue('table', 'height', e.target.value)} />}
|
|
350
477
|
</div>
|
|
351
478
|
|
|
352
|
-
<div className=
|
|
479
|
+
<div className='wrap'>
|
|
353
480
|
<label>
|
|
354
481
|
<input type='checkbox' defaultChecked={config.table.download} onChange={e => changeConfigValue('table', 'download', e.target.checked)} />
|
|
355
482
|
Show Download CSV Link
|
|
@@ -362,10 +489,9 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
362
489
|
</>
|
|
363
490
|
)}
|
|
364
491
|
</div>
|
|
365
|
-
</div
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
</div >
|
|
492
|
+
</div>
|
|
493
|
+
)}
|
|
494
|
+
</div>
|
|
369
495
|
)
|
|
370
496
|
}
|
|
371
497
|
|