@cdc/dashboard 4.25.10 → 4.26.1

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 (86) hide show
  1. package/Dynamic_Data.md +66 -0
  2. package/dist/{cdcdashboard-fce76882.es.js → cdcdashboard-BnB1QM5d.es.js} +6 -13
  3. package/dist/{cdcdashboard-c55ac1ea.es.js → cdcdashboard-D6CG2-Hb.es.js} +5 -12
  4. package/dist/{cdcdashboard-31a33da1.es.js → cdcdashboard-MXgURbdZ.es.js} +6 -13
  5. package/dist/{cdcdashboard-1a1724a1.es.js → cdcdashboard-dgT_1dIT.es.js} +136 -151
  6. package/dist/cdcdashboard.js +84214 -79641
  7. package/examples/api-dashboard-data.json +272 -0
  8. package/examples/api-dashboard-years.json +11 -0
  9. package/examples/api-geographies-data.json +11 -0
  10. package/examples/api-test/categories.json +18 -0
  11. package/examples/api-test/chart-data.json +602 -0
  12. package/examples/api-test/topics.json +47 -0
  13. package/examples/api-test/years.json +22 -0
  14. package/examples/markup-axis-label.json +4167 -0
  15. package/examples/private/big-dashboard.json +39095 -39077
  16. package/examples/private/cat-y.json +1235 -0
  17. package/examples/private/chronic-dash.json +1584 -0
  18. package/examples/private/clade-2.json +430 -0
  19. package/examples/private/diabetes.json +546 -196
  20. package/examples/private/map-issue.json +2260 -0
  21. package/examples/private/markup-footer/mortality-deaths-footnotes-age.csv +3 -0
  22. package/examples/private/mpinc-state-reports.json +2260 -0
  23. package/examples/private/mpox.json +38128 -0
  24. package/examples/private/nwss/rsv.json +1240 -0
  25. package/examples/private/reset.json +32920 -0
  26. package/examples/private/simple-dash.json +490 -0
  27. package/examples/private/test-dash.json +0 -0
  28. package/examples/private/test123.json +491 -0
  29. package/examples/test-api-filter-reset.json +132 -0
  30. package/examples/test-dashboard-simple.json +503 -0
  31. package/index.html +25 -26
  32. package/package.json +11 -11
  33. package/src/CdcDashboardComponent.tsx +35 -10
  34. package/src/DashboardContext.tsx +3 -1
  35. package/src/_stories/Dashboard.DataSetup.stories.tsx +203 -0
  36. package/src/_stories/Dashboard.stories.tsx +402 -1
  37. package/src/_stories/_mock/custom-order-new-values.json +116 -0
  38. package/src/_stories/_mock/filter-cascade.json +3350 -0
  39. package/src/_stories/_mock/gallery-data-bite-dashboard.json +3500 -0
  40. package/src/_stories/_mock/nested-parent-child-filters.json +392 -0
  41. package/src/_stories/_mock/parent-child-filters.json +233 -0
  42. package/src/components/DashboardFilters/DashboardFilters.tsx +54 -31
  43. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +118 -50
  44. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +96 -108
  45. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +196 -59
  46. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +129 -29
  47. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +62 -3
  48. package/src/components/DataDesignerModal.tsx +18 -6
  49. package/src/components/Header/Header.tsx +53 -21
  50. package/src/components/Toggle/Toggle.tsx +48 -48
  51. package/src/components/VisualizationRow.tsx +73 -6
  52. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +2 -3
  53. package/src/components/Widget/Widget.tsx +1 -1
  54. package/src/data/initial-state.js +1 -0
  55. package/src/helpers/addValuesToDashboardFilters.ts +24 -6
  56. package/src/helpers/apiFilterHelpers.ts +26 -2
  57. package/src/helpers/changeFilterActive.ts +67 -65
  58. package/src/helpers/filterData.ts +52 -7
  59. package/src/helpers/filterResetHelpers.ts +102 -0
  60. package/src/helpers/formatConfigBeforeSave.ts +6 -5
  61. package/src/helpers/getUpdateConfig.ts +91 -91
  62. package/src/helpers/getVizConfig.ts +2 -2
  63. package/src/helpers/loadAPIFilters.ts +109 -99
  64. package/src/helpers/tests/filterResetHelpers.test.ts +532 -0
  65. package/src/helpers/tests/updatesChildFilters.test.ts +53 -22
  66. package/src/helpers/updateChildFilters.ts +50 -27
  67. package/src/index.tsx +1 -0
  68. package/src/scss/editor-panel.scss +3 -431
  69. package/src/scss/main.scss +142 -25
  70. package/src/store/errorMessage/errorMessage.reducer.ts +1 -1
  71. package/src/test/CdcDashboard.test.jsx +9 -4
  72. package/src/types/Dashboard.ts +1 -0
  73. package/src/types/DashboardFilters.ts +9 -8
  74. package/src/types/FilterStyles.ts +8 -7
  75. package/src/types/SharedFilter.ts +13 -0
  76. package/LICENSE +0 -201
  77. package/examples/private/DEV-11072.json +0 -7591
  78. package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_data.csv +0 -14041
  79. package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_per_100000_data.csv +0 -14041
  80. package/examples/private/burden_toolkit_mortality_qaly_data.csv +0 -18721
  81. package/examples/private/burden_toolkit_mortality_yll_data.csv +0 -18721
  82. package/examples/private/pedro.json +0 -1
  83. package/src/helpers/getAutoLoadVisualization.ts +0 -11
  84. package/src/scss/mixins.scss +0 -47
  85. package/src/scss/variables.scss +0 -5
  86. /package/dist/{cdcdashboard-548642e6.es.js → cdcdashboard-Ct2SB0vL.es.js} +0 -0
@@ -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'
@@ -18,6 +20,7 @@ type DashboardFilterProps = {
18
20
  showSubmit: boolean
19
21
  applyFilters: MouseEventHandler<HTMLButtonElement>
20
22
  applyFiltersButtonText?: string
23
+ handleReset?: MouseEventHandler<HTMLButtonElement>
21
24
  }
22
25
 
23
26
  const DashboardFilters: React.FC<DashboardFilterProps> = ({
@@ -27,7 +30,8 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
27
30
  handleOnChange,
28
31
  showSubmit,
29
32
  applyFilters,
30
- applyFiltersButtonText
33
+ applyFiltersButtonText,
34
+ handleReset
31
35
  }) => {
32
36
  const nullVal = (filter: SharedFilter) => {
33
37
  const val = filter.queuedActive || filter.active
@@ -54,14 +58,12 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
54
58
 
55
59
  return (
56
60
  <form className='d-flex flex-wrap'>
57
- {sharedFilters.map((filter, filterIndex) => {
61
+ {show.map(filterIndex => {
62
+ const filter = sharedFilters[filterIndex]
58
63
  const urlFilterType = filter.type === 'urlfilter'
59
64
  const label = stripDuplicateLabelIncrement(filter.key || '')
60
65
 
61
- if (
62
- (!urlFilterType && !filter.showDropdown && filter.filterStyle !== FILTER_STYLE.nestedDropdown) ||
63
- (show && !show.includes(filterIndex))
64
- )
66
+ if (!urlFilterType && !filter.showDropdown && filter.filterStyle !== FILTER_STYLE.nestedDropdown)
65
67
  return <React.Fragment key={`${filter.key}-filtersection-${filterIndex}-option`} />
66
68
  const values: JSX.Element[] = []
67
69
 
@@ -69,12 +71,11 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
69
71
  const loading = apiFilterDropdowns[_key] === null
70
72
 
71
73
  const multiValues: { value; label }[] = []
72
- const nestedOptions: NestedOptions = Object.entries(filter?.subGrouping?.valuesLookup || {}).map(
73
- ([key, data]) => [
74
- [key, key], // Main option: [value, text]
75
- Array.isArray(data?.values) ? data.values.map(value => [value, value]) : [] // Ensure `values` is an array
76
- ]
77
- )
74
+ const nestedOptions: NestedOptions = getNestedOptions({
75
+ orderedValues: filter.orderedValues,
76
+ values: filter.values,
77
+ subGrouping: filter.subGrouping
78
+ })
78
79
 
79
80
  if (_key && apiFilterDropdowns[_key]) {
80
81
  // URL Filter
@@ -115,8 +116,8 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
115
116
  }
116
117
 
117
118
  const isDisabled = !values.length
118
- // push reset label only if it does not includes in filter values options
119
- if (filter.resetLabel && !filter.values.includes(filter.resetLabel)) {
119
+ // push reset label only if it does not includes in filter values options
120
+ if (filter.resetLabel && !filter.values.includes(filter.resetLabel) && !_key) {
120
121
  values.unshift(
121
122
  <option key={`${filter.resetLabel}-option`} value={filter.resetLabel}>
122
123
  {filter.resetLabel}
@@ -145,15 +146,23 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
145
146
  ) : filter.filterStyle === FILTER_STYLE.nestedDropdown ? (
146
147
  <NestedDropdown
147
148
  activeGroup={(filter.queuedActive?.[0] || filter.active) as string}
148
- activeSubGroup={
149
- _key ? filter.queuedActive?.[1] || filter.subGrouping?.active : filter.subGrouping?.active
150
- }
149
+ activeSubGroup={(filter.queuedActive?.[1] || filter.subGrouping?.active) as string}
151
150
  filterIndex={filterIndex}
152
151
  options={_key ? getNestedDropdownOptions(apiFilterDropdowns[_key]) : nestedOptions}
153
152
  listLabel={label}
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
@@ -167,7 +176,14 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
167
176
  disabled={loading || isDisabled}
168
177
  >
169
178
  {loading && <option value='Loading...'>Loading...</option>}
170
- {nullVal(filter) && (
179
+ {/* For API filters, show placeholder when no value is selected */}
180
+ {_key && nullVal(filter) && (
181
+ <option key={`reset-label`} value=''>
182
+ {filter.resetLabel || '- Select One -'}
183
+ </option>
184
+ )}
185
+ {/* For non-API filters or when no value is selected, show empty option */}
186
+ {!_key && nullVal(filter) && (
171
187
  <option key={`select`} value=''>
172
188
  {filter.resetLabel || '- Select -'}
173
189
  </option>
@@ -181,19 +197,26 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
181
197
  )
182
198
  })}
183
199
  {showSubmit && (
184
- <button
185
- className='btn btn-primary mb-1'
186
- onClick={applyFilters}
187
- disabled={show.some(filterIndex => {
188
- const emptyFilterValues = [undefined, '', '- Select -']
189
- return (
190
- emptyFilterValues.includes(sharedFilters[filterIndex].queuedActive) &&
191
- emptyFilterValues.includes(sharedFilters[filterIndex].active)
192
- )
193
- })}
194
- >
195
- {applyFiltersButtonText || 'GO!'}
196
- </button>
200
+ <>
201
+ <button
202
+ className='btn btn-primary mb-1 me-2'
203
+ onClick={applyFilters}
204
+ disabled={show.some(filterIndex => {
205
+ const emptyFilterValues = [undefined, '', '- Select -']
206
+ return (
207
+ emptyFilterValues.includes(sharedFilters[filterIndex].queuedActive) &&
208
+ emptyFilterValues.includes(sharedFilters[filterIndex].active)
209
+ )
210
+ })}
211
+ >
212
+ {applyFiltersButtonText || 'GO!'}
213
+ </button>
214
+ {handleReset && (
215
+ <button className='btn btn-link mb-1' onClick={handleReset}>
216
+ Clear Filters
217
+ </button>
218
+ )}
219
+ </>
197
220
  )}
198
221
  </form>
199
222
  )
@@ -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
@@ -43,9 +44,10 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
43
44
  return config.dashboard.sharedFilters
44
45
  ?.map<[number, string]>(({ key }, i) => [i, key])
45
46
  .filter(([filterIndex]) => !sharedFilterIndexes.includes(filterIndex)) // filter out already added filters
46
- .map(([filterIndex, filterName]) => (
47
- <option key={filterIndex} value={filterIndex}>{`${filterIndex} - ${filterName}`}</option>
48
- ))
47
+ .map(([filterIndex, filterName]) => ({
48
+ value: String(filterIndex),
49
+ label: `${filterIndex} - ${filterName}`
50
+ }))
49
51
  }, [config.visualizations, vizConfig.uid])
50
52
 
51
53
  const openControls = useState({})
@@ -70,6 +72,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
70
72
  newSharedFilters[index][prop] = value
71
73
  if (prop === 'columnName') {
72
74
  if (newSharedFilters[index].subGrouping) delete newSharedFilters[index].subGrouping
75
+ newSharedFilters[index].defaultValue = ''
73
76
  // changing a data column and want to load the data into the preview options
74
77
  const sharedFiltersWithValues = addValuesToDashboardFilters(newSharedFilters, data)
75
78
  dispatch({ type: 'SET_SHARED_FILTERS', payload: sharedFiltersWithValues })
@@ -121,6 +124,29 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
121
124
  dispatch({ type: 'SET_CONFIG', payload: { dashboard, visualizations: newVisualizations } })
122
125
  }
123
126
 
127
+ const handleFilterReorder = (result: DropResult) => {
128
+ const { source, destination } = result
129
+ if (!destination || source.index === destination.index) return
130
+
131
+ const newIndexes = [...vizConfig.sharedFilterIndexes]
132
+ const [movedIndex] = newIndexes.splice(source.index, 1)
133
+ newIndexes.splice(destination.index, 0, movedIndex)
134
+
135
+ updateConfig({
136
+ ...vizConfig,
137
+ sharedFilterIndexes: newIndexes
138
+ })
139
+ }
140
+
141
+ const getItemStyle = (isDragging, draggableStyle) => ({
142
+ ...draggableStyle,
143
+ ...(isDragging && sortableItemStyles)
144
+ })
145
+
146
+ const sortableItemStyles = {
147
+ background: 'rgba(0, 0, 0, 0.1)'
148
+ }
149
+
124
150
  const addNewFilter = () => {
125
151
  const _sharedFilters = _.cloneDeep(sharedFilters) || []
126
152
  const columnName = 'New Dashboard Filter ' + (_sharedFilters.length + 1)
@@ -189,6 +215,27 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
189
215
  }
190
216
  />
191
217
  )}
218
+ {vizConfig.filterBehavior === 'Apply Button' && (
219
+ <CheckBox
220
+ label='Show Clear Filters Button'
221
+ value={vizConfig.showClearButton ?? true}
222
+ updateField={(_section, _subsection, _key, value) => {
223
+ updateConfig({ ...vizConfig, showClearButton: value })
224
+ }}
225
+ tooltip={
226
+ <Tooltip style={{ textTransform: 'none' }}>
227
+ <Tooltip.Target>
228
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
229
+ </Tooltip.Target>
230
+ <Tooltip.Content>
231
+ <p>
232
+ When enabled, displays a "Clear Filters" button that allows users to reset all filter selections.
233
+ </p>
234
+ </Tooltip.Content>
235
+ </Tooltip>
236
+ }
237
+ />
238
+ )}
192
239
  </AccordionItemPanel>
193
240
  </AccordionItem>
194
241
 
@@ -197,44 +244,70 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
197
244
  <AccordionItemButton>Filters</AccordionItemButton>
198
245
  </AccordionItemHeading>
199
246
  <AccordionItemPanel>
200
- {vizConfig.sharedFilterIndexes.map(index => {
201
- const filter = sharedFilters[index]
202
- return (
203
- <FieldSetWrapper
204
- key={filter.key + index}
205
- fieldName={filter.key}
206
- fieldKey={index}
207
- fieldType='Dashboard Filter'
208
- controls={openControls}
209
- deleteField={() => {
210
- overlay?.actions.openOverlay(
211
- <DeleteFilterModal
212
- removeFilterCompletely={removeFilter}
213
- removeFilterFromViz={index => {
214
- updateConfig({
215
- ...vizConfig,
216
- sharedFilterIndexes: vizConfig.sharedFilterIndexes.filter(i => i !== index)
217
- })
218
- }}
219
- filterIndex={index}
220
- />
221
- )
222
- }}
223
- >
224
- <FilterEditor
225
- filter={filter}
226
- filterIndex={index}
227
- updateFilterProp={(name, value) => {
228
- updateFilterProp(name, index, value)
229
- }}
230
- toggleNestedQueryParameters={checked => {
231
- toggleNestedQueryParameters(index, checked)
232
- }}
233
- config={config}
234
- />
235
- </FieldSetWrapper>
236
- )
237
- })}
247
+ <DragDropContext onDragEnd={handleFilterReorder}>
248
+ <Droppable droppableId='dashboard_filters_list'>
249
+ {provided => (
250
+ <ul {...provided.droppableProps} ref={provided.innerRef} className='draggable-field-list'>
251
+ {vizConfig.sharedFilterIndexes.map((index, filterIndex) => {
252
+ const filter = sharedFilters[index]
253
+ return (
254
+ <Draggable
255
+ key={filter.key + index}
256
+ draggableId={`filter-${filter.key}-${index}`}
257
+ index={filterIndex}
258
+ >
259
+ {(provided, snapshot) => (
260
+ <div
261
+ ref={provided.innerRef}
262
+ {...provided.draggableProps}
263
+ {...provided.dragHandleProps}
264
+ style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
265
+ className={snapshot.isDragging ? 'currently-dragging' : ''}
266
+ >
267
+ <FieldSetWrapper
268
+ key={filter.key + index}
269
+ fieldName={filter.key}
270
+ fieldKey={index}
271
+ fieldType='Dashboard Filter'
272
+ controls={openControls}
273
+ draggable={true}
274
+ deleteField={() => {
275
+ overlay?.actions.openOverlay(
276
+ <DeleteFilterModal
277
+ removeFilterCompletely={removeFilter}
278
+ removeFilterFromViz={index => {
279
+ updateConfig({
280
+ ...vizConfig,
281
+ sharedFilterIndexes: vizConfig.sharedFilterIndexes.filter(i => i !== index)
282
+ })
283
+ }}
284
+ filterIndex={index}
285
+ />
286
+ )
287
+ }}
288
+ >
289
+ <FilterEditor
290
+ filter={filter}
291
+ filterIndex={index}
292
+ updateFilterProp={(name, value) => {
293
+ updateFilterProp(name, index, value)
294
+ }}
295
+ toggleNestedQueryParameters={checked => {
296
+ toggleNestedQueryParameters(index, checked)
297
+ }}
298
+ config={config}
299
+ />
300
+ </FieldSetWrapper>
301
+ </div>
302
+ )}
303
+ </Draggable>
304
+ )
305
+ })}
306
+ {provided.placeholder}
307
+ </ul>
308
+ )}
309
+ </Droppable>
310
+ </DragDropContext>
238
311
  <button onClick={addNewFilter} className='btn btn-primary full-width'>
239
312
  Add Filter
240
313
  </button>
@@ -254,8 +327,10 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
254
327
  </Tooltip.Content>
255
328
  </Tooltip>
256
329
  </span>
257
- <select
330
+ <Select
331
+ label=''
258
332
  value={''}
333
+ options={[{ value: '', label: 'Select' }, ...(existingOptions || [])]}
259
334
  onChange={e => {
260
335
  updateConfig({
261
336
  ...vizConfig,
@@ -263,14 +338,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
263
338
  })
264
339
  setCanAddExisting(false)
265
340
  }}
266
- >
267
- {[
268
- <option key='select' value=''>
269
- Select
270
- </option>,
271
- ...existingOptions
272
- ]}
273
- </select>
341
+ />
274
342
  </label>
275
343
  ) : (
276
344
  <button onClick={() => setCanAddExisting(true)} className='btn btn-primary full-width mt-2'>
@@ -43,7 +43,11 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
43
43
  const filterStyles = Object.values(FILTER_STYLE)
44
44
 
45
45
  const parentFilters: string[] = (config.dashboard.sharedFilters || [])
46
- .filter(({ key, type }) => key !== filter.key && type !== 'datafilter')
46
+ .filter(({ key }) => key !== filter.key)
47
+ .map(({ key }) => key)
48
+
49
+ const dataFilterParents: string[] = (config.dashboard.sharedFilters || [])
50
+ .filter(({ key }) => key !== filter.key)
47
51
  .map(({ key }) => key)
48
52
 
49
53
  const vizRowColumnLocator = getVizRowColumnLocator(config.rows)
@@ -184,33 +188,25 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
184
188
  return (
185
189
  <>
186
190
  {dataFiltersLoading && <Loading />}
187
- <label>
188
- <span className='edit-label column-heading'>Filter Type: </span>
189
- <select
190
- defaultValue={filter.type || ''}
191
- onChange={e => selectFilterType(e.target.value)}
192
- disabled={!!filter.type}
193
- >
194
- <option value=''>- Select Option -</option>
195
- <option value='urlfilter'>URL</option>
196
- <option value='datafilter'>Data</option>
197
- </select>
198
- </label>
191
+ <Select
192
+ label='Filter Type'
193
+ value={filter.type || ''}
194
+ options={[
195
+ { value: '', label: '- Select Option -' },
196
+ { value: 'urlfilter', label: 'URL' },
197
+ { value: 'datafilter', label: 'Data' }
198
+ ]}
199
+ onChange={e => selectFilterType(e.target.value)}
200
+ disabled={!!filter.type}
201
+ />
199
202
  {filter.type !== undefined && (
200
203
  <>
201
- <label>
202
- <span className='edit-label column-heading'>Filter Style: </span>
203
- <select
204
- value={filter.filterStyle || FILTER_STYLE.dropdown}
205
- onChange={e => updateFilterProp('filterStyle', e.target.value)}
206
- >
207
- {filterStyles.map(dataKey => (
208
- <option value={dataKey} key={`filter-style-select-item-${dataKey}`}>
209
- {dataKey}
210
- </option>
211
- ))}
212
- </select>
213
- </label>
204
+ <Select
205
+ label='Filter Style'
206
+ value={filter.filterStyle || FILTER_STYLE.dropdown}
207
+ options={filterStyles}
208
+ onChange={e => updateFilterProp('filterStyle', e.target.value)}
209
+ />
214
210
  {filter.filterStyle === FILTER_STYLE.dropdown && (
215
211
  <label>
216
212
  <span className='me-1'>Show Dropdown</span>
@@ -254,41 +250,50 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
254
250
  <>
255
251
  {!hasDashboardApplyBehavior(config.visualizations) && (
256
252
  <>
257
- <label>
258
- <span className='edit-label column-heading'>URL to Filter: </span>
259
- <select
260
- defaultValue={filter.datasetKey || ''}
253
+ <Select
254
+ label='Filter By'
255
+ value={filter.filterBy || ''}
256
+ options={[
257
+ { value: '', label: '- Select Option -' },
258
+ { value: 'Query String', label: 'Query String' },
259
+ { value: 'File Name', label: 'File Name' }
260
+ ]}
261
+ onChange={e => updateFilterProp('filterBy', e.target.value)}
262
+ />
263
+
264
+ {filter.filterBy === 'File Name' && (
265
+ <Select
266
+ label='URL to Filter'
267
+ value={filter.datasetKey || ''}
268
+ options={[
269
+ { value: '', label: '- Select Option -' },
270
+ ...Object.keys(config.datasets)
271
+ .filter(datasetKey => config.datasets[datasetKey].dataUrl)
272
+ .map(datasetKey => ({
273
+ value: datasetKey,
274
+ label: config.datasets[datasetKey].dataUrl
275
+ }))
276
+ ]}
261
277
  onChange={e => updateFilterProp('datasetKey', e.target.value)}
262
- >
263
- <option value=''>- Select Option -</option>
264
- {Object.keys(config.datasets).map(datasetKey => {
265
- if (config.datasets[datasetKey].dataUrl) {
266
- return (
267
- <option key={datasetKey} value={datasetKey}>
268
- {config.datasets[datasetKey].dataUrl}
269
- </option>
270
- )
271
- }
272
- return null
273
- })}
274
- </select>
275
- </label>
278
+ tooltip={
279
+ <Tooltip style={{ textTransform: 'none' }}>
280
+ <Tooltip.Target>
281
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
282
+ </Tooltip.Target>
283
+ <Tooltip.Content>
284
+ <p>Select which dataset URL's filename should be modified by this filter.</p>
285
+ </Tooltip.Content>
286
+ </Tooltip>
287
+ }
288
+ />
289
+ )}
276
290
 
277
- <label>
278
- <span className='edit-label column-heading'>Filter By: </span>
279
- <select
280
- defaultValue={filter.filterBy || ''}
281
- onChange={e => updateFilterProp('filterBy', e.target.value)}
282
- >
283
- <option value=''>- Select Option -</option>
284
- <option key={'query-string'} value={'Query String'}>
285
- Query String
286
- </option>
287
- <option key={'file-name'} value={'File Name'}>
288
- File Name
289
- </option>
290
- </select>
291
- </label>
291
+ {filter.filterBy === 'Query String' && filter.usedBy && filter.usedBy.length > 0 && (
292
+ <div className='bg-info-subtle p-2 my-2' style={{ fontSize: '0.9em' }}>
293
+ <Icon display='info' style={{ marginRight: '0.5rem' }} />
294
+ Will apply to datasets used by selected widgets
295
+ </div>
296
+ )}
292
297
  {filter.filterBy === 'File Name' && (
293
298
  <>
294
299
  <TextField
@@ -307,9 +312,16 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
307
312
  }
308
313
  />
309
314
 
310
- <label>
311
- <span className='edit-label column-heading'>
312
- White Space Replacments
315
+ <Select
316
+ label='White Space Replacments'
317
+ value={filter.whitespaceReplacement || 'Keep Spaces'}
318
+ options={[
319
+ { value: 'Remove Spaces', label: 'Remove Spaces' },
320
+ { value: 'Replace With Underscore', label: 'Replace With Underscore' },
321
+ { value: 'Keep Spaces', label: 'Keep Spaces' }
322
+ ]}
323
+ onChange={e => updateFilterProp('whitespaceReplacement', e.target.value)}
324
+ tooltip={
313
325
  <Tooltip style={{ textTransform: 'none' }}>
314
326
  <Tooltip.Target>
315
327
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
@@ -318,22 +330,8 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
318
330
  <p>{`Set how whitespace characters will be handled in the file request`}</p>
319
331
  </Tooltip.Content>
320
332
  </Tooltip>
321
- </span>
322
- <select
323
- defaultValue={filter.whitespaceReplacement || 'Keep Spaces'}
324
- onChange={e => updateFilterProp('whitespaceReplacement', e.target.value)}
325
- >
326
- <option key={'remove-spaces'} value={'Remove Spaces'}>
327
- Remove Spaces
328
- </option>
329
- <option key={'replace-with-underscore'} value={'Replace With Underscore'}>
330
- Replace With Underscore
331
- </option>
332
- <option key={'keep-spaces'} value={'Keep Spaces'}>
333
- Keep Spaces
334
- </option>
335
- </select>
336
- </label>
333
+ }
334
+ />
337
335
  </>
338
336
  )}
339
337
  </>
@@ -507,22 +505,17 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
507
505
  <>
508
506
  {filter.filterStyle !== FILTER_STYLE.nestedDropdown ? (
509
507
  <>
510
- <label>
511
- <span className='edit-label column-heading'>Filter: </span>
512
- <select
513
- value={filter.columnName}
514
- onChange={e => {
515
- updateFilterProp('columnName', e.target.value)
516
- }}
517
- >
518
- <option value=''>- Select Option -</option>
519
- {columns.map(dataKey => (
520
- <option value={dataKey} key={`filter-column-select-item-${dataKey}`}>
521
- {dataKey}
522
- </option>
523
- ))}
524
- </select>
525
- </label>
508
+ <Select
509
+ label='Filter'
510
+ value={filter.columnName || ''}
511
+ options={[
512
+ { value: '', label: '- Select Option -' },
513
+ ...columns.map(col => ({ value: col, label: col }))
514
+ ]}
515
+ onChange={e => {
516
+ updateFilterProp('columnName', e.target.value)
517
+ }}
518
+ />
526
519
 
527
520
  <Select
528
521
  value={filter.defaultValue}
@@ -648,21 +641,16 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
648
641
  />
649
642
 
650
643
  <label>
651
- <span className='edit-label column-heading'>Parent Filter: </span>
652
- <select
653
- value={filter.parents || []}
654
- onChange={e => {
655
- updateFilterProp('parents', e.target.value)
644
+ <span className='edit-label column-heading mt-1'>Parent Filter(s): </span>
645
+ <MultiSelect
646
+ label='Parent Filter(s): '
647
+ options={dataFilterParents.map(key => ({ value: key, label: key }))}
648
+ fieldName='parents'
649
+ selected={filter.parents}
650
+ updateField={(_section, _subsection, _fieldname, newItems) => {
651
+ updateFilterProp('parents', newItems)
656
652
  }}
657
- >
658
- <option value=''>Select a filter</option>
659
- {config.dashboard.sharedFilters &&
660
- config.dashboard.sharedFilters.map(sharedFilter => {
661
- if (sharedFilter.key !== filter.key) {
662
- return <option key={sharedFilter.key}>{sharedFilter.key}</option>
663
- }
664
- })}
665
- </select>
653
+ />
666
654
  </label>
667
655
 
668
656
  {!isNestedDropdown && (