@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.
- package/dist/{cdcdashboard-fce76882.es.js → cdcdashboard-BnB1QM5d.es.js} +6 -13
- package/dist/{cdcdashboard-c55ac1ea.es.js → cdcdashboard-D6CG2-Hb.es.js} +5 -12
- package/dist/{cdcdashboard-31a33da1.es.js → cdcdashboard-MXgURbdZ.es.js} +6 -13
- package/dist/{cdcdashboard-1a1724a1.es.js → cdcdashboard-dgT_1dIT.es.js} +136 -151
- package/dist/cdcdashboard.js +48574 -46414
- package/examples/api-test/categories.json +18 -0
- package/examples/api-test/chart-data.json +602 -0
- package/examples/api-test/topics.json +47 -0
- package/examples/api-test/years.json +22 -0
- package/examples/markup-axis-label.json +4167 -0
- package/examples/private/DEV-10538.json +407 -0
- package/examples/private/DEV-11405.json +39112 -0
- package/examples/private/big-dashboard.json +39095 -39077
- package/examples/private/clade-2.json +430 -0
- package/examples/private/delete.json +32919 -0
- package/examples/private/diabetes.json +546 -196
- package/examples/private/markup-footer/mortality-deaths-footnotes-age.csv +3 -0
- package/examples/private/mpox.json +38128 -0
- package/examples/private/reset.json +32920 -0
- package/examples/test-api-filter-reset.json +132 -0
- package/index.html +2 -2
- package/package.json +9 -10
- package/src/CdcDashboardComponent.tsx +17 -8
- package/src/DashboardContext.tsx +3 -1
- package/src/_stories/Dashboard.stories.tsx +17 -0
- package/src/_stories/_mock/custom-order-new-values.json +116 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +34 -20
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +29 -12
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +77 -111
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +51 -51
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +120 -24
- package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +62 -3
- package/src/components/DataDesignerModal.tsx +12 -5
- package/src/components/Header/Header.tsx +10 -9
- package/src/components/Toggle/Toggle.tsx +48 -48
- package/src/components/VisualizationRow.tsx +4 -3
- package/src/helpers/addValuesToDashboardFilters.ts +29 -4
- package/src/helpers/apiFilterHelpers.ts +26 -2
- package/src/helpers/filterData.ts +52 -7
- package/src/helpers/filterResetHelpers.ts +102 -0
- package/src/helpers/getVizConfig.ts +2 -2
- package/src/helpers/loadAPIFilters.ts +109 -99
- package/src/helpers/tests/filterResetHelpers.test.ts +532 -0
- package/src/index.tsx +1 -0
- package/src/scss/editor-panel.scss +3 -431
- package/src/scss/main.scss +1 -24
- package/src/store/errorMessage/errorMessage.reducer.ts +1 -1
- package/src/types/DashboardFilters.ts +9 -8
- package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_data.csv +0 -14041
- package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_per_100000_data.csv +0 -14041
- package/examples/private/burden_toolkit_mortality_qaly_data.csv +0 -18721
- package/examples/private/burden_toolkit_mortality_yll_data.csv +0 -18721
- package/src/helpers/getAutoLoadVisualization.ts +0 -11
- package/src/scss/mixins.scss +0 -47
- package/src/scss/variables.scss +0 -5
- /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
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
<
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
<
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
<
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
322
|
-
|
|
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
|
-
<
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
<
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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 =
|
|
51
|
-
|
|
52
|
-
|
|
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 =
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
//
|
|
97
|
-
const
|
|
98
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
108
|
-
|
|
117
|
+
// First try to reload URL data (for filters that actually change the API call)
|
|
118
|
+
await reloadURLData(newFilters)
|
|
109
119
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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={`${
|
|
228
|
-
|
|
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>
|