@cdc/dashboard 4.24.3 → 4.24.4

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.
Files changed (34) hide show
  1. package/dist/cdcdashboard.js +119414 -103311
  2. package/examples/chart-data.json +5409 -0
  3. package/examples/full-dash-test.json +14643 -0
  4. package/examples/full-dashboard.json +10036 -0
  5. package/index.html +2 -2
  6. package/package.json +9 -9
  7. package/src/CdcDashboard.tsx +8 -3
  8. package/src/CdcDashboardComponent.tsx +232 -344
  9. package/src/_stories/Dashboard.stories.tsx +59 -38
  10. package/src/_stories/_mock/api-filter-chart.json +11 -35
  11. package/src/_stories/_mock/api-filter-map.json +17 -31
  12. package/src/_stories/_mock/dashboard-gallery.json +523 -534
  13. package/src/_stories/_mock/multi-viz.json +378 -0
  14. package/src/_stories/_mock/pivot-filter.json +0 -2
  15. package/src/components/DataDesignerModal.tsx +145 -0
  16. package/src/components/Grid.tsx +3 -1
  17. package/src/components/Header/FilterModal.tsx +49 -23
  18. package/src/components/Row.tsx +50 -25
  19. package/src/components/Toggle/Toggle.tsx +6 -7
  20. package/src/components/VisualizationRow.tsx +174 -0
  21. package/src/components/Widget.tsx +21 -103
  22. package/src/helpers/filterData.ts +16 -14
  23. package/src/helpers/getFilteredData.ts +39 -0
  24. package/src/helpers/getUpdateConfig.ts +15 -0
  25. package/src/helpers/getVizConfig.ts +31 -0
  26. package/src/helpers/getVizRowColumnLocator.ts +9 -0
  27. package/src/scss/grid.scss +9 -2
  28. package/src/scss/main.scss +5 -0
  29. package/src/store/dashboard.actions.ts +16 -1
  30. package/src/store/dashboard.reducer.ts +25 -2
  31. package/src/types/APIFilter.ts +4 -5
  32. package/src/types/ConfigRow.ts +12 -3
  33. package/src/types/DataSet.ts +11 -8
  34. package/src/types/SharedFilter.ts +1 -1
@@ -0,0 +1,378 @@
1
+ {
2
+ "dashboard": {
3
+ "theme": "theme-blue",
4
+ "sharedFilters": [
5
+ {
6
+ "key": "Dashboard Filter 1",
7
+ "type": "datafilter",
8
+ "columnName": "Sample Categories",
9
+ "showDropdown": true,
10
+ "usedBy": ["0"],
11
+ "tier": 1
12
+ }
13
+ ]
14
+ },
15
+ "rows": [
16
+ {
17
+ "toggle": true,
18
+ "columns": [
19
+ {
20
+ "width": 12,
21
+ "widget": "chart1711646813542"
22
+ },
23
+ {
24
+ "width": 12,
25
+ "widget": "table1711646820983"
26
+ },
27
+ {
28
+ "width": 12
29
+ }
30
+ ],
31
+ "dataDescription": {
32
+ "horizontal": false,
33
+ "series": false
34
+ },
35
+
36
+ "dataKey": "valid-world-data.json",
37
+
38
+ "multiVizColumn": "Country"
39
+ }
40
+ ],
41
+ "visualizations": {
42
+ "chart1711646813542": {
43
+ "type": "chart",
44
+ "debugSvg": false,
45
+ "chartMessage": {
46
+ "noData": "No Data Available"
47
+ },
48
+ "title": "",
49
+ "showTitle": true,
50
+ "showDownloadMediaButton": false,
51
+ "theme": "theme-blue",
52
+ "animate": false,
53
+ "fontSize": "medium",
54
+ "lineDatapointStyle": "hover",
55
+ "lineDatapointColor": "Same as Line",
56
+ "barHasBorder": "false",
57
+ "isLollipopChart": false,
58
+ "lollipopShape": "circle",
59
+ "lollipopColorStyle": "two-tone",
60
+ "visualizationSubType": "regular",
61
+ "barStyle": "",
62
+ "roundingStyle": "standard",
63
+ "tipRounding": "top",
64
+ "isResponsiveTicks": false,
65
+ "general": {
66
+ "showDownloadButton": false
67
+ },
68
+ "padding": {
69
+ "left": 5,
70
+ "right": 5
71
+ },
72
+ "suppressedData": [],
73
+ "preliminaryData": [],
74
+ "yAxis": {
75
+ "hideAxis": false,
76
+ "displayNumbersOnBar": false,
77
+ "hideLabel": false,
78
+ "hideTicks": false,
79
+ "size": 50,
80
+ "gridLines": false,
81
+ "enablePadding": false,
82
+ "min": "",
83
+ "max": "",
84
+ "labelColor": "#333",
85
+ "tickLabelColor": "#333",
86
+ "tickColor": "#333",
87
+ "rightHideAxis": true,
88
+ "rightAxisSize": 0,
89
+ "rightLabel": "",
90
+ "rightLabelOffsetSize": 0,
91
+ "rightAxisLabelColor": "#333",
92
+ "rightAxisTickLabelColor": "#333",
93
+ "rightAxisTickColor": "#333",
94
+ "numTicks": "",
95
+ "axisPadding": 0,
96
+ "scalePadding": 10,
97
+ "tickRotation": 0,
98
+ "anchors": []
99
+ },
100
+ "boxplot": {
101
+ "plots": [],
102
+ "borders": "true",
103
+ "firstQuartilePercentage": 25,
104
+ "thirdQuartilePercentage": 75,
105
+ "boxWidthPercentage": 40,
106
+ "plotOutlierValues": false,
107
+ "plotNonOutlierValues": true,
108
+ "legend": {
109
+ "showHowToReadText": false,
110
+ "howToReadText": ""
111
+ },
112
+ "labels": {
113
+ "q1": "Lower Quartile",
114
+ "q2": "q2",
115
+ "q3": "Upper Quartile",
116
+ "q4": "q4",
117
+ "minimum": "Minimum",
118
+ "maximum": "Maximum",
119
+ "mean": "Mean",
120
+ "median": "Median",
121
+ "sd": "Standard Deviation",
122
+ "iqr": "Interquartile Range",
123
+ "total": "Total",
124
+ "outliers": "Outliers",
125
+ "values": "Values",
126
+ "lowerBounds": "Lower Bounds",
127
+ "upperBounds": "Upper Bounds"
128
+ }
129
+ },
130
+ "topAxis": {
131
+ "hasLine": false
132
+ },
133
+ "isLegendValue": false,
134
+ "barThickness": 0.35,
135
+ "barHeight": 25,
136
+ "barSpace": 15,
137
+ "heights": {
138
+ "vertical": 300,
139
+ "horizontal": 750
140
+ },
141
+ "xAxis": {
142
+ "sortDates": false,
143
+ "anchors": [],
144
+ "type": "categorical",
145
+ "showTargetLabel": true,
146
+ "targetLabel": "Target",
147
+ "hideAxis": false,
148
+ "hideLabel": false,
149
+ "hideTicks": false,
150
+ "size": 75,
151
+ "tickRotation": 0,
152
+ "min": "",
153
+ "max": "",
154
+ "labelColor": "#333",
155
+ "tickLabelColor": "#333",
156
+ "tickColor": "#333",
157
+ "numTicks": "",
158
+ "labelOffset": 65,
159
+ "axisPadding": 0,
160
+ "target": 0,
161
+ "maxTickRotation": 0,
162
+ "dataKey": "Sample Categories",
163
+ "tickWidthMax": 89
164
+ },
165
+ "table": {
166
+ "label": "Data Table",
167
+ "expanded": true,
168
+ "limitHeight": false,
169
+ "height": "",
170
+ "caption": "",
171
+ "showDownloadUrl": false,
172
+ "showDataTableLink": false,
173
+ "indexLabel": "",
174
+ "download": false,
175
+ "showVertical": true,
176
+ "dateDisplayFormat": "",
177
+ "show": false
178
+ },
179
+ "orientation": "vertical",
180
+ "color": "pinkpurple",
181
+ "columns": {},
182
+ "legend": {
183
+ "hide": false,
184
+ "behavior": "isolate",
185
+ "singleRow": true,
186
+ "colorCode": "",
187
+ "reverseLabelOrder": false,
188
+ "description": "",
189
+ "dynamicLegend": false,
190
+ "dynamicLegendDefaultText": "Show All",
191
+ "dynamicLegendItemLimit": 5,
192
+ "dynamicLegendItemLimitMessage": "Dynamic Legend Item Limit Hit.",
193
+ "dynamicLegendChartMessage": "Select Options from the Legend",
194
+ "lineMode": false,
195
+ "verticalSorted": false,
196
+ "highlightOnHover": false,
197
+ "seriesHighlight": []
198
+ },
199
+ "brush": {
200
+ "height": 25,
201
+ "active": false
202
+ },
203
+ "exclusions": {
204
+ "active": false,
205
+ "keys": []
206
+ },
207
+ "palette": "qualitative-bold",
208
+ "isPaletteReversed": false,
209
+ "twoColor": {
210
+ "palette": "monochrome-1",
211
+ "isPaletteReversed": false
212
+ },
213
+ "labels": false,
214
+ "dataFormat": {
215
+ "commas": false,
216
+ "prefix": "",
217
+ "suffix": "",
218
+ "abbreviated": false,
219
+ "bottomSuffix": "",
220
+ "bottomPrefix": "",
221
+ "bottomAbbreviated": false
222
+ },
223
+ "confidenceKeys": {},
224
+ "visual": {
225
+ "border": true,
226
+ "accent": true,
227
+ "background": true,
228
+ "verticalHoverLine": false,
229
+ "horizontalHoverLine": false
230
+ },
231
+ "useLogScale": false,
232
+ "filterBehavior": "Filter Change",
233
+ "highlightedBarValues": [],
234
+ "series": [
235
+ {
236
+ "dataKey": "Data",
237
+ "type": "Bar",
238
+ "axis": "Left",
239
+ "tooltip": true
240
+ }
241
+ ],
242
+ "tooltips": {
243
+ "opacity": 90,
244
+ "singleSeries": false,
245
+ "dateDisplayFormat": ""
246
+ },
247
+ "forestPlot": {
248
+ "startAt": 0,
249
+ "colors": {
250
+ "line": "",
251
+ "shape": ""
252
+ },
253
+ "lineOfNoEffect": {
254
+ "show": true
255
+ },
256
+ "type": "",
257
+ "pooledResult": {
258
+ "diamondHeight": 5,
259
+ "column": ""
260
+ },
261
+ "estimateField": "",
262
+ "estimateRadius": "",
263
+ "shape": "square",
264
+ "rowHeight": 20,
265
+ "description": {
266
+ "show": true,
267
+ "text": "description",
268
+ "location": 0
269
+ },
270
+ "result": {
271
+ "show": true,
272
+ "text": "result",
273
+ "location": 100
274
+ },
275
+ "radius": {
276
+ "min": 2,
277
+ "max": 10,
278
+ "scalingColumn": ""
279
+ },
280
+ "regression": {
281
+ "lower": 0,
282
+ "upper": 0,
283
+ "estimateField": 0
284
+ },
285
+ "leftWidthOffset": 0,
286
+ "rightWidthOffset": 0,
287
+ "showZeroLine": false,
288
+ "leftLabel": "",
289
+ "rightLabel": ""
290
+ },
291
+ "area": {
292
+ "isStacked": false
293
+ },
294
+ "sankey": {
295
+ "title": {
296
+ "defaultColor": "black"
297
+ },
298
+ "iterations": 1,
299
+ "rxValue": 0.9,
300
+ "overallSize": {
301
+ "width": 900,
302
+ "height": 700
303
+ },
304
+ "margin": {
305
+ "margin_y": 25,
306
+ "margin_x": 0
307
+ },
308
+ "nodeSize": {
309
+ "nodeWidth": 26,
310
+ "nodeHeight": 40
311
+ },
312
+ "nodePadding": 55,
313
+ "nodeFontColor": "black",
314
+ "nodeColor": {
315
+ "default": "#ff8500",
316
+ "inactive": "#808080"
317
+ },
318
+ "linkColor": {
319
+ "default": "#ffc900",
320
+ "inactive": "#D3D3D3"
321
+ },
322
+ "opacity": {
323
+ "nodeOpacityDefault": 1,
324
+ "nodeOpacityInactive": 0.1,
325
+ "LinkOpacityDefault": 1,
326
+ "LinkOpacityInactive": 0.1
327
+ },
328
+ "storyNodeFontColor": "#006778",
329
+ "storyNodeText": [],
330
+ "nodeValueStyle": {
331
+ "textBefore": "(",
332
+ "textAfter": ")"
333
+ },
334
+ "data": []
335
+ },
336
+ "openModal": true,
337
+ "uid": "chart1711646813542",
338
+ "visualizationType": "Bar",
339
+ "editing": false,
340
+ "dataKey": "valid-world-data.json",
341
+ "dataDescription": {
342
+ "horizontal": false,
343
+ "series": false
344
+ },
345
+ "version": "4.23",
346
+ "dynamicMarginTop": 0
347
+ },
348
+ "table1711646820983": {
349
+ "newViz": false,
350
+ "openModal": true,
351
+ "uid": "table1711646820983",
352
+ "type": "table",
353
+ "formattedData": {},
354
+ "table": {
355
+ "label": "Data Table",
356
+ "show": true,
357
+ "showDownloadUrl": false,
358
+ "showVertical": true,
359
+ "expanded": true
360
+ },
361
+ "columns": {},
362
+ "dataFormat": {},
363
+ "visualizationType": "table",
364
+ "editing": false
365
+ }
366
+ },
367
+ "table": {
368
+ "label": "Data Table",
369
+ "show": true,
370
+ "showDownloadUrl": false,
371
+ "showVertical": true
372
+ },
373
+ "newViz": true,
374
+ "datasets": {},
375
+ "type": "dashboard",
376
+ "runtime": {},
377
+ "version": "4.23"
378
+ }
@@ -24,7 +24,6 @@
24
24
  ],
25
25
  "visualizations": {
26
26
  "table1707935263149": {
27
- "newViz": true,
28
27
  "openModal": false,
29
28
  "uid": "table1707935263149",
30
29
  "type": "table",
@@ -115,7 +114,6 @@
115
114
  "showDownloadUrl": false,
116
115
  "showVertical": true
117
116
  },
118
- "newViz": true,
119
117
  "datasets": {
120
118
  "valid-data-chart.csv": {
121
119
  "data": [
@@ -0,0 +1,145 @@
1
+ import { useGlobalContext } from '@cdc/core/components/GlobalContext'
2
+ import DataDesigner from '@cdc/core/components/managers/DataDesigner'
3
+ import { useContext, useMemo, useState } from 'react'
4
+ import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
5
+ import Modal from '@cdc/core/components/ui/Modal'
6
+ import { CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
7
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
8
+ import _ from 'lodash'
9
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
10
+ import DataTransform from '@cdc/core/helpers/DataTransform'
11
+ import { ConfigureData } from '@cdc/core/types/ConfigureData'
12
+ import Icon from '@cdc/core/components/ui/Icon'
13
+ import InputSelect from '@cdc/core/components/inputs/InputSelect'
14
+
15
+ type DataDesignerModalProps = {
16
+ rowIndex: number
17
+ vizKey?: string
18
+ }
19
+
20
+ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, rowIndex }) => {
21
+ const { config } = useContext(DashboardContext)
22
+ const { overlay } = useGlobalContext()
23
+ const transform = new DataTransform()
24
+ const dispatch = useContext(DashboardDispatchContext)
25
+ const [canContinue, setCanContinue] = useState(false)
26
+ const [useRow, setUseRow] = useState(!vizKey)
27
+ const [multiViz, setMultiViz] = useState(!!config.rows[rowIndex].multiVizColumn)
28
+
29
+ const configureData = useMemo(() => {
30
+ if (vizKey && !useRow) {
31
+ return config.visualizations[vizKey]
32
+ }
33
+ return config.rows[rowIndex]
34
+ }, [config.visualizations, config.rows, vizKey, rowIndex, useRow])
35
+
36
+ const updateConfigureData = (newConfigureData: Partial<ConfigureData>) => {
37
+ if (vizKey && !useRow) {
38
+ dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey, configureData: newConfigureData } })
39
+ } else {
40
+ dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: newConfigureData } })
41
+ }
42
+ }
43
+
44
+ const changeDataset = ({ target: { value } }) => {
45
+ const newConfigureData = { dataDescription: {}, formattedData: undefined, dataKey: value }
46
+
47
+ updateConfigureData(newConfigureData)
48
+ }
49
+
50
+ const updateDescriptionProp = async (key, value) => {
51
+ const datasetKey = configureData.dataKey
52
+ const { data, dataUrl } = config.datasets[datasetKey]
53
+ let newData = data
54
+ if (!data && dataUrl) {
55
+ newData = await fetchRemoteData(dataUrl)
56
+ newData = transform.autoStandardize(newData)
57
+ }
58
+
59
+ const dataDescription = { ...configureData.dataDescription, [key]: value }
60
+
61
+ const newConfigureData = { data: newData, dataDescription, formattedData: transform.developerStandardize(newData, dataDescription) }
62
+
63
+ updateConfigureData(newConfigureData)
64
+ setCanContinue(true)
65
+ }
66
+
67
+ const setMultiVizColumn = (column: string) => {
68
+ if (column !== '') {
69
+ dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: { multiVizColumn: column } } })
70
+ setCanContinue(true)
71
+ }
72
+ }
73
+
74
+ return (
75
+ <Modal>
76
+ <Modal.Content>
77
+ <div className='dataset-selector-container'>
78
+ Select a dataset:&nbsp;
79
+ <select className='dataset-selector' value={configureData.dataKey || ''} onChange={changeDataset}>
80
+ <option value=''>Select a dataset</option>
81
+ {config.datasets && Object.keys(config.datasets).map(datasetKey => <option key={datasetKey}>{datasetKey}</option>)}
82
+ </select>
83
+ {vizKey && (
84
+ <CheckBox
85
+ label='Apply To Row'
86
+ value={useRow}
87
+ updateField={(section, subsection, fieldName, value) => {
88
+ setUseRow(value)
89
+ changeDataset({ target: { value: '' } })
90
+ }}
91
+ />
92
+ )}
93
+ </div>
94
+ {configureData.dataKey && (
95
+ <DataDesigner
96
+ {...{
97
+ configureData,
98
+ visualizationKey: vizKey,
99
+ updateDescriptionProp
100
+ }}
101
+ />
102
+ )}
103
+ {useRow && !!configureData.dataKey ? (
104
+ !multiViz ? (
105
+ <CheckBox
106
+ label='Configure Multiple Visualizations'
107
+ value={multiViz}
108
+ tooltip={
109
+ <Tooltip style={{ textTransform: 'none' }}>
110
+ <Tooltip.Target>
111
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
112
+ </Tooltip.Target>
113
+ <Tooltip.Content>
114
+ <p>You can select a column where for each unique value in the column the configuration for the row will be repeated in to the data preview.</p>
115
+ </Tooltip.Content>
116
+ </Tooltip>
117
+ }
118
+ updateField={(section, subsection, fieldName, value) => {
119
+ if (canContinue && value === true) setCanContinue(false)
120
+ setMultiViz(value)
121
+ }}
122
+ />
123
+ ) : (
124
+ <InputSelect
125
+ options={Object.keys(config.datasets[configureData.dataKey]?.data[0] || {})}
126
+ value={config.rows[rowIndex].multiVizColumn}
127
+ label='Multi-Visualization Column'
128
+ initial='--Select--'
129
+ fieldName=''
130
+ updateField={(section, subsection, fieldName, value) => setMultiVizColumn(value)}
131
+ required
132
+ />
133
+ )
134
+ ) : (
135
+ <></>
136
+ )}
137
+ {canContinue && (
138
+ <button style={{ margin: '1em', display: 'block' }} className='cove-button' onClick={() => overlay?.actions.toggleOverlay()}>
139
+ Continue
140
+ </button>
141
+ )}
142
+ </Modal.Content>
143
+ </Modal>
144
+ )
145
+ }
@@ -2,6 +2,7 @@ import React, { useContext } from 'react'
2
2
  import Row from './Row'
3
3
 
4
4
  import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
5
+ import { ConfigRow } from '../types/ConfigRow'
5
6
 
6
7
  const Grid = () => {
7
8
  const { config } = useContext(DashboardContext)
@@ -10,9 +11,10 @@ const Grid = () => {
10
11
  const dispatch = useContext(DashboardDispatchContext)
11
12
  const updateConfig = config => dispatch({ type: 'UPDATE_CONFIG', payload: [config] })
12
13
  const addRow = () => {
14
+ const blankRow: Partial<ConfigRow> = { columns: [{ width: 12 }] }
13
15
  updateConfig({
14
16
  ...config,
15
- rows: [...rows, [{ width: 12 }, { equalHeight: false }, {}, {}]],
17
+ rows: [...rows, blankRow],
16
18
  uuid: Date.now()
17
19
  })
18
20
  }
@@ -1,4 +1,4 @@
1
- import { useContext, useEffect, useState } from 'react'
1
+ import { useContext, useEffect, useMemo, useState } from 'react'
2
2
  import { MultiDashboardConfig } from '../../types/MultiDashboard'
3
3
  import { SharedFilter } from '../../types/SharedFilter'
4
4
  import { DashboardDispatchContext } from '../../DashboardContext'
@@ -11,6 +11,8 @@ import Icon from '@cdc/core/components/ui/Icon'
11
11
  import Button from '@cdc/core/components/elements/Button'
12
12
  import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
13
13
  import DataTransform from '@cdc/core/helpers/DataTransform'
14
+ import { getVizRowColumnLocator } from '../../helpers/getVizRowColumnLocator'
15
+ import _ from 'lodash'
14
16
 
15
17
  type ModalProps = {
16
18
  config: MultiDashboardConfig
@@ -26,9 +28,39 @@ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeF
26
28
  const [columns, setColumns] = useState<string[]>([])
27
29
  const transform = new DataTransform()
28
30
 
31
+ const vizRowColumnLocator = getVizRowColumnLocator(config.rows)
32
+
33
+ const [usedByNameLookup, usedByOptions] = useMemo(() => {
34
+ const nameLookup = {}
35
+ const vizOptions = Object.keys(config.visualizations)
36
+ .filter(vizKey => {
37
+ const notAdded = !filter.usedBy || filter.usedBy.indexOf(vizKey) === -1
38
+ const usesSharedFilter = config.visualizations[vizKey].usesSharedFilter
39
+ const row = vizRowColumnLocator[vizKey].row
40
+ const dataConfiguredOnRow = config.rows[row].dataKey
41
+ return filter.setBy !== vizKey && notAdded && !usesSharedFilter && !dataConfiguredOnRow
42
+ })
43
+ .map(vizKey => {
44
+ const viz = config.visualizations[vizKey]
45
+ const vizName = viz.general?.title || viz.title || vizKey
46
+ nameLookup[vizKey] = vizName
47
+ return vizKey
48
+ })
49
+ const rowOptions: number[] = []
50
+
51
+ config.rows.forEach((row, rowIndex) => {
52
+ if (!!row.multiVizColumn) {
53
+ nameLookup[rowIndex] = `Row ${rowIndex + 1}`
54
+ rowOptions.push(rowIndex)
55
+ }
56
+ })
57
+
58
+ const rowsNotSelected = rowOptions.filter(row => !filter.usedBy || filter.usedBy.indexOf(row.toString()) === -1)
59
+ return [nameLookup, [...vizOptions, ...rowsNotSelected]]
60
+ }, [config.visualizations, filter.usedBy, filter.setBy, vizRowColumnLocator])
61
+
29
62
  useEffect(() => {
30
63
  const runSetColumns = async () => {
31
- if (config.filterBehavior === FilterBehavior.Apply) return
32
64
  let columns = {}
33
65
  let dataKeys = Object.keys(config.datasets)
34
66
 
@@ -71,18 +103,13 @@ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeF
71
103
  }
72
104
 
73
105
  const updateFilterProp = (name, value) => {
74
- // @TODO this should be refactored into a reducer function.
75
- // it's unsafe to directly set objects w/o guardrails
76
- let newFilter = { ...filter }
77
-
78
- newFilter[name] = value
79
-
80
- console.log('newFilter', newFilter)
106
+ const newFilter = { ..._.cloneDeep(filter), [name]: value }
81
107
 
82
108
  setFilter(newFilter)
83
109
  }
84
110
 
85
111
  const addFilterUsedBy = (filter, value) => {
112
+ if (value === '') return
86
113
  if (!filter.usedBy) filter.usedBy = []
87
114
  filter.usedBy.push(value)
88
115
  updateFilterProp('usedBy', filter.usedBy)
@@ -97,9 +124,10 @@ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeF
97
124
  }
98
125
 
99
126
  const updateAPIFilter = (key: keyof APIFilter, value: string | boolean) => {
100
- const _filter = filter.apiFilter || { apiEndpoint: '', valueSelector: '', textSelector: '' }
127
+ const filterClone = _.cloneDeep(filter)
128
+ const _filter = filterClone.apiFilter || { apiEndpoint: '', valueSelector: '', textSelector: '' }
101
129
  const newAPIFilter: APIFilter = { ..._filter, [key]: value }
102
- setFilter({ ...filter, apiFilter: newAPIFilter })
130
+ setFilter({ ...filterClone, apiFilter: newAPIFilter })
103
131
  }
104
132
 
105
133
  return (
@@ -388,13 +416,13 @@ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeF
388
416
  <span className='edit-label column-heading'>Used By: </span>
389
417
  <ul>
390
418
  {filter.usedBy &&
391
- filter.usedBy.map(vizKey => (
392
- <li key={`used-by-list-item-${vizKey}`}>
393
- <span>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}</span>{' '}
419
+ filter.usedBy.map(opt => (
420
+ <li key={`used-by-list-item-${opt}`}>
421
+ <span>{usedByNameLookup[opt] || opt}</span>{' '}
394
422
  <button
395
423
  onClick={e => {
396
424
  e.preventDefault()
397
- removeFilterUsedBy(filter, vizKey)
425
+ removeFilterUsedBy(filter, opt)
398
426
  }}
399
427
  >
400
428
  X
@@ -402,15 +430,13 @@ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeF
402
430
  </li>
403
431
  ))}
404
432
  </ul>
405
- <select onChange={e => addFilterUsedBy(filter, e.target.value)}>
433
+ <select value='' onChange={e => addFilterUsedBy(filter, e.target.value)}>
406
434
  <option value=''>- Select Option -</option>
407
- {Object.keys(config.visualizations)
408
- .filter(vizKey => filter.setBy !== vizKey && (!filter.usedBy || filter.usedBy.indexOf(vizKey) === -1) && !config.visualizations[vizKey].usesSharedFilter)
409
- .map(vizKey => (
410
- <option value={vizKey} key={`used-by-select-item-${vizKey}`}>
411
- {config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}
412
- </option>
413
- ))}
435
+ {usedByOptions.map(opt => (
436
+ <option value={opt} key={`used-by-select-item-${opt}`}>
437
+ {usedByNameLookup[opt] || opt}
438
+ </option>
439
+ ))}
414
440
  </select>
415
441
  </label>
416
442
  <label>