@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.
@@ -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[dataKey].dataDescription)
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.map(row => {
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: </span>
161
- <select
162
- value={filter.columnName}
163
- onChange={e => {
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="toggle-bar__wrapper">
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><br />
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><br />
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="wrap">
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="wrap">
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
 
@@ -7,6 +7,7 @@ export default {
7
7
  table: {
8
8
  label: 'Data Table',
9
9
  show: true,
10
- showDownloadUrl: false
10
+ showDownloadUrl: false,
11
+ showVertical: true,
11
12
  }
12
13
  }