@cdc/dashboard 4.23.4 → 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/index.html CHANGED
@@ -1,31 +1,29 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
6
+ <style>
7
+ body {
8
+ margin: 0 auto !important;
9
+ display: flex;
10
+ flex-direction: column;
11
+ justify-content: center;
12
+ min-height: unset !important;
13
+ }
3
14
 
4
- <head>
5
- <meta charset="utf-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
7
- <style>
8
- body {
9
- margin: 0 auto !important;
10
- display: flex;
11
- flex-direction: column;
12
- justify-content: center;
13
- min-height: unset !important;
14
- }
15
-
16
- .react-container+.react-container {
17
- margin-top: 3rem;
18
- }
19
- </style>
20
- </head>
21
-
22
- <body>
23
- <!-- <div class="react-container" data-config="/examples/default.json"></div> -->
24
- <!-- <div class="react-container" data-config="/examples/default-multi-dataset.json"></div> -->
25
- <div class="react-container" data-config="/examples/default-filter-control.json"></div>
26
- <!-- <div class="react-container" data-config="/examples/private/totals.json"></div> -->
27
- <!-- <div class="react-container" data-config="/examples/private/totals-two.json"></div> -->
28
- <script type="module" src="./src/index.jsx"></script>
29
- </body>
15
+ .react-container + .react-container {
16
+ margin-top: 3rem;
17
+ }
18
+ </style>
19
+ </head>
30
20
 
21
+ <body>
22
+ <div class="react-container" data-config="/examples/default.json"></div>
23
+ <!-- <div class="react-container" data-config="/examples/default-multi-dataset.json"></div> -->
24
+ <!-- <div class="react-container" data-config="/examples/default-filter-control.json"></div> -->
25
+ <!-- <div class="react-container" data-config="/examples/private/totals.json"></div> -->
26
+ <!-- <div class="react-container" data-config="/examples/private/totals-two.json"></div> -->
27
+ <script type="module" src="./src/index.jsx"></script>
28
+ </body>
31
29
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/dashboard",
3
- "version": "4.23.4",
3
+ "version": "4.23.5",
4
4
  "description": "React component for combining multiple visualizations into a single dashboard",
5
5
  "moduleName": "CdcDashboard",
6
6
  "main": "dist/cdcdashboard",
@@ -25,13 +25,13 @@
25
25
  },
26
26
  "license": "Apache-2.0",
27
27
  "dependencies": {
28
- "@cdc/chart": "^4.23.4",
29
- "@cdc/core": "^4.23.4",
30
- "@cdc/data-bite": "^4.23.4",
31
- "@cdc/filtered-text": "^4.23.4",
32
- "@cdc/map": "^4.23.4",
33
- "@cdc/markup-include": "^4.23.4",
34
- "@cdc/waffle-chart": "^4.23.4",
28
+ "@cdc/chart": "^4.23.5",
29
+ "@cdc/core": "^4.23.5",
30
+ "@cdc/data-bite": "^4.23.5",
31
+ "@cdc/filtered-text": "^4.23.5",
32
+ "@cdc/map": "^4.23.5",
33
+ "@cdc/markup-include": "^4.23.5",
34
+ "@cdc/waffle-chart": "^4.23.5",
35
35
  "html-react-parser": "^3.0.8",
36
36
  "js-base64": "^2.5.2",
37
37
  "papaparse": "^5.3.0",
@@ -47,5 +47,5 @@
47
47
  "react": "^18.2.0",
48
48
  "react-dom": "^18.2.0"
49
49
  },
50
- "gitHead": "dcd395d76f70b2d113f2b4c6fe50a52522655cd1"
50
+ "gitHead": "34add3436994ca3cf13e51f313add4d70377f53e"
51
51
  }
@@ -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
 
@@ -115,25 +114,106 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
115
114
 
116
115
  const transform = new DataTransform()
117
116
 
117
+ const getFormattedData = (data, dataDescription) => {
118
+ if (data && dataDescription) {
119
+ try {
120
+ let formattedData = transform.autoStandardize(data)
121
+ formattedData = transform.developerStandardize(data, dataDescription)
122
+ return formattedData
123
+ } catch (e) {
124
+ return data
125
+ }
126
+ }
127
+
128
+ return data
129
+ }
130
+
118
131
  const processData = async config => {
119
132
  let dataset = config.formattedData || config.data
120
133
 
121
134
  if (config.dataUrl) {
122
- dataset = await fetchRemoteData(`${config.dataUrl}?v=${cacheBustingString()}`)
123
-
124
- if (dataset && config.dataDescription) {
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
- }
135
+ dataset = await fetchRemoteData(`${config.dataUrl}`)
136
+
137
+ dataset = getFormattedData(dataset, config.dataDescription)
132
138
  }
133
139
 
134
140
  return dataset
135
141
  }
136
142
 
143
+ const reloadURLData = async () => {
144
+ if (config.datasets) {
145
+ let newData = { ...data }
146
+ let newDatasets = { ...config.datasets }
147
+ let datasetsNeedsUpdate = false
148
+ let datasetKeys = Object.keys(config.datasets)
149
+ for (let i = 0; i < datasetKeys.length; i++) {
150
+ const dataset = config.datasets[datasetKeys[i]]
151
+ if (dataset.dataUrl && config.dashboard && config.dashboard.sharedFilters) {
152
+ const dataUrl = new URL(dataset.runtimeDataUrl || dataset.dataUrl)
153
+ let qsParams = Object.fromEntries(new URLSearchParams(dataUrl.search))
154
+
155
+ let isUpdateNeeded = false
156
+
157
+ config.dashboard.sharedFilters.forEach(filter => {
158
+ if (filter.type === 'url' && qsParams[filter.queryParameter] !== decodeURIComponent(filter.active)) {
159
+ qsParams[filter.queryParameter] = filter.active
160
+ isUpdateNeeded = true
161
+ }
162
+ })
163
+
164
+ if (!isUpdateNeeded) return
165
+
166
+ let dataUrlFinal = `${dataUrl.origin}${dataUrl.pathname}${Object.keys(qsParams)
167
+ .map((param, i) => {
168
+ let qs = i === 0 ? '?' : '&'
169
+ qs += param + '='
170
+ qs += qsParams[param]
171
+ return qs
172
+ })
173
+ .join('')}`
174
+
175
+ let newDataset = await fetchRemoteData(`${dataUrlFinal}`)
176
+
177
+ if (newDataset && dataset.dataDescription) {
178
+ try {
179
+ newDataset = transform.autoStandardize(newDataset)
180
+ newDataset = transform.developerStandardize(newDataset, dataset.dataDescription)
181
+ } catch (e) {
182
+ //Data not able to be standardized, leave as is
183
+ }
184
+ }
185
+
186
+ newDatasets[datasetKeys[i]].runtimeDataUrl = dataUrlFinal
187
+ newData[datasetKeys[i]] = newDataset
188
+ datasetsNeedsUpdate = true
189
+ }
190
+ }
191
+
192
+ if (datasetsNeedsUpdate) {
193
+ setData(newData)
194
+
195
+ let newFilteredData = {}
196
+ let newConfig = { ...config }
197
+ Object.keys(config.visualizations).forEach(key => {
198
+ let dataKey = config.visualizations[key].dataKey
199
+
200
+ let applicableFilters = config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(key) !== -1)
201
+ if (applicableFilters.length > 0) {
202
+ newFilteredData[key] = filterData(applicableFilters, newData[dataKey])
203
+ }
204
+
205
+ if (newData[dataKey]) {
206
+ newConfig.visualizations[key].formattedData = newData[dataKey]
207
+ }
208
+ })
209
+ setFilteredData(newFilteredData)
210
+
211
+ newConfig.datasets = newDatasets
212
+ setConfig(newConfig)
213
+ }
214
+ }
215
+ }
216
+
137
217
  const loadConfig = async () => {
138
218
  let response = configObj || (await (await fetch(configUrl)).json())
139
219
  let newConfig = { ...defaults, ...response }
@@ -201,7 +281,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
201
281
  let add = true
202
282
 
203
283
  filters.forEach(filter => {
204
- if (row[filter.columnName] !== filter.active) {
284
+ if (filter.type !== 'url' && row[filter.columnName] != filter.active) {
205
285
  add = false
206
286
  }
207
287
  })
@@ -230,7 +310,9 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
230
310
  if (applicableFilters.length > 0) {
231
311
  const visualization = newConfig.visualizations[visualizationKey]
232
312
 
233
- newFilteredData[visualizationKey] = filterData(applicableFilters, visualization.formattedData || data[visualization.dataKey])
313
+ const formattedData = visualization.dataDescription ? getFormattedData(data[visualization.dataKey] || visualization.data, visualization.dataDescription) : undefined
314
+
315
+ newFilteredData[visualizationKey] = filterData(applicableFilters, formattedData || data[visualization.dataKey])
234
316
  }
235
317
  })
236
318
 
@@ -291,7 +373,11 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
291
373
  let applicableFilters = newConfig.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) !== -1)
292
374
 
293
375
  if (applicableFilters.length > 0) {
294
- newFilteredData[visualizationKey] = filterData(applicableFilters, newConfig.visualizations[visualizationKey].formattedData || newConfig.visualizations[visualizationKey].data || (dataOverride || data)[newConfig.visualizations[visualizationKey].dataKey])
376
+ const visualization = newConfig.visualizations[visualizationKey]
377
+
378
+ const formattedData = getFormattedData(visualization.data, visualization.dataDescription)
379
+
380
+ newFilteredData[visualizationKey] = filterData(applicableFilters, formattedData || visualization.data || (dataOverride || data)[visualization.dataKey])
295
381
  }
296
382
  })
297
383
  }
@@ -315,6 +401,10 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
315
401
  }
316
402
  }, [config])
317
403
 
404
+ useEffect(() => {
405
+ reloadURLData()
406
+ }, [JSON.stringify(config.dashboard ? config.dashboard.sharedFilters : undefined)])
407
+
318
408
  const updateChildConfig = (visualizationKey, newConfig) => {
319
409
  let updatedConfig = { ...config }
320
410
 
@@ -336,7 +426,11 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
336
426
  Object.keys(config.visualizations).forEach(key => {
337
427
  let applicableFilters = dashboardConfig.sharedFilters.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(key) !== -1)
338
428
  if (applicableFilters.length > 0) {
339
- newFilteredData[key] = filterData(applicableFilters, config.visualizations[key].formattedData || data[config.visualizations[key].dataKey])
429
+ const visualization = config.visualizations[key]
430
+
431
+ const formattedData = visualization.dataDescription ? getFormattedData(data[config.visualizations[key].dataKey] || visualization.data, visualization.dataDescription) : undefined
432
+
433
+ newFilteredData[key] = filterData(applicableFilters, formattedData || data[config.visualizations[key].dataKey])
340
434
  }
341
435
  })
342
436
 
@@ -346,14 +440,14 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
346
440
  const announceChange = text => {}
347
441
 
348
442
  return config.dashboard.sharedFilters.map((singleFilter, index) => {
349
- if (!singleFilter.showDropdown) return <></>
443
+ if (singleFilter.type !== 'url' && !singleFilter.showDropdown) return <></>
350
444
 
351
445
  const values = []
352
446
 
353
447
  singleFilter.values.forEach((filterOption, index) => {
354
448
  values.push(
355
449
  <option key={`${singleFilter.key}-option-${index}`} value={filterOption}>
356
- {filterOption}
450
+ {singleFilter.labels ? singleFilter.labels[filterOption] || filterOption : filterOption}
357
451
  </option>
358
452
  )
359
453
  })
@@ -578,7 +672,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
578
672
  }}
579
673
  setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
580
674
  isDashboard={true}
581
- link={config.table && config.table.show && config.datasets && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
675
+ link={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
582
676
  />
583
677
  )}
584
678
  {visualizationConfig.type === 'map' && (
@@ -592,7 +686,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
592
686
  setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
593
687
  setSharedFilterValue={setSharedFilterValue}
594
688
  isDashboard={true}
595
- 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}
596
690
  />
597
691
  )}
598
692
  {visualizationConfig.type === 'data-bite' && (
@@ -615,7 +709,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
615
709
  updateChildConfig(col.widget, newConfig)
616
710
  }}
617
711
  isDashboard={true}
618
- link={config.table && config.table.show && config.datasets && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
712
+ link={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
619
713
  />
620
714
  )}
621
715
  {visualizationConfig.type === 'markup-include' && (
@@ -678,7 +772,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
678
772
  config.dashboard.sharedFilters.forEach(sharedFilter => {
679
773
  let allMatch = true
680
774
  vizKeysUsingDataset.forEach(visualizationKey => {
681
- if (sharedFilter.usedBy.indexOf(visualizationKey) === -1) {
775
+ if (sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) === -1) {
682
776
  allMatch = false
683
777
  }
684
778
  })
@@ -697,7 +791,7 @@ export default function CdcDashboard({ configUrl = '', config: configObj = undef
697
791
 
698
792
  return (
699
793
  <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>
794
+ <DataTable data={filteredTableData || config.datasets[datasetKey].data || []} downloadData={config.datasets[datasetKey].data} dataFileSourceType={dataFileSourceType} datasetKey={datasetKey} config={config} imageRef={imageId}></DataTable>
701
795
  </div>
702
796
  )
703
797
  })}
@@ -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,47 @@ 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
+
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
+
145
204
  return (
146
205
  <Modal>
147
206
  <Modal.Content>
@@ -157,81 +216,146 @@ const Header = ({ setPreview, tabSelected, setTabSelected, back, subEditor = nul
157
216
  Remove Filter
158
217
  </button>
159
218
  <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>{' '}
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
+ />
213
267
  <button
214
- onClick={e => {
215
- e.preventDefault()
216
- removeFilterUsedBy(filter, index, vizKey)
217
- }}
268
+ onClick={() => removeValue(valueIndex)}
218
269
  >
219
270
  X
220
271
  </button>
221
272
  </li>
222
273
  ))}
223
274
  </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}`}>
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}`}>
230
324
  {config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}
231
325
  </option>
232
326
  ))}
233
- </select>
234
- </label>
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
+ </>}
235
359
  </fieldset>
236
360
  <button type='button' className='btn btn-primary' style={{ display: 'inline-block', 'margin-right': '1em' }} onClick={overlay?.actions.toggleOverlay}>
237
361
  Cancel
@@ -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
  }