@cdc/dashboard 4.25.11 → 4.26.2

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 (77) hide show
  1. package/Dynamic_Data.md +66 -0
  2. package/dist/cdcdashboard-8NmHlKRI.es.js +15 -0
  3. package/dist/cdcdashboard-BPoPzKPz.es.js +6 -0
  4. package/dist/cdcdashboard-Cf9_fbQf.es.js +6 -0
  5. package/dist/{cdcdashboard-dgT_1dIT.es.js → cdcdashboard-DQ00cQCm.es.js} +1 -20
  6. package/dist/cdcdashboard-jiQQPkty.es.js +6 -0
  7. package/dist/cdcdashboard.js +83537 -86913
  8. package/examples/api-dashboard-data.json +272 -0
  9. package/examples/api-dashboard-years.json +11 -0
  10. package/examples/api-geographies-data.json +11 -0
  11. package/examples/default.json +522 -133
  12. package/examples/nested-dropdown.json +6985 -0
  13. package/examples/private/abc.json +467 -0
  14. package/examples/private/cat-y.json +1235 -0
  15. package/examples/private/chronic-dash.json +1584 -0
  16. package/examples/private/dash.json +12696 -0
  17. package/examples/private/map-issue.json +2260 -0
  18. package/examples/private/mpinc-state-reports.json +2260 -0
  19. package/examples/private/npcr.json +1 -0
  20. package/examples/private/nwss/rsv.json +1240 -0
  21. package/examples/private/simple-dash.json +490 -0
  22. package/examples/private/test-dash.json +0 -0
  23. package/examples/private/test.json +125407 -0
  24. package/examples/private/test123.json +491 -0
  25. package/examples/private/timeline-data.json +4994 -0
  26. package/examples/private/timeline.json +1708 -0
  27. package/examples/test-api-filter-reset.json +8 -4
  28. package/examples/test-dashboard-simple.json +503 -0
  29. package/examples/tp5-gauges.json +196 -0
  30. package/examples/tp5-test.json +266 -0
  31. package/index.html +1 -30
  32. package/package.json +39 -40
  33. package/src/CdcDashboardComponent.tsx +18 -5
  34. package/src/_stories/Dashboard.DataSetup.stories.tsx +204 -0
  35. package/src/_stories/Dashboard.stories.tsx +407 -1
  36. package/src/_stories/_mock/dashboard-line-chart-angles.json +1030 -0
  37. package/src/_stories/_mock/filter-cascade.json +3350 -0
  38. package/src/_stories/_mock/gallery-data-bite-dashboard.json +3500 -0
  39. package/src/_stories/_mock/nested-parent-child-filters.json +392 -0
  40. package/src/_stories/_mock/parent-child-filters.json +233 -0
  41. package/src/_stories/_mock/tp5-test.json +267 -0
  42. package/src/components/DashboardFilters/DashboardFilters.tsx +20 -11
  43. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +92 -38
  44. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +56 -30
  45. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +151 -10
  46. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +11 -7
  47. package/src/components/DataDesignerModal.tsx +6 -1
  48. package/src/components/Header/Header.tsx +51 -20
  49. package/src/components/VisualizationRow.tsx +76 -5
  50. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +2 -20
  51. package/src/components/Widget/Widget.tsx +1 -1
  52. package/src/data/initial-state.js +1 -0
  53. package/src/helpers/addValuesToDashboardFilters.ts +30 -31
  54. package/src/helpers/apiFilterHelpers.ts +28 -32
  55. package/src/helpers/changeFilterActive.ts +67 -65
  56. package/src/helpers/formatConfigBeforeSave.ts +6 -5
  57. package/src/helpers/getUpdateConfig.ts +91 -91
  58. package/src/helpers/tests/addValuesToDashboardFilters.test.ts +141 -44
  59. package/src/helpers/tests/apiFilterHelpers.test.ts +523 -420
  60. package/src/helpers/tests/updatesChildFilters.test.ts +53 -22
  61. package/src/helpers/updateChildFilters.ts +50 -27
  62. package/src/scss/main.scss +144 -1
  63. package/src/test/CdcDashboard.test.jsx +9 -4
  64. package/src/types/Dashboard.ts +1 -0
  65. package/src/types/FilterStyles.ts +8 -7
  66. package/src/types/SharedFilter.ts +13 -0
  67. package/vite.config.js +7 -1
  68. package/LICENSE +0 -201
  69. package/dist/cdcdashboard-BnB1QM5d.es.js +0 -361528
  70. package/dist/cdcdashboard-Ct2SB0vL.es.js +0 -231049
  71. package/dist/cdcdashboard-D6CG2-Hb.es.js +0 -39377
  72. package/dist/cdcdashboard-MXgURbdZ.es.js +0 -39194
  73. package/examples/private/DEV-10538.json +0 -407
  74. package/examples/private/DEV-11072.json +0 -7591
  75. package/examples/private/DEV-11405.json +0 -39112
  76. package/examples/private/delete.json +0 -32919
  77. package/examples/private/pedro.json +0 -1
@@ -0,0 +1,267 @@
1
+ {
2
+ "type": "dashboard",
3
+ "title": "TP5 Alignment Test Dashboard",
4
+ "description": "Testing alignment of TP5 Waffles, Data Bites, and Gauges",
5
+ "data": [
6
+ {
7
+ "Category": "Adults",
8
+ "Vaccination Rate": "68.5",
9
+ "Insured Rate": "87.2",
10
+ "Screening Rate": "72.8"
11
+ },
12
+ {
13
+ "Category": "Seniors",
14
+ "Vaccination Rate": "82.3",
15
+ "Insured Rate": "95.1",
16
+ "Screening Rate": "84.6"
17
+ },
18
+ {
19
+ "Category": "Youth",
20
+ "Vaccination Rate": "54.2",
21
+ "Insured Rate": "92.4",
22
+ "Screening Rate": "65.3"
23
+ }
24
+ ],
25
+ "dashboard": {
26
+ "filters": []
27
+ },
28
+ "rows": [
29
+ [
30
+ { "width": 4, "widget": "waffle1" },
31
+ { "width": 4, "widget": "waffle2" },
32
+ { "width": 4, "widget": "waffle3" }
33
+ ],
34
+ [
35
+ { "width": 4, "widget": "bite1" },
36
+ { "width": 4, "widget": "bite2" },
37
+ { "width": 4, "widget": "bite3" }
38
+ ],
39
+ [
40
+ { "width": 4, "widget": "gauge1" },
41
+ { "width": 4, "widget": "gauge2" },
42
+ { "width": 4, "widget": "gauge3" }
43
+ ]
44
+ ],
45
+ "visualizations": {
46
+ "waffle1": {
47
+ "uid": "waffle1",
48
+ "type": "waffle-chart",
49
+ "title": "Vaccination Coverage",
50
+ "visualizationType": "TP5 Waffle",
51
+ "visualizationSubType": "linear",
52
+ "showPercent": true,
53
+ "showDenominator": false,
54
+ "valueDescription": "",
55
+ "content": "of the population is vaccinated against seasonal flu",
56
+ "subtext": "Based on 2024 CDC surveillance data across all age groups",
57
+ "dataColumn": "Vaccination Rate",
58
+ "dataFunction": "Mean (Average)",
59
+ "customDenom": false,
60
+ "dataDenom": "100",
61
+ "suffix": "%",
62
+ "roundToPlace": "1",
63
+ "theme": "theme-blue",
64
+ "shape": "square",
65
+ "visual": {
66
+ "whiteBackground": false
67
+ },
68
+ "showTitle": true,
69
+ "overallFontSize": "medium"
70
+ },
71
+ "waffle2": {
72
+ "uid": "waffle2",
73
+ "type": "waffle-chart",
74
+ "title": "Health Insurance Coverage Rate",
75
+ "visualizationType": "TP5 Waffle",
76
+ "visualizationSubType": "linear",
77
+ "showPercent": true,
78
+ "showDenominator": false,
79
+ "valueDescription": "",
80
+ "content": "completed recommended cancer screenings including mammography, colonoscopy, and cervical cancer screening",
81
+ "subtext": "",
82
+ "dataColumn": "Insured Rate",
83
+ "dataFunction": "Mean (Average)",
84
+ "customDenom": false,
85
+ "dataDenom": "100",
86
+ "suffix": "%",
87
+ "roundToPlace": "1",
88
+ "theme": "theme-teal",
89
+ "shape": "person",
90
+ "visual": {
91
+ "whiteBackground": false
92
+ },
93
+ "showTitle": true,
94
+ "overallFontSize": "medium"
95
+ },
96
+ "waffle3": {
97
+ "uid": "waffle3",
98
+ "type": "waffle-chart",
99
+ "title": "Cancer Screening Completion",
100
+ "visualizationType": "TP5 Waffle",
101
+ "visualizationSubType": "linear",
102
+ "showPercent": true,
103
+ "showDenominator": false,
104
+ "valueDescription": "",
105
+ "content": "completed recommended cancer screenings including mammography, colonoscopy, and cervical cancer screening",
106
+ "subtext": "Data from National Health Interview Survey 2024",
107
+ "dataColumn": "Screening Rate",
108
+ "dataFunction": "Mean (Average)",
109
+ "customDenom": false,
110
+ "dataDenom": "100",
111
+ "suffix": "%",
112
+ "roundToPlace": "1",
113
+ "theme": "theme-purple",
114
+ "shape": "circle",
115
+ "visual": {
116
+ "whiteBackground": false
117
+ },
118
+ "showTitle": true,
119
+ "overallFontSize": "medium"
120
+ },
121
+ "gauge1": {
122
+ "uid": "gauge1",
123
+ "type": "waffle-chart",
124
+ "title": "Vaccination Coverage",
125
+ "visualizationType": "TP5 Gauge",
126
+ "visualizationSubType": "linear",
127
+ "showPercent": true,
128
+ "showDenominator": false,
129
+ "valueDescription": "",
130
+ "content": "of the population is vaccinated against seasonal flu",
131
+ "subtext": "Based on 2024 CDC surveillance data across all age groups",
132
+ "dataColumn": "Vaccination Rate",
133
+ "dataFunction": "Mean (Average)",
134
+ "customDenom": false,
135
+ "dataDenom": "100",
136
+ "suffix": "%",
137
+ "roundToPlace": "1",
138
+ "theme": "theme-blue",
139
+ "gauge": {
140
+ "height": 35,
141
+ "width": "100%"
142
+ },
143
+ "visual": {
144
+ "whiteBackground": false
145
+ },
146
+ "showTitle": true,
147
+ "overallFontSize": "medium"
148
+ },
149
+ "gauge2": {
150
+ "uid": "gauge2",
151
+ "type": "waffle-chart",
152
+ "title": "Health Insurance Coverage Rate",
153
+ "visualizationType": "TP5 Gauge",
154
+ "visualizationSubType": "linear",
155
+ "showPercent": true,
156
+ "showDenominator": false,
157
+ "valueDescription": "",
158
+ "content": "",
159
+ "subtext": "",
160
+ "dataColumn": "Insured Rate",
161
+ "dataFunction": "Mean (Average)",
162
+ "customDenom": false,
163
+ "dataDenom": "100",
164
+ "suffix": "%",
165
+ "roundToPlace": "1",
166
+ "theme": "theme-teal",
167
+ "gauge": {
168
+ "height": 35,
169
+ "width": "100%"
170
+ },
171
+ "visual": {
172
+ "whiteBackground": false
173
+ },
174
+ "showTitle": true,
175
+ "overallFontSize": "medium"
176
+ },
177
+ "gauge3": {
178
+ "uid": "gauge3",
179
+ "type": "waffle-chart",
180
+ "title": "Cancer Screening Completion",
181
+ "visualizationType": "TP5 Gauge",
182
+ "visualizationSubType": "linear",
183
+ "showPercent": true,
184
+ "showDenominator": false,
185
+ "valueDescription": "",
186
+ "content": "completed recommended cancer screenings including mammography, colonoscopy, and cervical cancer screening",
187
+ "subtext": "Data from National Health Interview Survey 2024",
188
+ "dataColumn": "Screening Rate",
189
+ "dataFunction": "Mean (Average)",
190
+ "customDenom": false,
191
+ "dataDenom": "100",
192
+ "suffix": "%",
193
+ "roundToPlace": "1",
194
+ "theme": "theme-purple",
195
+ "gauge": {
196
+ "height": 35,
197
+ "width": "100%"
198
+ },
199
+ "visual": {
200
+ "whiteBackground": false
201
+ },
202
+ "showTitle": true,
203
+ "overallFontSize": "medium"
204
+ },
205
+ "bite1": {
206
+ "uid": "bite1",
207
+ "type": "data-bite",
208
+ "title": "Vaccination Coverage",
209
+ "biteStyle": "tp5",
210
+ "dataColumn": "Vaccination Rate",
211
+ "dataFunction": "Mean (Average)",
212
+ "biteBody": "of the population is vaccinated against seasonal flu",
213
+ "subtext": "Based on 2024 CDC surveillance data across all age groups",
214
+ "dataFormat": {
215
+ "roundToPlace": 1,
216
+ "commas": true,
217
+ "prefix": "",
218
+ "suffix": "%"
219
+ },
220
+ "theme": "theme-blue",
221
+ "visual": {
222
+ "hideBackgroundColor": false
223
+ }
224
+ },
225
+ "bite2": {
226
+ "uid": "bite2",
227
+ "type": "data-bite",
228
+ "title": "Health Insurance Coverage Rate",
229
+ "biteStyle": "tp5",
230
+ "dataColumn": "Insured Rate",
231
+ "dataFunction": "Mean (Average)",
232
+ "biteBody": "",
233
+ "subtext": "",
234
+ "dataFormat": {
235
+ "roundToPlace": 1,
236
+ "commas": true,
237
+ "prefix": "",
238
+ "suffix": "%"
239
+ },
240
+ "theme": "theme-teal",
241
+ "visual": {
242
+ "hideBackgroundColor": false
243
+ }
244
+ },
245
+ "bite3": {
246
+ "uid": "bite3",
247
+ "type": "data-bite",
248
+ "title": "Cancer Screening Completion",
249
+ "biteStyle": "tp5",
250
+ "dataColumn": "Screening Rate",
251
+ "dataFunction": "Mean (Average)",
252
+ "biteBody": "completed recommended cancer screenings including mammography, colonoscopy, and cervical cancer screening",
253
+ "subtext": "Data from National Health Interview Survey 2024",
254
+ "dataFormat": {
255
+ "roundToPlace": 1,
256
+ "commas": true,
257
+ "prefix": "",
258
+ "suffix": "%"
259
+ },
260
+ "theme": "theme-purple",
261
+ "visual": {
262
+ "hideBackgroundColor": false
263
+ }
264
+ }
265
+ }
266
+ }
267
+
@@ -1,10 +1,12 @@
1
1
  import React from 'react'
2
2
  import MultiSelect from '@cdc/core/components/MultiSelect'
3
+ import ComboBox from '@cdc/core/components/ComboBox'
3
4
  import { SharedFilter } from '../../types/SharedFilter'
4
5
  import { APIFilterDropdowns, DropdownOptions } from './DashboardFiltersWrapper'
5
6
  import { FILTER_STYLE } from '../../types/FilterStyles'
6
7
  import { NestedOptions, ValueTextPair } from '@cdc/core/components/NestedDropdown/nestedDropdownHelpers'
7
8
  import NestedDropdown from '@cdc/core/components/NestedDropdown'
9
+ import { getNestedOptions } from '@cdc/core/components/Filters/helpers/getNestedOptions'
8
10
  import { MouseEventHandler } from 'react'
9
11
  import Loader from '@cdc/core/components/Loader'
10
12
  import _ from 'lodash'
@@ -56,14 +58,12 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
56
58
 
57
59
  return (
58
60
  <form className='d-flex flex-wrap'>
59
- {sharedFilters.map((filter, filterIndex) => {
61
+ {show.map(filterIndex => {
62
+ const filter = sharedFilters[filterIndex]
60
63
  const urlFilterType = filter.type === 'urlfilter'
61
64
  const label = stripDuplicateLabelIncrement(filter.key || '')
62
65
 
63
- if (
64
- (!urlFilterType && !filter.showDropdown && filter.filterStyle !== FILTER_STYLE.nestedDropdown) ||
65
- (show && !show.includes(filterIndex))
66
- )
66
+ if (!urlFilterType && !filter.showDropdown && filter.filterStyle !== FILTER_STYLE.nestedDropdown)
67
67
  return <React.Fragment key={`${filter.key}-filtersection-${filterIndex}-option`} />
68
68
  const values: JSX.Element[] = []
69
69
 
@@ -71,12 +71,11 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
71
71
  const loading = apiFilterDropdowns[_key] === null
72
72
 
73
73
  const multiValues: { value; label }[] = []
74
- const nestedOptions: NestedOptions = Object.entries(filter?.subGrouping?.valuesLookup || {}).map(
75
- ([key, data]) => [
76
- [key, key], // Main option: [value, text]
77
- Array.isArray(data?.values) ? data.values.map(value => [value, value]) : [] // Ensure `values` is an array
78
- ]
79
- )
74
+ const nestedOptions: NestedOptions = getNestedOptions({
75
+ orderedValues: filter.orderedValues,
76
+ values: filter.values,
77
+ subGrouping: filter.subGrouping
78
+ })
80
79
 
81
80
  if (_key && apiFilterDropdowns[_key]) {
82
81
  // URL Filter
@@ -154,6 +153,16 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
154
153
  handleSelectedItems={value => updateField(null, null, filterIndex, value)}
155
154
  loading={loading}
156
155
  />
156
+ ) : filter.filterStyle === FILTER_STYLE.combobox ? (
157
+ <ComboBox
158
+ options={multiValues}
159
+ fieldName={filterIndex}
160
+ updateField={updateField}
161
+ selected={(filter.queuedActive || filter.active) as string}
162
+ label={label}
163
+ loading={loading}
164
+ placeholder={filter.resetLabel || '- Select -'}
165
+ />
157
166
  ) : (
158
167
  <>
159
168
  <select
@@ -21,6 +21,7 @@ import { addValuesToDashboardFilters } from '../../../helpers/addValuesToDashboa
21
21
  import { FILTER_STYLE } from '../../../types/FilterStyles'
22
22
  import { handleSorting } from '@cdc/core/components/Filters'
23
23
  import { removeDashboardFilter } from '../../../helpers/removeDashboardFilter'
24
+ import { DragDropContext, Droppable, Draggable, DropResult } from '@hello-pangea/dnd'
24
25
 
25
26
  type DashboardFitlersEditorProps = {
26
27
  vizConfig: DashboardFilters
@@ -51,6 +52,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
51
52
 
52
53
  const openControls = useState({})
53
54
  const [canAddExisting, setCanAddExisting] = useState(false)
55
+ const [isNestedDragHovered, setIsNestedDragHovered] = useState(false)
54
56
 
55
57
  const updateFilterProp = (prop: string, index: number, value) => {
56
58
  const newSharedFilters = _.cloneDeep(sharedFilters)
@@ -71,6 +73,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
71
73
  newSharedFilters[index][prop] = value
72
74
  if (prop === 'columnName') {
73
75
  if (newSharedFilters[index].subGrouping) delete newSharedFilters[index].subGrouping
76
+ newSharedFilters[index].defaultValue = ''
74
77
  // changing a data column and want to load the data into the preview options
75
78
  const sharedFiltersWithValues = addValuesToDashboardFilters(newSharedFilters, data)
76
79
  dispatch({ type: 'SET_SHARED_FILTERS', payload: sharedFiltersWithValues })
@@ -122,6 +125,29 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
122
125
  dispatch({ type: 'SET_CONFIG', payload: { dashboard, visualizations: newVisualizations } })
123
126
  }
124
127
 
128
+ const handleFilterReorder = (result: DropResult) => {
129
+ const { source, destination } = result
130
+ if (!destination || source.index === destination.index) return
131
+
132
+ const newIndexes = [...vizConfig.sharedFilterIndexes]
133
+ const [movedIndex] = newIndexes.splice(source.index, 1)
134
+ newIndexes.splice(destination.index, 0, movedIndex)
135
+
136
+ updateConfig({
137
+ ...vizConfig,
138
+ sharedFilterIndexes: newIndexes
139
+ })
140
+ }
141
+
142
+ const getItemStyle = (isDragging, draggableStyle) => ({
143
+ ...draggableStyle,
144
+ ...(isDragging && sortableItemStyles)
145
+ })
146
+
147
+ const sortableItemStyles = {
148
+ background: 'rgba(0, 0, 0, 0.1)'
149
+ }
150
+
125
151
  const addNewFilter = () => {
126
152
  const _sharedFilters = _.cloneDeep(sharedFilters) || []
127
153
  const columnName = 'New Dashboard Filter ' + (_sharedFilters.length + 1)
@@ -219,44 +245,72 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
219
245
  <AccordionItemButton>Filters</AccordionItemButton>
220
246
  </AccordionItemHeading>
221
247
  <AccordionItemPanel>
222
- {vizConfig.sharedFilterIndexes.map(index => {
223
- const filter = sharedFilters[index]
224
- return (
225
- <FieldSetWrapper
226
- key={filter.key + index}
227
- fieldName={filter.key}
228
- fieldKey={index}
229
- fieldType='Dashboard Filter'
230
- controls={openControls}
231
- deleteField={() => {
232
- overlay?.actions.openOverlay(
233
- <DeleteFilterModal
234
- removeFilterCompletely={removeFilter}
235
- removeFilterFromViz={index => {
236
- updateConfig({
237
- ...vizConfig,
238
- sharedFilterIndexes: vizConfig.sharedFilterIndexes.filter(i => i !== index)
239
- })
240
- }}
241
- filterIndex={index}
242
- />
243
- )
244
- }}
245
- >
246
- <FilterEditor
247
- filter={filter}
248
- filterIndex={index}
249
- updateFilterProp={(name, value) => {
250
- updateFilterProp(name, index, value)
251
- }}
252
- toggleNestedQueryParameters={checked => {
253
- toggleNestedQueryParameters(index, checked)
254
- }}
255
- config={config}
256
- />
257
- </FieldSetWrapper>
258
- )
259
- })}
248
+ <DragDropContext onDragEnd={handleFilterReorder}>
249
+ <Droppable droppableId='dashboard_filters_list'>
250
+ {provided => (
251
+ <ul {...provided.droppableProps} ref={provided.innerRef} className='draggable-field-list'>
252
+ {vizConfig.sharedFilterIndexes.map((index, filterIndex) => {
253
+ const filter = sharedFilters[index]
254
+ return (
255
+ <Draggable
256
+ key={filter.key + index}
257
+ draggableId={`filter-${filter.key}-${index}`}
258
+ index={filterIndex}
259
+ isDragDisabled={isNestedDragHovered}
260
+ >
261
+ {(provided, snapshot) => (
262
+ <div
263
+ ref={provided.innerRef}
264
+ {...provided.draggableProps}
265
+ {...provided.dragHandleProps}
266
+ style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
267
+ className={snapshot.isDragging ? 'currently-dragging' : ''}
268
+ >
269
+ <FieldSetWrapper
270
+ key={filter.key + index}
271
+ fieldName={filter.key}
272
+ fieldKey={index}
273
+ fieldType='Dashboard Filter'
274
+ controls={openControls}
275
+ draggable={true}
276
+ deleteField={() => {
277
+ overlay?.actions.openOverlay(
278
+ <DeleteFilterModal
279
+ removeFilterCompletely={removeFilter}
280
+ removeFilterFromViz={index => {
281
+ updateConfig({
282
+ ...vizConfig,
283
+ sharedFilterIndexes: vizConfig.sharedFilterIndexes.filter(i => i !== index)
284
+ })
285
+ }}
286
+ filterIndex={index}
287
+ />
288
+ )
289
+ }}
290
+ >
291
+ <FilterEditor
292
+ filter={filter}
293
+ filterIndex={index}
294
+ updateFilterProp={(name, value) => {
295
+ updateFilterProp(name, index, value)
296
+ }}
297
+ toggleNestedQueryParameters={checked => {
298
+ toggleNestedQueryParameters(index, checked)
299
+ }}
300
+ config={config}
301
+ onNestedDragAreaHover={setIsNestedDragHovered}
302
+ />
303
+ </FieldSetWrapper>
304
+ </div>
305
+ )}
306
+ </Draggable>
307
+ )
308
+ })}
309
+ {provided.placeholder}
310
+ </ul>
311
+ )}
312
+ </Droppable>
313
+ </DragDropContext>
260
314
  <button onClick={addNewFilter} className='btn btn-primary full-width'>
261
315
  Add Filter
262
316
  </button>
@@ -27,6 +27,7 @@ type FilterEditorProps = {
27
27
  filterIndex: number
28
28
  updateFilterProp: (name: keyof SharedFilter, value: any) => void
29
29
  toggleNestedQueryParameters: (checked: boolean) => void
30
+ onNestedDragAreaHover?: (isHovering: boolean) => void
30
31
  }
31
32
 
32
33
  const FilterEditor: React.FC<FilterEditorProps> = ({
@@ -34,7 +35,8 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
34
35
  filterIndex,
35
36
  config,
36
37
  updateFilterProp,
37
- toggleNestedQueryParameters
38
+ toggleNestedQueryParameters,
39
+ onNestedDragAreaHover
38
40
  }) => {
39
41
  const [columns, setColumns] = useState<string[]>([])
40
42
  const [dataFiltersLoading, setDataFiltersLoading] = useState(false)
@@ -43,7 +45,11 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
43
45
  const filterStyles = Object.values(FILTER_STYLE)
44
46
 
45
47
  const parentFilters: string[] = (config.dashboard.sharedFilters || [])
46
- .filter(({ key, type }) => key !== filter.key && type !== 'datafilter')
48
+ .filter(({ key }) => key !== filter.key)
49
+ .map(({ key }) => key)
50
+
51
+ const dataFilterParents: string[] = (config.dashboard.sharedFilters || [])
52
+ .filter(({ key }) => key !== filter.key)
47
53
  .map(({ key }) => key)
48
54
 
49
55
  const vizRowColumnLocator = getVizRowColumnLocator(config.rows)
@@ -246,21 +252,6 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
246
252
  <>
247
253
  {!hasDashboardApplyBehavior(config.visualizations) && (
248
254
  <>
249
- <Select
250
- label='URL to Filter'
251
- value={filter.datasetKey || ''}
252
- options={[
253
- { value: '', label: '- Select Option -' },
254
- ...Object.keys(config.datasets)
255
- .filter(datasetKey => config.datasets[datasetKey].dataUrl)
256
- .map(datasetKey => ({
257
- value: datasetKey,
258
- label: config.datasets[datasetKey].dataUrl
259
- }))
260
- ]}
261
- onChange={e => updateFilterProp('datasetKey', e.target.value)}
262
- />
263
-
264
255
  <Select
265
256
  label='Filter By'
266
257
  value={filter.filterBy || ''}
@@ -271,6 +262,40 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
271
262
  ]}
272
263
  onChange={e => updateFilterProp('filterBy', e.target.value)}
273
264
  />
265
+
266
+ {filter.filterBy === 'File Name' && (
267
+ <Select
268
+ label='URL to Filter'
269
+ value={filter.datasetKey || ''}
270
+ options={[
271
+ { value: '', label: '- Select Option -' },
272
+ ...Object.keys(config.datasets)
273
+ .filter(datasetKey => config.datasets[datasetKey].dataUrl)
274
+ .map(datasetKey => ({
275
+ value: datasetKey,
276
+ label: config.datasets[datasetKey].dataUrl
277
+ }))
278
+ ]}
279
+ onChange={e => updateFilterProp('datasetKey', e.target.value)}
280
+ tooltip={
281
+ <Tooltip style={{ textTransform: 'none' }}>
282
+ <Tooltip.Target>
283
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
284
+ </Tooltip.Target>
285
+ <Tooltip.Content>
286
+ <p>Select which dataset URL's filename should be modified by this filter.</p>
287
+ </Tooltip.Content>
288
+ </Tooltip>
289
+ }
290
+ />
291
+ )}
292
+
293
+ {filter.filterBy === 'Query String' && filter.usedBy && filter.usedBy.length > 0 && (
294
+ <div className='bg-info-subtle p-2 my-2' style={{ fontSize: '0.9em' }}>
295
+ <Icon display='info' style={{ marginRight: '0.5rem' }} />
296
+ Will apply to datasets used by selected widgets
297
+ </div>
298
+ )}
274
299
  {filter.filterBy === 'File Name' && (
275
300
  <>
276
301
  <TextField
@@ -523,6 +548,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
523
548
  values.splice(index2, 0, removed)
524
549
  updateFilterProp('orderedValues', values)
525
550
  }}
551
+ onNestedDragAreaHover={onNestedDragAreaHover}
526
552
  />
527
553
  )}
528
554
 
@@ -546,6 +572,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
546
572
  }}
547
573
  isDashboard={true}
548
574
  config={config}
575
+ onNestedDragAreaHover={onNestedDragAreaHover}
549
576
  />
550
577
  <label>
551
578
  <input
@@ -617,19 +644,18 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
617
644
  updateField={(_section, _subSection, _key, value) => updateFilterProp('resetLabel', value)}
618
645
  />
619
646
 
620
- <Select
621
- label='Parent Filter'
622
- value={filter.parents || ''}
623
- options={[
624
- { value: '', label: 'Select a filter' },
625
- ...(config.dashboard.sharedFilters || [])
626
- .filter(sharedFilter => sharedFilter.key !== filter.key)
627
- .map(sharedFilter => ({ value: sharedFilter.key, label: sharedFilter.key }))
628
- ]}
629
- onChange={e => {
630
- updateFilterProp('parents', e.target.value)
631
- }}
632
- />
647
+ <label>
648
+ <span className='edit-label column-heading mt-1'>Parent Filter(s): </span>
649
+ <MultiSelect
650
+ label='Parent Filter(s): '
651
+ options={dataFilterParents.map(key => ({ value: key, label: key }))}
652
+ fieldName='parents'
653
+ selected={filter.parents}
654
+ updateField={(_section, _subsection, _fieldname, newItems) => {
655
+ updateFilterProp('parents', newItems)
656
+ }}
657
+ />
658
+ </label>
633
659
 
634
660
  {!isNestedDropdown && (
635
661
  <TextField