@cdc/dashboard 4.25.10 → 4.25.11

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 (56) hide show
  1. package/dist/{cdcdashboard-fce76882.es.js → cdcdashboard-BnB1QM5d.es.js} +6 -13
  2. package/dist/{cdcdashboard-c55ac1ea.es.js → cdcdashboard-D6CG2-Hb.es.js} +5 -12
  3. package/dist/{cdcdashboard-31a33da1.es.js → cdcdashboard-MXgURbdZ.es.js} +6 -13
  4. package/dist/{cdcdashboard-1a1724a1.es.js → cdcdashboard-dgT_1dIT.es.js} +136 -151
  5. package/dist/cdcdashboard.js +48574 -46414
  6. package/examples/api-test/categories.json +18 -0
  7. package/examples/api-test/chart-data.json +602 -0
  8. package/examples/api-test/topics.json +47 -0
  9. package/examples/api-test/years.json +22 -0
  10. package/examples/markup-axis-label.json +4167 -0
  11. package/examples/private/DEV-10538.json +407 -0
  12. package/examples/private/DEV-11405.json +39112 -0
  13. package/examples/private/big-dashboard.json +39095 -39077
  14. package/examples/private/clade-2.json +430 -0
  15. package/examples/private/delete.json +32919 -0
  16. package/examples/private/diabetes.json +546 -196
  17. package/examples/private/markup-footer/mortality-deaths-footnotes-age.csv +3 -0
  18. package/examples/private/mpox.json +38128 -0
  19. package/examples/private/reset.json +32920 -0
  20. package/examples/test-api-filter-reset.json +132 -0
  21. package/index.html +2 -2
  22. package/package.json +9 -10
  23. package/src/CdcDashboardComponent.tsx +17 -8
  24. package/src/DashboardContext.tsx +3 -1
  25. package/src/_stories/Dashboard.stories.tsx +17 -0
  26. package/src/_stories/_mock/custom-order-new-values.json +116 -0
  27. package/src/components/DashboardFilters/DashboardFilters.tsx +34 -20
  28. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +29 -12
  29. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +77 -111
  30. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +51 -51
  31. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +120 -24
  32. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +62 -3
  33. package/src/components/DataDesignerModal.tsx +12 -5
  34. package/src/components/Header/Header.tsx +10 -9
  35. package/src/components/Toggle/Toggle.tsx +48 -48
  36. package/src/components/VisualizationRow.tsx +4 -3
  37. package/src/helpers/addValuesToDashboardFilters.ts +29 -4
  38. package/src/helpers/apiFilterHelpers.ts +26 -2
  39. package/src/helpers/filterData.ts +52 -7
  40. package/src/helpers/filterResetHelpers.ts +102 -0
  41. package/src/helpers/getVizConfig.ts +2 -2
  42. package/src/helpers/loadAPIFilters.ts +109 -99
  43. package/src/helpers/tests/filterResetHelpers.test.ts +532 -0
  44. package/src/index.tsx +1 -0
  45. package/src/scss/editor-panel.scss +3 -431
  46. package/src/scss/main.scss +1 -24
  47. package/src/store/errorMessage/errorMessage.reducer.ts +1 -1
  48. package/src/types/DashboardFilters.ts +9 -8
  49. package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_data.csv +0 -14041
  50. package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_per_100000_data.csv +0 -14041
  51. package/examples/private/burden_toolkit_mortality_qaly_data.csv +0 -18721
  52. package/examples/private/burden_toolkit_mortality_yll_data.csv +0 -18721
  53. package/src/helpers/getAutoLoadVisualization.ts +0 -11
  54. package/src/scss/mixins.scss +0 -47
  55. package/src/scss/variables.scss +0 -5
  56. /package/dist/{cdcdashboard-548642e6.es.js → cdcdashboard-Ct2SB0vL.es.js} +0 -0
@@ -184,33 +184,25 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
184
184
  return (
185
185
  <>
186
186
  {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>
187
+ <Select
188
+ label='Filter Type'
189
+ value={filter.type || ''}
190
+ options={[
191
+ { value: '', label: '- Select Option -' },
192
+ { value: 'urlfilter', label: 'URL' },
193
+ { value: 'datafilter', label: 'Data' }
194
+ ]}
195
+ onChange={e => selectFilterType(e.target.value)}
196
+ disabled={!!filter.type}
197
+ />
199
198
  {filter.type !== undefined && (
200
199
  <>
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>
200
+ <Select
201
+ label='Filter Style'
202
+ value={filter.filterStyle || FILTER_STYLE.dropdown}
203
+ options={filterStyles}
204
+ onChange={e => updateFilterProp('filterStyle', e.target.value)}
205
+ />
214
206
  {filter.filterStyle === FILTER_STYLE.dropdown && (
215
207
  <label>
216
208
  <span className='me-1'>Show Dropdown</span>
@@ -254,41 +246,31 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
254
246
  <>
255
247
  {!hasDashboardApplyBehavior(config.visualizations) && (
256
248
  <>
257
- <label>
258
- <span className='edit-label column-heading'>URL to Filter: </span>
259
- <select
260
- defaultValue={filter.datasetKey || ''}
261
- 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>
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
+ />
276
263
 
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>
264
+ <Select
265
+ label='Filter By'
266
+ value={filter.filterBy || ''}
267
+ options={[
268
+ { value: '', label: '- Select Option -' },
269
+ { value: 'Query String', label: 'Query String' },
270
+ { value: 'File Name', label: 'File Name' }
271
+ ]}
272
+ onChange={e => updateFilterProp('filterBy', e.target.value)}
273
+ />
292
274
  {filter.filterBy === 'File Name' && (
293
275
  <>
294
276
  <TextField
@@ -307,9 +289,16 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
307
289
  }
308
290
  />
309
291
 
310
- <label>
311
- <span className='edit-label column-heading'>
312
- White Space Replacments
292
+ <Select
293
+ label='White Space Replacments'
294
+ value={filter.whitespaceReplacement || 'Keep Spaces'}
295
+ options={[
296
+ { value: 'Remove Spaces', label: 'Remove Spaces' },
297
+ { value: 'Replace With Underscore', label: 'Replace With Underscore' },
298
+ { value: 'Keep Spaces', label: 'Keep Spaces' }
299
+ ]}
300
+ onChange={e => updateFilterProp('whitespaceReplacement', e.target.value)}
301
+ tooltip={
313
302
  <Tooltip style={{ textTransform: 'none' }}>
314
303
  <Tooltip.Target>
315
304
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
@@ -318,22 +307,8 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
318
307
  <p>{`Set how whitespace characters will be handled in the file request`}</p>
319
308
  </Tooltip.Content>
320
309
  </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>
310
+ }
311
+ />
337
312
  </>
338
313
  )}
339
314
  </>
@@ -507,22 +482,17 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
507
482
  <>
508
483
  {filter.filterStyle !== FILTER_STYLE.nestedDropdown ? (
509
484
  <>
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>
485
+ <Select
486
+ label='Filter'
487
+ value={filter.columnName || ''}
488
+ options={[
489
+ { value: '', label: '- Select Option -' },
490
+ ...columns.map(col => ({ value: col, label: col }))
491
+ ]}
492
+ onChange={e => {
493
+ updateFilterProp('columnName', e.target.value)
494
+ }}
495
+ />
526
496
 
527
497
  <Select
528
498
  value={filter.defaultValue}
@@ -647,23 +617,19 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
647
617
  updateField={(_section, _subSection, _key, value) => updateFilterProp('resetLabel', value)}
648
618
  />
649
619
 
650
- <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)
656
- }}
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>
666
- </label>
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
+ />
667
633
 
668
634
  {!isNestedDropdown && (
669
635
  <TextField
@@ -47,17 +47,24 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
47
47
  })
48
48
  }
49
49
 
50
- const handleFitlerGroupColumnNameChange = selectedOption => {
51
- const selectedOptionDatasetName = selectedOption.selectedOptions[0].dataset.set
52
- const newColumnName = selectedOption.value
50
+ const handleFitlerGroupColumnNameChange = (value: string) => {
51
+ if (!value) {
52
+ updateFilterProp('columnName', '')
53
+ updateFilterProp('defaultValue', '')
54
+ return
55
+ }
56
+ const [newColumnName, selectedOptionDatasetName] = value.split('|')
53
57
  updateFilterProp('columnName', newColumnName)
54
58
  updateFilterProp('defaultValue', '') // Reset default value when column changes
55
59
  populateSubGroupingOptions(selectedOptionDatasetName, newColumnName)
56
60
  }
57
61
 
58
- const handleSubGroupColumnNameChange = selectedOption => {
59
- const selectedOptionDatasetName = selectedOption.selectedOptions[0].dataset.set
60
- const newColumnName = selectedOption.value
62
+ const handleSubGroupColumnNameChange = (value: string) => {
63
+ if (!value) {
64
+ updateFilterProp('subGrouping', { ...subGrouping, columnName: '', valuesLookup: {}, defaultValue: '' })
65
+ return
66
+ }
67
+ const [newColumnName, selectedOptionDatasetName] = value.split('|')
61
68
 
62
69
  const valuesLookup = filter.values.reduce((acc, groupName) => {
63
70
  const values: string[] = _.uniq(
@@ -94,51 +101,44 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
94
101
  updateField={(_section, _subSection, _key, value) => updateFilterProp('key', value)}
95
102
  />
96
103
  )}
97
- <label>
98
- <div className='edit-label column-heading mt-2'>
99
- Filter Grouping
100
- <span></span>
101
- </div>
102
- <select value={filter.columnName} onChange={e => handleFitlerGroupColumnNameChange(e.target)}>
103
- <option value=''>- Select Option -</option>
104
- {columnNameOptionsInDataset?.map(option => (
105
- <option
106
- value={option.columnName}
107
- data-set={option.datasetKey}
108
- key={`filter_${option.datasetKey}_${option.columnName} `}
109
- >
110
- {option.columnName}
111
- </option>
112
- ))}
113
- </select>
114
- </label>
115
- <label>
116
- <div className='edit-label column-heading mt-2'>
117
- Filter SubGrouping
118
- <span></span>
119
- </div>
120
- <select
121
- value={subGrouping?.columnName ?? ''}
122
- onChange={e => {
123
- handleSubGroupColumnNameChange(e.target)
124
- }}
125
- >
126
- <option value=''>- Select Option -</option>
127
- {columnNameOptionsInDataset.map(option => {
128
- if (option.columnName !== filter.columnName) {
129
- return (
130
- <option
131
- value={option.columnName}
132
- data-set={option.datasetKey}
133
- key={`subFilter_${option.datasetKey}_${option.columnName} `}
134
- >
135
- {option.columnName}
136
- </option>
137
- )
138
- }
139
- })}
140
- </select>
141
- </label>
104
+ <Select
105
+ label='Filter Grouping'
106
+ value={
107
+ filter.columnName
108
+ ? `${filter.columnName}|${
109
+ columnNameOptionsInDataset.find(opt => opt.columnName === filter.columnName)?.datasetKey || ''
110
+ }`
111
+ : ''
112
+ }
113
+ options={[
114
+ { value: '', label: '- Select Option -' },
115
+ ...columnNameOptionsInDataset.map(option => ({
116
+ value: `${option.columnName}|${option.datasetKey}`,
117
+ label: option.columnName
118
+ }))
119
+ ]}
120
+ onChange={e => handleFitlerGroupColumnNameChange(e.target.value)}
121
+ />
122
+ <Select
123
+ label='Filter SubGrouping'
124
+ value={
125
+ subGrouping?.columnName
126
+ ? `${subGrouping.columnName}|${
127
+ columnNameOptionsInDataset.find(opt => opt.columnName === subGrouping.columnName)?.datasetKey || ''
128
+ }`
129
+ : ''
130
+ }
131
+ options={[
132
+ { value: '', label: '- Select Option -' },
133
+ ...columnNameOptionsInDataset
134
+ .filter(option => option.columnName !== filter.columnName)
135
+ .map(option => ({
136
+ value: `${option.columnName}|${option.datasetKey}`,
137
+ label: option.columnName
138
+ }))
139
+ ]}
140
+ onChange={e => handleSubGroupColumnNameChange(e.target.value)}
141
+ />
142
142
 
143
143
  {/* Default Value for Main Group */}
144
144
  {filter.columnName && filter.values && filter.values.length > 0 && (
@@ -1,4 +1,4 @@
1
- import { useContext, useState } from 'react'
1
+ import { useContext, useState, useRef } from 'react'
2
2
  import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
3
3
  import Filters from './DashboardFilters'
4
4
  import { changeFilterActive } from '../../helpers/changeFilterActive'
@@ -12,6 +12,7 @@ import DashboardFiltersEditor from './DashboardFiltersEditor'
12
12
  import { ViewPort } from '@cdc/core/types/ViewPort'
13
13
  import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavior'
14
14
  import * as apiFilterHelpers from '../../helpers/apiFilterHelpers'
15
+ import * as filterResetHelpers from '../../helpers/filterResetHelpers'
15
16
  import { applyQueuedActive } from '@cdc/core/components/Filters/helpers/applyQueuedActive'
16
17
  import './dashboardfilter.styles.css'
17
18
  import { updateChildFilters } from '../../helpers/updateChildFilters'
@@ -49,9 +50,15 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
49
50
  const { config: dashboardConfig, reloadURLData, loadAPIFilters, setAPIFilterDropdowns, setAPILoading } = state
50
51
  const dispatch = useContext(DashboardDispatchContext)
51
52
 
53
+ // Track filter version to prevent stale async updates from overwriting cleared filters
54
+ const filterVersionRef = useRef(0)
55
+
52
56
  const applyFilters = e => {
53
57
  e.preventDefault() // prevent form submission
54
58
 
59
+ // Increment version to invalidate any pending async filter operations from handleOnChange
60
+ filterVersionRef.current += 1
61
+
55
62
  const dashboardConfig = {
56
63
  ...state.config.dashboard,
57
64
  sharedFilters: [...state.config.dashboard.sharedFilters] // Only clone the array we need to modify
@@ -59,18 +66,21 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
59
66
 
60
67
  const nonAutoLoadFilterIndexes = Object.values(state.config.visualizations)
61
68
  .filter(v => v.type === 'dashboardFilters')
62
- .reduce((acc, viz: DashboardFilters) => (!viz.autoLoad ? [...acc, viz.sharedFilterIndexes] : acc), [])
69
+ .reduce((acc, viz: DashboardFilters) => (!viz.autoLoad ? [...acc, ...viz.sharedFilterIndexes] : acc), [])
63
70
  const allRequiredFiltersSelected = !dashboardConfig.sharedFilters.some((filter, filterIndex) => {
64
71
  if (nonAutoLoadFilterIndexes.includes(filterIndex)) {
65
- return !filter.active && !filter.queuedActive
72
+ const activeValue = filter.queuedActive || filter.active
73
+ // Check if filter is not selected OR is set to its reset label
74
+ const isNotSelected = !activeValue || (filter.resetLabel && activeValue === filter.resetLabel)
75
+ return isNotSelected
66
76
  } else {
67
77
  // autoload filters don't need to be selected to apply filters
68
78
  return false
69
79
  }
70
80
  })
71
81
  if (allRequiredFiltersSelected) {
72
- if (hasDashboardApplyBehavior(state.config.visualizations)) {
73
- dispatch({ type: 'SET_FILTERS_APPLIED', payload: true })
82
+ const hasApplyBehavior = hasDashboardApplyBehavior(state.config.visualizations)
83
+ if (hasApplyBehavior) {
74
84
  const queryParams = getQueryParams()
75
85
  let needsQueryUpdate = false
76
86
  dashboardConfig.sharedFilters.forEach(sharedFilter => {
@@ -93,32 +103,105 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
93
103
  setAPILoading(true)
94
104
  dispatch({ type: 'SET_SHARED_FILTERS', payload: dashboardConfig.sharedFilters })
95
105
 
96
- // Clear data when applying filters to force fresh reload
97
- const emptyData = Object.keys(state.data).reduce((acc, key) => {
98
- acc[key] = []
99
- return acc
100
- }, {})
106
+ // Capture current version for this operation
107
+ const operationVersion = filterVersionRef.current
108
+ const isStale = () => filterVersionRef.current !== operationVersion
101
109
 
102
- const emptyFilteredData = Object.keys(state.filteredData).reduce((acc, key) => {
103
- acc[key] = []
104
- return acc
105
- }, {})
110
+ loadAPIFilters(dashboardConfig.sharedFilters, apiFilterDropdowns, undefined, undefined, isStale)
111
+ .then(async newFilters => {
112
+ // Skip if operation is stale
113
+ if (isStale()) {
114
+ return
115
+ }
106
116
 
107
- dispatch({ type: 'SET_DATA', payload: emptyData })
108
- dispatch({ type: 'SET_FILTERED_DATA', payload: emptyFilteredData })
117
+ // First try to reload URL data (for filters that actually change the API call)
118
+ await reloadURLData(newFilters)
109
119
 
110
- loadAPIFilters(dashboardConfig.sharedFilters, apiFilterDropdowns)
111
- .then(newFilters => {
112
- reloadURLData(newFilters)
120
+ // Set filters applied AFTER data is loaded to prevent "no data" flash
121
+ if (hasApplyBehavior) {
122
+ dispatch({ type: 'SET_FILTERS_APPLIED', payload: true })
123
+ }
124
+ setAPILoading(false)
113
125
  })
114
126
  .catch(e => {
115
127
  console.error(e)
128
+ setAPILoading(false)
116
129
  })
117
130
  } else {
118
131
  // TODO noftify of required fields
119
132
  }
120
133
  }
121
134
 
135
+ const handleReset = e => {
136
+ e.preventDefault()
137
+
138
+ // Increment version to invalidate any pending async filter operations
139
+ filterVersionRef.current += 1
140
+
141
+ const dashboardConfig = {
142
+ ...state.config.dashboard,
143
+ sharedFilters: _.cloneDeep(state.config.dashboard.sharedFilters)
144
+ }
145
+
146
+ const queryParams = getQueryParams()
147
+ let needsQueryUpdate = false
148
+
149
+ // Reset each filter to empty/resetLabel state (forceEmpty = true)
150
+ dashboardConfig.sharedFilters.forEach((filter, i) => {
151
+ const resetValue = filterResetHelpers.getFilterResetValue(filter, apiFilterDropdowns, true)
152
+ filterResetHelpers.resetFilterToValue(dashboardConfig.sharedFilters[i], resetValue, apiFilterDropdowns)
153
+
154
+ // Update query parameters if needed
155
+ if (
156
+ filter.setByQueryParameter &&
157
+ queryParams[filter.setByQueryParameter] !== dashboardConfig.sharedFilters[i].active
158
+ ) {
159
+ queryParams[filter.setByQueryParameter] = dashboardConfig.sharedFilters[i].active
160
+ needsQueryUpdate = true
161
+ }
162
+ })
163
+
164
+ if (needsQueryUpdate) {
165
+ updateQueryString(queryParams)
166
+ }
167
+
168
+ // Clear dropdown cache for child filters that depend on parents
169
+ const updatedDropdowns = filterResetHelpers.clearChildFilterDropdowns(
170
+ dashboardConfig.sharedFilters,
171
+ apiFilterDropdowns
172
+ )
173
+ setAPIFilterDropdowns(updatedDropdowns)
174
+
175
+ dispatch({ type: 'SET_SHARED_FILTERS', payload: dashboardConfig.sharedFilters })
176
+
177
+ // Reset filtersApplied state to false when clearing filters
178
+ dispatch({ type: 'SET_FILTERS_APPLIED', payload: false })
179
+
180
+ // Update filtered data immediately after resetting filters
181
+ // Use the updated dashboardConfig filters instead of state
182
+ const clonedState = {
183
+ ...state,
184
+ config: {
185
+ ...state.config,
186
+ dashboard: {
187
+ ...state.config.dashboard,
188
+ sharedFilters: dashboardConfig.sharedFilters
189
+ }
190
+ }
191
+ }
192
+ const newFilteredData = getFilteredData(clonedState)
193
+ dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
194
+
195
+ publishAnalyticsEvent({
196
+ vizType: dashboardConfig.type,
197
+ vizSubType: getVizSubType(dashboardConfig),
198
+ eventType: `dashboard_filter_reset`,
199
+ eventAction: 'click',
200
+ eventLabel: interactionLabel,
201
+ vizTitle: getVizTitle(dashboardConfig)
202
+ })
203
+ }
204
+
122
205
  const handleOnChange = (index: number, value: string | string[]) => {
123
206
  const newConfig = {
124
207
  ...dashboardConfig,
@@ -157,12 +240,18 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
157
240
  apiFilterDropdowns,
158
241
  changedFilterIndexes
159
242
  )
243
+ // Capture current version for this operation
244
+ const operationVersion = filterVersionRef.current
245
+ const isStale = () => filterVersionRef.current !== operationVersion
246
+
160
247
  if (isAutoSelectFilter && !missingFilterSelections) {
161
248
  // a dropdown has been selected that doesn't
162
249
  // require the Go Button
163
250
  setAPIFilterDropdowns(loadingFilterMemo)
164
- loadAPIFilters(newSharedFilters, loadingFilterMemo).then(filters => {
165
- reloadURLData(filters)
251
+ loadAPIFilters(newSharedFilters, loadingFilterMemo, undefined, undefined, isStale).then(filters => {
252
+ if (!isStale()) {
253
+ reloadURLData(filters)
254
+ }
166
255
  })
167
256
  } else {
168
257
  newSharedFilters[index].queuedActive = value
@@ -170,7 +259,7 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
170
259
  // Don't clear data immediately - keep existing data until new data loads
171
260
  // Only update the filter dropdowns and prepare for reload
172
261
  setAPIFilterDropdowns(loadingFilterMemo)
173
- loadAPIFilters(newSharedFilters, loadingFilterMemo)
262
+ loadAPIFilters(newSharedFilters, loadingFilterMemo, undefined, undefined, isStale)
174
263
  }
175
264
  } else {
176
265
  if (newSharedFilters[index].type === 'urlfilter' && newSharedFilters[index].apiFilter) {
@@ -224,8 +313,9 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
224
313
  {!displayNone && (
225
314
  <Layout.Responsive isEditor={isEditor}>
226
315
  <div
227
- className={`${isEditor ? ' is-editor' : ''
228
- } cove-component__content col-12 cove-dashboard-filters-container`}
316
+ className={`${
317
+ isEditor ? ' is-editor' : ''
318
+ } cove-component__content col-12 cove-dashboard-filters-container`}
229
319
  >
230
320
  <Filters
231
321
  show={visualizationConfig?.sharedFilterIndexes?.map(Number)}
@@ -235,6 +325,12 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
235
325
  showSubmit={visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad}
236
326
  applyFilters={applyFilters}
237
327
  applyFiltersButtonText={visualizationConfig.applyFiltersButtonText}
328
+ handleReset={
329
+ visualizationConfig.filterBehavior === FilterBehavior.Apply &&
330
+ (visualizationConfig.showClearButton ?? true)
331
+ ? handleReset
332
+ : undefined
333
+ }
238
334
  />
239
335
  </div>
240
336
  </Layout.Responsive>