@cdc/dashboard 4.25.7 → 4.25.8

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/index.html CHANGED
@@ -1,28 +1,30 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
6
- <style>
7
- body {
8
- margin: 0 auto !important;
9
- display: flex;
10
- flex-direction: column;
11
- justify-content: center;
12
- min-height: unset !important;
13
- }
14
3
 
15
- .react-container + .react-container {
16
- margin-top: 3rem;
17
- }
18
- </style>
19
- <link rel="stylesheet prefetch" href="examples/custom/css/respiratory.css" />
20
- <link rel="stylesheet prefetch" href="https://www.cdc.gov/TemplatePackage/5.0/css/app.min.css?_=71669" />
21
- </head>
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
7
+ <style>
8
+ body {
9
+ margin: 0 auto !important;
10
+ display: flex;
11
+ flex-direction: column;
12
+ justify-content: center;
13
+ min-height: unset !important;
14
+ }
22
15
 
23
- <body>
24
- <div class="react-container" data-config="/examples/map.json"></div>
25
- <script src="https://www.cdc.gov/TemplatePackage/contrib/libs/jquery/latest/jquery.min.js?_=91329"></script>
26
- <script type="module" src="./src/index.tsx"></script>
27
- </body>
28
- </html>
16
+ .react-container+.react-container {
17
+ margin-top: 3rem;
18
+ }
19
+ </style>
20
+ <link rel="stylesheet prefetch" href="examples/custom/css/respiratory.css" />
21
+ <link rel="stylesheet prefetch" href="https://www.cdc.gov/TemplatePackage/5.0/css/app.min.css?_=71669" />
22
+ </head>
23
+
24
+ <body>
25
+ <div class="react-container" data-config="/examples/private/oral-health.json"></div>
26
+ <script src="https://www.cdc.gov/TemplatePackage/contrib/libs/jquery/latest/jquery.min.js?_=91329"></script>
27
+ <script type="module" src="./src/index.tsx"></script>
28
+ </body>
29
+
30
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/dashboard",
3
- "version": "4.25.7",
3
+ "version": "4.25.8",
4
4
  "description": "React component for combining multiple visualizations into a single dashboard",
5
5
  "moduleName": "CdcDashboard",
6
6
  "main": "dist/cdcdashboard",
@@ -27,13 +27,13 @@
27
27
  },
28
28
  "license": "Apache-2.0",
29
29
  "dependencies": {
30
- "@cdc/chart": "^4.25.7",
31
- "@cdc/core": "^4.25.7",
32
- "@cdc/data-bite": "^4.25.7",
33
- "@cdc/filtered-text": "^4.25.7",
34
- "@cdc/map": "^4.25.7",
35
- "@cdc/markup-include": "^4.25.7",
36
- "@cdc/waffle-chart": "^4.25.7",
30
+ "@cdc/chart": "^4.25.8",
31
+ "@cdc/core": "^4.25.8",
32
+ "@cdc/data-bite": "^4.25.8",
33
+ "@cdc/filtered-text": "^4.25.8",
34
+ "@cdc/map": "^4.25.8",
35
+ "@cdc/markup-include": "^4.25.8",
36
+ "@cdc/waffle-chart": "^4.25.8",
37
37
  "js-base64": "^2.5.2",
38
38
  "react-accessible-accordion": "^3.3.4",
39
39
  "react-dnd": "^14.0.2",
@@ -44,5 +44,5 @@
44
44
  "react": "^18.2.0",
45
45
  "react-dom": "^18.2.0"
46
46
  },
47
- "gitHead": "9062881d50c824ee6cdd71868bafd016a5e5694d"
47
+ "gitHead": "e369994230b5e3facff224e1d89d5937528ac5a0"
48
48
  }
@@ -14,13 +14,21 @@ import { coveUpdateWorker } from '@cdc/core/helpers/coveUpdateWorker'
14
14
  import _ from 'lodash'
15
15
  import { getQueryParams } from '@cdc/core/helpers/queryStringUtils'
16
16
  import EditorContext from '../../editor/src/ConfigContext'
17
+ import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
17
18
 
18
19
  type MultiDashboardProps = Omit<WCMSProps, 'configUrl'> & {
19
20
  configUrl?: string
20
21
  config?: MultiDashboardConfig
22
+ interactionLabel?: string
21
23
  }
22
24
 
23
- const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, isEditor, isDebug, config }) => {
25
+ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({
26
+ configUrl,
27
+ isEditor,
28
+ isDebug,
29
+ config,
30
+ interactionLabel = ''
31
+ }) => {
24
32
  const [initial, setInitial] = useState<InitialState>(undefined)
25
33
  const editorContext = useContext(EditorContext)
26
34
 
@@ -43,7 +51,6 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, isEdi
43
51
  const versionedConfig = coveUpdateWorker(config)
44
52
  return { ...initialState, config: versionedConfig, filteredData, data: datasets }
45
53
  }
46
-
47
54
  const loadConfig = async () => {
48
55
  const _config: MultiDashboardConfig = config || editorContext.config || (await (await fetch(configUrl)).json())
49
56
  const selected = getSelectedConfig(_config)
@@ -51,6 +58,7 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, isEdi
51
58
  const { newConfig, datasets } =
52
59
  selected !== null ? await loadMultiDashboard(_config, selected) : await loadSingleDashboard(_config)
53
60
  setInitial(formatInitialState(newConfig, datasets))
61
+ publishAnalyticsEvent('dashboard_loaded', 'load', interactionLabel, 'dashboard')
54
62
  }
55
63
 
56
64
  useEffect(() => {
@@ -138,7 +146,9 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, isEdi
138
146
  }
139
147
 
140
148
  if (!initial) return <Loading />
141
- return <CdcDashboard isEditor={isEditor} isDebug={isDebug} initialState={initial} />
149
+ return (
150
+ <CdcDashboard isEditor={isEditor} isDebug={isDebug} initialState={initial} interactionLabel={interactionLabel} />
151
+ )
142
152
  }
143
153
 
144
154
  export default MultiDashboardWrapper
@@ -13,6 +13,8 @@ import parse from 'html-react-parser'
13
13
  import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
14
14
  import { GlobalContextProvider } from '@cdc/core/components/GlobalContext'
15
15
  import { DashboardContext, DashboardDispatchContext } from './DashboardContext'
16
+ import { Visualization } from '@cdc/core/types/Visualization'
17
+ import { hasDashboardApplyBehavior } from './helpers/hasDashboardApplyBehavior'
16
18
 
17
19
  import OverlayFrame from '@cdc/core/components/ui/OverlayFrame'
18
20
  import Loading from '@cdc/core/components/Loading'
@@ -42,7 +44,6 @@ import MultiTabs from './components/MultiConfigTabs'
42
44
  import _ from 'lodash'
43
45
  import EditorContext from '../../editor/src/ConfigContext'
44
46
  import { APIFilterDropdowns } from './components/DashboardFilters'
45
- import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
46
47
  import { ViewPort } from '@cdc/core/types/ViewPort'
47
48
  import VisualizationRow from './components/VisualizationRow'
48
49
  import { getVizConfig } from './helpers/getVizConfig'
@@ -52,7 +53,6 @@ import Layout from '@cdc/core/components/Layout'
52
53
  import * as reloadURLHelpers from './helpers/reloadURLHelpers'
53
54
  import { addValuesToDashboardFilters } from './helpers/addValuesToDashboardFilters'
54
55
  import { DashboardFilters } from './types/DashboardFilters'
55
- import DashboardSharedFilters from './components/DashboardFilters'
56
56
  import { loadAPIFiltersFactory } from './helpers/loadAPIFilters'
57
57
  import Loader from '@cdc/core/components/Loader'
58
58
  import Alert from '@cdc/core/components/Alert'
@@ -62,9 +62,14 @@ import DashboardEditors from './components/DashboardEditors'
62
62
 
63
63
  type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
64
64
  initialState: InitialState
65
- }
66
-
67
- export default function CdcDashboard({ initialState, isEditor = false, isDebug = false }: DashboardProps) {
65
+ } & { interactionLabel: string }
66
+
67
+ export default function CdcDashboard({
68
+ initialState,
69
+ isEditor = false,
70
+ isDebug = false,
71
+ interactionLabel = ''
72
+ }: DashboardProps) {
68
73
  const [state, dispatch] = useReducer(dashboardReducer, initialState)
69
74
  const [errorMessages, dispatchErrorMessages] = useReducer(errorMessagesReducer, [])
70
75
  const editorContext = useContext(EditorContext)
@@ -77,10 +82,16 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
77
82
  const isPreview = state.tabSelected === 'Dashboard Preview'
78
83
 
79
84
  const inNoDataState = useMemo(() => {
85
+ const hasApplyBehavior = hasDashboardApplyBehavior(state.config.visualizations)
86
+
87
+ if (hasApplyBehavior && !state.filtersApplied) {
88
+ return true
89
+ }
90
+
80
91
  const vals = reloadURLHelpers.getDatasetKeys(state.config).map(key => state.data[key])
81
92
  if (!vals.length) return true
82
93
  return vals.some(val => val === undefined)
83
- }, [state.data])
94
+ }, [state.data, state.config.visualizations, state.filtersApplied])
84
95
 
85
96
  const vizRowColumnLocator = getVizRowColumnLocator(state.config.rows)
86
97
 
@@ -105,7 +116,12 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
105
116
  const filters = newFilters || config.dashboard.sharedFilters
106
117
  const datasetKeys = reloadURLHelpers.getDatasetKeys(config)
107
118
 
108
- const newData = _.cloneDeep(state.data)
119
+ const emptyData = {}
120
+ const emptyFilteredData = {}
121
+ dispatch({ type: 'SET_DATA', payload: emptyData })
122
+ dispatch({ type: 'SET_FILTERED_DATA', payload: emptyFilteredData })
123
+
124
+ const newData = {} // Start with empty object instead of cloning existing data
109
125
  const newDatasets = _.cloneDeep(config.datasets)
110
126
  let dataWasFetched = false
111
127
  let newFileName = ''
@@ -157,8 +173,11 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
157
173
  })
158
174
 
159
175
  const dataNeedsUpdate = reloadURLHelpers.isUpdateNeeded(filters, currentQSParams, updatedQSParams)
160
-
161
- if (!!newFilters || dataNeedsUpdate) {
176
+ const alreadyFetched = !!dataset.data
177
+ if (alreadyFetched && !newFilters && !dataNeedsUpdate) {
178
+ dataWasFetched = true
179
+ newData[datasetKey] = dataset.data
180
+ } else if (!!newFilters || dataNeedsUpdate) {
162
181
  dataWasFetched = true
163
182
  const dataUrlFinal = reloadURLHelpers.getDataURL(
164
183
  { ...currentQSParams, ...updatedQSParams },
@@ -223,7 +242,10 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
223
242
  _newData
224
243
  )
225
244
  dispatch({ type: 'SET_FILTERED_DATA', payload: filteredData })
226
- const visualizations = reloadURLHelpers.getVisualizationsWithFormattedData(config.visualizations, newData)
245
+ const visualizations = reloadURLHelpers.getVisualizationsWithFormattedData(
246
+ config.visualizations as Record<string, Visualization>,
247
+ newData
248
+ )
227
249
  dispatch({
228
250
  type: 'SET_CONFIG',
229
251
  payload: {
@@ -305,13 +327,32 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
305
327
  })
306
328
  if (allValuesSelected) {
307
329
  reloadURLData(newFilters)
308
- setAPILoading(false)
309
330
  } else {
310
331
  setAPILoading(false)
311
332
  }
312
333
  })
313
334
  }, [isEditor, isPreview, state.config?.activeDashboard])
314
335
 
336
+ useEffect(() => {
337
+ return () => {
338
+ // Clear all data when component unmounts to prevent memory leaks
339
+ dispatch({ type: 'SET_DATA', payload: {} })
340
+ dispatch({ type: 'SET_FILTERED_DATA', payload: {} })
341
+
342
+ // Clear any pending API requests
343
+ setAPILoading(false)
344
+
345
+ // Force garbage collection hint if available
346
+ if (window.gc && typeof window.gc === 'function') {
347
+ try {
348
+ window.gc()
349
+ } catch (e) {
350
+ // Ignore gc errors
351
+ }
352
+ }
353
+ }
354
+ }, [])
355
+
315
356
  const updateChildConfig = (visualizationKey, newConfig) => {
316
357
  const config = _.cloneDeep(state.config)
317
358
  const updatedConfig = _.pick(config, ['visualizations', 'multiDashboards'])
@@ -394,6 +435,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
394
435
  setSharedFilter={setSharedFilter}
395
436
  apiFilterDropdowns={apiFilterDropdowns}
396
437
  state={state}
438
+ interactionLabel={interactionLabel}
397
439
  />
398
440
  </>
399
441
  )
@@ -458,6 +500,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
458
500
  apiFilterDropdowns={apiFilterDropdowns}
459
501
  currentViewport={currentViewport}
460
502
  inNoDataState={inNoDataState}
503
+ interactionLabel={interactionLabel}
461
504
  isLastRow={index === filteredRows.length - 1}
462
505
  />
463
506
  ))}
@@ -473,6 +516,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
473
516
  state={config}
474
517
  text='Download Dashboard Image'
475
518
  elementToCapture={imageId}
519
+ interactionLabel={interactionLabel}
476
520
  />
477
521
  )}
478
522
  {config.table?.downloadPdfButton && (
@@ -482,6 +526,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
482
526
  state={config}
483
527
  text='Download Dashboard PDF'
484
528
  elementToCapture={imageId}
529
+ interactionLabel={interactionLabel}
485
530
  />
486
531
  )}
487
532
  </section>
@@ -500,61 +545,73 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
500
545
  imageRef={imageId}
501
546
  isDebug={isDebug}
502
547
  isEditor={isEditor}
548
+ interactionLabel={interactionLabel}
503
549
  />
504
550
  )}
505
551
  {config.table?.show &&
506
552
  config.datasets &&
507
- Object.keys(config.datasets).map(datasetKey => {
508
- //For each dataset, find any shared filters that apply to all visualizations using the dataset
553
+ Object.keys(config.datasets)
554
+ .map(datasetKey => {
555
+ //For each dataset, find any shared filters that apply to all visualizations using the dataset
509
556
 
510
- //Gets list of visuailzations using the dataset
511
- const vizKeysUsingDataset: string[] = getVizKeys(config).filter(visualizationKey => {
512
- return config.visualizations[visualizationKey].dataKey === datasetKey
513
- })
557
+ // Get the active dashboard configuration (for multidashboard support)
558
+ const activeConfig = config.multiDashboards ? config.multiDashboards[config.activeDashboard] : config
514
559
 
515
- //Checks shared filters against list to see if all visualizations are represented
516
- let applicableFilters: SharedFilter[] = []
517
- config.dashboard.sharedFilters?.forEach(sharedFilter => {
518
- let allMatch = true
519
- vizKeysUsingDataset.forEach(visualizationKey => {
520
- if (sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) === -1) {
521
- allMatch = false
522
- }
560
+ //Gets list of visuailzations using the dataset (only from current dashboard tab)
561
+ const vizKeysUsingDataset: string[] = getVizKeys(activeConfig).filter(visualizationKey => {
562
+ return activeConfig.visualizations[visualizationKey].dataKey === datasetKey
523
563
  })
524
- if (allMatch) {
525
- applicableFilters.push(sharedFilter)
564
+
565
+ // Skip this dataset if no visualizations in the current dashboard use it
566
+ if (vizKeysUsingDataset.length === 0) {
567
+ return null
526
568
  }
527
- })
528
569
 
529
- //Applys any applicable filters to the Table
530
- const filteredTableData =
531
- applicableFilters.length > 0
532
- ? filterData(applicableFilters, config.datasets[datasetKey].data)
533
- : undefined
534
- return (
535
- <div
536
- className='multi-table-container'
537
- id={`data-table-${datasetKey}`}
538
- key={`data-table-${datasetKey}`}
539
- >
540
- <DataTable
541
- config={config as TableConfig}
542
- dataConfig={config.datasets[datasetKey]}
543
- rawData={config.datasets[datasetKey].data?.[0]?.tableData || config.datasets[datasetKey].data}
544
- runtimeData={
545
- config.datasets[datasetKey].data?.[0]?.tableData ||
546
- filteredTableData ||
547
- config.datasets[datasetKey].data ||
548
- []
570
+ //Checks shared filters against list to see if all visualizations are represented
571
+ let applicableFilters: SharedFilter[] = []
572
+ activeConfig.dashboard.sharedFilters?.forEach(sharedFilter => {
573
+ let allMatch = true
574
+ vizKeysUsingDataset.forEach(visualizationKey => {
575
+ if (sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) === -1) {
576
+ allMatch = false
549
577
  }
550
- expandDataTable={config.table.expanded}
551
- tableTitle={datasetKey}
552
- viewport={currentViewport}
553
- tabbingId={datasetKey}
554
- />
555
- </div>
556
- )
557
- })}
578
+ })
579
+ if (allMatch) {
580
+ applicableFilters.push(sharedFilter)
581
+ }
582
+ })
583
+
584
+ //Applys any applicable filters to the Table
585
+ const filteredTableData =
586
+ applicableFilters.length > 0
587
+ ? filterData(applicableFilters, config.datasets[datasetKey].data)
588
+ : undefined
589
+ return (
590
+ <div
591
+ className='multi-table-container'
592
+ id={`data-table-${datasetKey}`}
593
+ key={`data-table-${datasetKey}`}
594
+ >
595
+ <DataTable
596
+ config={config as TableConfig}
597
+ dataConfig={config.datasets[datasetKey]}
598
+ rawData={config.datasets[datasetKey].data?.[0]?.tableData || config.datasets[datasetKey].data}
599
+ runtimeData={
600
+ config.datasets[datasetKey].data?.[0]?.tableData ||
601
+ filteredTableData ||
602
+ config.datasets[datasetKey].data ||
603
+ []
604
+ }
605
+ expandDataTable={config.table.expanded}
606
+ tableTitle={datasetKey}
607
+ viewport={currentViewport}
608
+ tabbingId={datasetKey}
609
+ interactionLabel={interactionLabel}
610
+ />
611
+ </div>
612
+ )
613
+ })
614
+ .filter(Boolean)}
558
615
  </div>
559
616
  </Layout.Responsive>
560
617
  </>
@@ -27,7 +27,8 @@ export const initialState = {
27
27
  loading: false,
28
28
  filteredData: {},
29
29
  preview: false,
30
- tabSelected: firstTab
30
+ tabSelected: firstTab,
31
+ filtersApplied: false
31
32
  }
32
33
 
33
34
  const initialContext: ConfigCTX = {
@@ -19,6 +19,7 @@ type DashboardEditorProps = {
19
19
  setSharedFilter?: Function
20
20
  apiFilterDropdowns?: APIFilterDropdowns
21
21
  state: DashboardState
22
+ interactionLabel: string
22
23
  }
23
24
 
24
25
  const DashboardEditors: React.FC<DashboardEditorProps> = ({
@@ -28,7 +29,8 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
28
29
  isDebug,
29
30
  setSharedFilter,
30
31
  apiFilterDropdowns,
31
- state
32
+ state,
33
+ interactionLabel = ''
32
34
  }) => {
33
35
  const setsSharedFilter =
34
36
  state.config.dashboard.sharedFilters &&
@@ -66,6 +68,7 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
66
68
  showLoader={false}
67
69
  dashboardConfig={state.config}
68
70
  datasets={state.config.datasets}
71
+ interactionLabel={interactionLabel}
69
72
  />
70
73
  )
71
74
 
@@ -77,6 +80,7 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
77
80
  isEditor={true}
78
81
  setConfig={_updateConfig}
79
82
  isDashboard={true}
83
+ interactionLabel={interactionLabel}
80
84
  />
81
85
  )
82
86
 
@@ -88,6 +92,7 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
88
92
  isEditor={true}
89
93
  setConfig={_updateConfig}
90
94
  isDashboard={true}
95
+ interactionLabel={interactionLabel}
91
96
  />
92
97
  )
93
98
 
@@ -100,6 +105,7 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
100
105
  setConfig={_updateConfig}
101
106
  isDashboard={true}
102
107
  datasets={state.config.datasets}
108
+ interactionLabel={interactionLabel}
103
109
  />
104
110
  )
105
111
 
@@ -111,9 +117,9 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
111
117
  isEditor={true}
112
118
  setConfig={_updateConfig}
113
119
  isDashboard={true}
120
+ interactionLabel={interactionLabel}
114
121
  />
115
122
  )
116
-
117
123
  case 'dashboardFilters':
118
124
  return (
119
125
  <DashboardSharedFilters
@@ -121,6 +127,7 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
121
127
  visualizationConfig={visualizationConfig}
122
128
  apiFilterDropdowns={apiFilterDropdowns}
123
129
  setConfig={_updateConfig}
130
+ interactionLabel={interactionLabel}
124
131
  />
125
132
  )
126
133
 
@@ -132,6 +139,7 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
132
139
  isEditor={true}
133
140
  updateConfig={_updateConfig}
134
141
  datasets={state.config.datasets}
142
+ interactionLabel={interactionLabel}
135
143
  />
136
144
  )
137
145
 
@@ -15,6 +15,7 @@ import * as apiFilterHelpers from '../../helpers/apiFilterHelpers'
15
15
  import { applyQueuedActive } from '@cdc/core/components/Filters/helpers/applyQueuedActive'
16
16
  import './dashboardfilter.styles.css'
17
17
  import { updateChildFilters } from '../../helpers/updateChildFilters'
18
+ import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
18
19
 
19
20
  type SubOptions = { subOptions?: Record<'value' | 'text', string>[] }
20
21
 
@@ -32,6 +33,7 @@ type DashboardFiltersProps = {
32
33
  isEditor?: boolean
33
34
  setConfig: (config: DashboardFilters) => void
34
35
  currentViewport?: ViewPort
36
+ interactionLabel: string
35
37
  }
36
38
 
37
39
  const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
@@ -39,7 +41,8 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
39
41
  visualizationConfig,
40
42
  setConfig: updateConfig,
41
43
  currentViewport,
42
- isEditor = false
44
+ isEditor = false,
45
+ interactionLabel = ''
43
46
  }) => {
44
47
  const state = useContext(DashboardContext)
45
48
  const { config: dashboardConfig, reloadURLData, loadAPIFilters, setAPIFilterDropdowns, setAPILoading } = state
@@ -47,7 +50,12 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
47
50
 
48
51
  const applyFilters = e => {
49
52
  e.preventDefault() // prevent form submission
50
- const dashboardConfig = _.cloneDeep(state.config.dashboard)
53
+
54
+ const dashboardConfig = {
55
+ ...state.config.dashboard,
56
+ sharedFilters: [...state.config.dashboard.sharedFilters] // Only clone the array we need to modify
57
+ }
58
+
51
59
  const nonAutoLoadFilterIndexes = Object.values(state.config.visualizations)
52
60
  .filter(v => v.type === 'dashboardFilters')
53
61
  .reduce((acc, viz: DashboardFilters) => (!viz.autoLoad ? [...acc, viz.sharedFilterIndexes] : acc), [])
@@ -61,6 +69,7 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
61
69
  })
62
70
  if (allRequiredFiltersSelected) {
63
71
  if (hasDashboardApplyBehavior(state.config.visualizations)) {
72
+ dispatch({ type: 'SET_FILTERS_APPLIED', payload: true })
64
73
  const queryParams = getQueryParams()
65
74
  let needsQueryUpdate = false
66
75
  dashboardConfig.sharedFilters.forEach(sharedFilter => {
@@ -82,7 +91,21 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
82
91
  }
83
92
  setAPILoading(true)
84
93
  dispatch({ type: 'SET_SHARED_FILTERS', payload: dashboardConfig.sharedFilters })
85
- dispatch({ type: 'SET_FILTERED_DATA', payload: getFilteredData(_.cloneDeep(state)) })
94
+
95
+ // Clear data when applying filters to force fresh reload
96
+ const emptyData = Object.keys(state.data).reduce((acc, key) => {
97
+ acc[key] = []
98
+ return acc
99
+ }, {})
100
+
101
+ const emptyFilteredData = Object.keys(state.filteredData).reduce((acc, key) => {
102
+ acc[key] = []
103
+ return acc
104
+ }, {})
105
+
106
+ dispatch({ type: 'SET_DATA', payload: emptyData })
107
+ dispatch({ type: 'SET_FILTERED_DATA', payload: emptyFilteredData })
108
+
86
109
  loadAPIFilters(dashboardConfig.sharedFilters, apiFilterDropdowns)
87
110
  .then(newFilters => {
88
111
  reloadURLData(newFilters)
@@ -96,7 +119,14 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
96
119
  }
97
120
 
98
121
  const handleOnChange = (index: number, value: string | string[]) => {
99
- const newConfig = _.cloneDeep(dashboardConfig)
122
+ const newConfig = {
123
+ ...dashboardConfig,
124
+ dashboard: {
125
+ ...dashboardConfig.dashboard,
126
+ sharedFilters: [...dashboardConfig.dashboard.sharedFilters] // Only clone the array we modify
127
+ }
128
+ }
129
+
100
130
  let [newSharedFilters, changedFilterIndexes] = changeFilterActive(
101
131
  index,
102
132
  value,
@@ -104,6 +134,13 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
104
134
  visualizationConfig
105
135
  )
106
136
 
137
+ publishAnalyticsEvent(
138
+ 'dashboard_filter_changed',
139
+ 'change',
140
+ `${interactionLabel}|key_${newSharedFilters?.[index]?.key}|value_${value}`,
141
+ 'dashboard'
142
+ )
143
+
107
144
  // sets the active filter option that the user just selected.
108
145
  dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
109
146
 
@@ -125,9 +162,9 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
125
162
  })
126
163
  } else {
127
164
  newSharedFilters[index].queuedActive = value
128
- // setData to empty object because we no longer have a data state.
129
- dispatch({ type: 'SET_DATA', payload: {} })
130
- dispatch({ type: 'SET_FILTERED_DATA', payload: {} })
165
+
166
+ // Don't clear data immediately - keep existing data until new data loads
167
+ // Only update the filter dropdowns and prepare for reload
131
168
  setAPIFilterDropdowns(loadingFilterMemo)
132
169
  loadAPIFilters(newSharedFilters, loadingFilterMemo)
133
170
  }
@@ -135,8 +172,16 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
135
172
  if (newSharedFilters[index].type === 'urlfilter' && newSharedFilters[index].apiFilter) {
136
173
  reloadURLData(newSharedFilters)
137
174
  } else {
138
- const clonedState = _.cloneDeep(state)
139
- clonedState.config.dashboard.sharedFilters = newSharedFilters
175
+ const clonedState = {
176
+ ...state,
177
+ config: {
178
+ ...state.config,
179
+ dashboard: {
180
+ ...state.config.dashboard,
181
+ sharedFilters: newSharedFilters
182
+ }
183
+ }
184
+ }
140
185
  const newFilteredData = getFilteredData(clonedState)
141
186
  dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
142
187
  dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })