@cdc/dashboard 4.23.3 → 4.23.5
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 +88498 -90345
- package/index.html +24 -26
- package/package.json +9 -9
- package/src/CdcDashboard.jsx +135 -28
- package/src/components/Header.jsx +195 -70
- package/src/data/initial-state.js +2 -1
- package/src/index.jsx +3 -2
- package/src/scss/main.scss +3 -2
|
@@ -2,6 +2,8 @@ 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'
|
|
6
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
5
7
|
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
6
8
|
import Modal from '@cdc/core/components/ui/Modal'
|
|
7
9
|
|
|
@@ -12,6 +14,8 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
12
14
|
|
|
13
15
|
const [columns, setColumns] = useState([])
|
|
14
16
|
|
|
17
|
+
const transform = new DataTransform()
|
|
18
|
+
|
|
15
19
|
const changeConfigValue = (parentObj, key, value) => {
|
|
16
20
|
let newConfig = { ...config }
|
|
17
21
|
if (!newConfig[parentObj]) newConfig[parentObj] = {}
|
|
@@ -44,6 +48,13 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
44
48
|
|
|
45
49
|
dashboardConfig.sharedFilters.splice(index, 1)
|
|
46
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
|
+
|
|
47
58
|
updateConfig({ ...config, dashboard: dashboardConfig })
|
|
48
59
|
|
|
49
60
|
overlay?.actions.toggleOverlay()
|
|
@@ -89,7 +100,7 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
89
100
|
if (config.datasets[dataKeys[i]].dataDescription) {
|
|
90
101
|
try {
|
|
91
102
|
config.datasets[dataKeys[i]].data = transform.autoStandardize(config.datasets[dataKeys[i]].data)
|
|
92
|
-
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)
|
|
93
104
|
} catch (e) {
|
|
94
105
|
//Data not able to be standardized, leave as is
|
|
95
106
|
}
|
|
@@ -97,7 +108,7 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
if (config.datasets[dataKeys[i]].data) {
|
|
100
|
-
config.datasets[dataKeys[i]].data.
|
|
111
|
+
config.datasets[dataKeys[i]].data.forEach(row => {
|
|
101
112
|
Object.keys(row).forEach(columnName => (columns[columnName] = true))
|
|
102
113
|
})
|
|
103
114
|
}
|
|
@@ -127,6 +138,14 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
127
138
|
overlay?.actions.openOverlay(filterModal(newFilter, index))
|
|
128
139
|
}
|
|
129
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
|
+
|
|
130
149
|
const addFilterUsedBy = (filter, index, value) => {
|
|
131
150
|
if (!filter.usedBy) filter.usedBy = []
|
|
132
151
|
filter.usedBy.push(value)
|
|
@@ -141,6 +160,47 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
141
160
|
}
|
|
142
161
|
}
|
|
143
162
|
|
|
163
|
+
const updateLabel = (e, value) => {
|
|
164
|
+
let newLabels = filter.labels || {}
|
|
165
|
+
|
|
166
|
+
newLabels[value] = e.target.value
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
updateFilterProp('labels', index, newLabels)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const removeValue = (valueIndex) => {
|
|
173
|
+
let newLabels = filter.labels || {}
|
|
174
|
+
let newValues = filter.values || []
|
|
175
|
+
|
|
176
|
+
delete newLabels[filter.values[valueIndex]]
|
|
177
|
+
newValues.splice(valueIndex, 1)
|
|
178
|
+
|
|
179
|
+
updateFilterPropByFunction(index, newFilter => {
|
|
180
|
+
newFilter.labels = newLabels
|
|
181
|
+
newFilter.orderedValue = newValues
|
|
182
|
+
return newFilter
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const addNewValue = (e) => {
|
|
187
|
+
e.preventDefault()
|
|
188
|
+
if (!filter.values || filter.values.indexOf(e.target[0].value) === -1) {
|
|
189
|
+
let newValues = filter.values || []
|
|
190
|
+
newValues.push(e.target[0].value)
|
|
191
|
+
|
|
192
|
+
updateFilterPropByFunction(index, newFilter => {
|
|
193
|
+
newFilter.values = newValues
|
|
194
|
+
if (!newFilter.active) {
|
|
195
|
+
newFilter.active = e.target[0].value
|
|
196
|
+
}
|
|
197
|
+
return newFilter
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
e.target[0].value = ''
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
144
204
|
return (
|
|
145
205
|
<Modal>
|
|
146
206
|
<Modal.Content>
|
|
@@ -156,81 +216,146 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
156
216
|
Remove Filter
|
|
157
217
|
</button>
|
|
158
218
|
<label>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
<
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
{
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
<ul>
|
|
208
|
-
{filter.usedBy &&
|
|
209
|
-
filter.usedBy.map(vizKey => (
|
|
210
|
-
<li key={`used-by-list-item-${vizKey}`}>
|
|
211
|
-
<span>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}</span>{' '}
|
|
219
|
+
<span className='edit-label column-heading'>Filter Type:</span>
|
|
220
|
+
<select defaultValue={filter.type || 'data'} onChange={e => updateFilterProp('type', index, e.target.value)}>
|
|
221
|
+
<option value="url">URL</option>
|
|
222
|
+
<option value="data">Data</option>
|
|
223
|
+
</select>
|
|
224
|
+
</label>
|
|
225
|
+
{filter.type === 'url' && <>
|
|
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 <option key={datasetKey} value={datasetKey}>{(config.datasets[datasetKey].dataUrl).substring(0, 50)}</option>
|
|
243
|
+
}
|
|
244
|
+
return <React.Fragment key={datasetKey}></React.Fragment>;
|
|
245
|
+
})}
|
|
246
|
+
</select>
|
|
247
|
+
</label>
|
|
248
|
+
<label>
|
|
249
|
+
<span className='edit-label column-heading'>Query string parameter</span>{' '}
|
|
250
|
+
<input
|
|
251
|
+
type='text'
|
|
252
|
+
defaultValue={filter.queryParameter}
|
|
253
|
+
onChange={e => updateFilterProp('queryParameter', index, e.target.value)}
|
|
254
|
+
/>
|
|
255
|
+
</label>
|
|
256
|
+
<span className='edit-label column-heading'>Values</span>{' '}
|
|
257
|
+
<ul className='value-list'>
|
|
258
|
+
{filter.values &&
|
|
259
|
+
filter.values.map((value, valueIndex) => (
|
|
260
|
+
<li>
|
|
261
|
+
{value}
|
|
262
|
+
<input
|
|
263
|
+
type='text'
|
|
264
|
+
value={filter.labels ? filter.labels[value] : undefined}
|
|
265
|
+
onChange={(e) => updateLabel(e, value)}
|
|
266
|
+
/>
|
|
212
267
|
<button
|
|
213
|
-
onClick={
|
|
214
|
-
e.preventDefault()
|
|
215
|
-
removeFilterUsedBy(filter, index, vizKey)
|
|
216
|
-
}}
|
|
268
|
+
onClick={() => removeValue(valueIndex)}
|
|
217
269
|
>
|
|
218
270
|
X
|
|
219
271
|
</button>
|
|
220
272
|
</li>
|
|
221
273
|
))}
|
|
222
274
|
</ul>
|
|
223
|
-
<
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
275
|
+
<form
|
|
276
|
+
onSubmit={addNewValue}
|
|
277
|
+
>
|
|
278
|
+
<input type='text' /> <button type='submit'>Add New Value</button>
|
|
279
|
+
</form>
|
|
280
|
+
</>}
|
|
281
|
+
{filter.type !== 'url' && <>
|
|
282
|
+
<label>
|
|
283
|
+
<span className='edit-label column-heading'>Filter: </span>
|
|
284
|
+
<select
|
|
285
|
+
value={filter.columnName}
|
|
286
|
+
onChange={e => {
|
|
287
|
+
updateFilterProp('columnName', index, e.target.value)
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
<option value=''>- Select Option -</option>
|
|
291
|
+
{columns.map(dataKey => (
|
|
292
|
+
<option value={dataKey} key={`filter-column-select-item-${dataKey}`}>
|
|
293
|
+
{dataKey}
|
|
294
|
+
</option>
|
|
295
|
+
))}
|
|
296
|
+
</select>
|
|
297
|
+
</label>
|
|
298
|
+
<label>
|
|
299
|
+
<span className='edit-label column-heading'>Label: </span>
|
|
300
|
+
<input
|
|
301
|
+
type='text'
|
|
302
|
+
value={filter.key}
|
|
303
|
+
onChange={e => {
|
|
304
|
+
updateFilterProp('key', index, e.target.value)
|
|
305
|
+
}}
|
|
306
|
+
/>
|
|
307
|
+
</label>
|
|
308
|
+
<label>
|
|
309
|
+
<span className='edit-label column-heading'>Show Dropdown</span>
|
|
310
|
+
<input
|
|
311
|
+
type='checkbox'
|
|
312
|
+
defaultChecked={filter.showDropdown === true}
|
|
313
|
+
onChange={e => {
|
|
314
|
+
updateFilterProp('showDropdown', index, !filter.showDropdown)
|
|
315
|
+
}}
|
|
316
|
+
/>
|
|
317
|
+
</label>
|
|
318
|
+
<label>
|
|
319
|
+
<span className='edit-label column-heading'>Set By: </span>
|
|
320
|
+
<select value={filter.setBy} onChange={e => updateFilterProp('setBy', index, e.target.value)}>
|
|
321
|
+
<option value=''>- Select Option -</option>
|
|
322
|
+
{Object.keys(config.visualizations).map(vizKey => (
|
|
323
|
+
<option value={vizKey} key={`set-by-select-item-${vizKey}`}>
|
|
229
324
|
{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}
|
|
230
325
|
</option>
|
|
231
326
|
))}
|
|
232
|
-
|
|
233
|
-
|
|
327
|
+
</select>
|
|
328
|
+
</label>
|
|
329
|
+
<label>
|
|
330
|
+
<span className='edit-label column-heading'>Used By:</span>
|
|
331
|
+
<ul>
|
|
332
|
+
{filter.usedBy &&
|
|
333
|
+
filter.usedBy.map(vizKey => (
|
|
334
|
+
<li key={`used-by-list-item-${vizKey}`}>
|
|
335
|
+
<span>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}</span>{' '}
|
|
336
|
+
<button
|
|
337
|
+
onClick={e => {
|
|
338
|
+
e.preventDefault()
|
|
339
|
+
removeFilterUsedBy(filter, index, vizKey)
|
|
340
|
+
}}
|
|
341
|
+
>
|
|
342
|
+
X
|
|
343
|
+
</button>
|
|
344
|
+
</li>
|
|
345
|
+
))}
|
|
346
|
+
</ul>
|
|
347
|
+
<select onChange={e => addFilterUsedBy(filter, index, e.target.value)}>
|
|
348
|
+
<option value=''>- Select Option -</option>
|
|
349
|
+
{Object.keys(config.visualizations)
|
|
350
|
+
.filter(vizKey => filter.setBy !== vizKey && (!filter.usedBy || filter.usedBy.indexOf(vizKey) === -1) && !config.visualizations[vizKey].usesSharedFilter)
|
|
351
|
+
.map(vizKey => (
|
|
352
|
+
<option value={vizKey} key={`used-by-select-item-${vizKey}`}>
|
|
353
|
+
{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}
|
|
354
|
+
</option>
|
|
355
|
+
))}
|
|
356
|
+
</select>
|
|
357
|
+
</label>
|
|
358
|
+
</>}
|
|
234
359
|
</fieldset>
|
|
235
360
|
<button type='button' className='btn btn-primary' style={{ display: 'inline-block', 'margin-right': '1em' }} onClick={overlay?.actions.toggleOverlay}>
|
|
236
361
|
Cancel
|
|
@@ -320,7 +445,7 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
320
445
|
<div className="wrap">
|
|
321
446
|
<label>
|
|
322
447
|
<input type='checkbox' defaultChecked={config.table.show} onChange={e => changeConfigValue('table', 'show', e.target.checked)} />
|
|
323
|
-
Show Table
|
|
448
|
+
Show Data Table(s)
|
|
324
449
|
</label><br />
|
|
325
450
|
|
|
326
451
|
<label>
|
|
@@ -351,11 +476,11 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
|
|
|
351
476
|
<div className="wrap">
|
|
352
477
|
<label>
|
|
353
478
|
<input type='checkbox' defaultChecked={config.table.download} onChange={e => changeConfigValue('table', 'download', e.target.checked)} />
|
|
354
|
-
Show CSV
|
|
479
|
+
Show Download CSV Link
|
|
355
480
|
</label>
|
|
356
481
|
<label>
|
|
357
482
|
<input type='checkbox' defaultChecked={config.table.showDownloadUrl} onChange={e => changeConfigValue('table', 'showDownloadUrl', e.target.checked)} />
|
|
358
|
-
Show
|
|
483
|
+
Show URL to Automatically Updated Data
|
|
359
484
|
</label>
|
|
360
485
|
</div>
|
|
361
486
|
</>
|
package/src/index.jsx
CHANGED
|
@@ -4,11 +4,12 @@ import ReactDOM from 'react-dom/client'
|
|
|
4
4
|
import CdcDashboard from './CdcDashboard'
|
|
5
5
|
|
|
6
6
|
let isEditor = window.location.href.includes('editor=true')
|
|
7
|
+
let isDebug = window.location.href.includes('debug=true')
|
|
7
8
|
|
|
8
9
|
let domContainer = document.getElementsByClassName('react-container')[0]
|
|
9
10
|
|
|
10
11
|
ReactDOM.createRoot(domContainer).render(
|
|
11
12
|
<React.StrictMode>
|
|
12
|
-
<CdcDashboard configUrl={domContainer.attributes['data-config'].value} isEditor={isEditor} />
|
|
13
|
-
</React.StrictMode
|
|
13
|
+
<CdcDashboard configUrl={domContainer.attributes['data-config'].value} isEditor={isEditor} isDebug={isDebug} />
|
|
14
|
+
</React.StrictMode>
|
|
14
15
|
)
|
package/src/scss/main.scss
CHANGED
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
.wrap {
|
|
49
49
|
display: inline-flex;
|
|
50
50
|
flex-wrap: wrap;
|
|
51
|
-
width: 20
|
|
51
|
+
width: 33%; // changed from 20% so that long checkbox name will fit without wrapping
|
|
52
|
+
font-size: 0.8em;
|
|
52
53
|
|
|
53
54
|
.table-height-input {
|
|
54
55
|
width: 100%;
|
|
@@ -322,4 +323,4 @@
|
|
|
322
323
|
display: block;
|
|
323
324
|
margin: 1em 0;
|
|
324
325
|
}
|
|
325
|
-
}
|
|
326
|
+
}
|