@cdc/dashboard 4.24.11 → 4.24.12-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cdcdashboard.js +49442 -49230
  2. package/examples/ed-visits-county-file.json +141 -357
  3. package/examples/private/DEV-10120.json +1294 -0
  4. package/examples/private/DEV-9199.json +606 -0
  5. package/examples/private/DEV-9684.json +2135 -0
  6. package/examples/private/DEV-9989.json +229 -0
  7. package/examples/private/art-dashboard.json +18174 -0
  8. package/examples/private/art-scratch.json +2406 -0
  9. package/examples/private/dashboard-config-ehdi.json +29915 -0
  10. package/examples/private/dashboard-margins.js +15 -0
  11. package/examples/private/dataset.json +1452 -0
  12. package/examples/private/ehdi-data.json +29502 -0
  13. package/examples/private/gaza-issue.json +1214 -0
  14. package/examples/private/workforce.json +2041 -0
  15. package/package.json +9 -9
  16. package/src/CdcDashboard.tsx +8 -15
  17. package/src/CdcDashboardComponent.tsx +53 -38
  18. package/src/DashboardContext.tsx +2 -0
  19. package/src/components/CollapsibleVisualizationRow.tsx +8 -2
  20. package/src/components/DashboardFilters/DashboardFilters.tsx +107 -59
  21. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +2 -0
  22. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +54 -50
  23. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +6 -2
  24. package/src/components/DashboardFilters/dashboardfilter.styles.css +16 -0
  25. package/src/components/VisualizationRow.tsx +30 -20
  26. package/src/components/Widget.tsx +1 -1
  27. package/src/data/initial-state.js +2 -1
  28. package/src/helpers/addValuesToDashboardFilters.ts +4 -2
  29. package/src/helpers/apiFilterHelpers.ts +55 -20
  30. package/src/helpers/changeFilterActive.ts +3 -0
  31. package/src/helpers/filterData.ts +1 -1
  32. package/src/helpers/loadAPIFilters.ts +25 -8
  33. package/src/helpers/reloadURLHelpers.ts +9 -2
  34. package/src/helpers/shouldLoadAllFilters.ts +30 -0
  35. package/src/helpers/tests/apiFilterHelpers.test.ts +85 -4
  36. package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +8 -3
  37. package/src/helpers/tests/reloadURLHelpers.test.ts +11 -5
  38. package/src/helpers/tests/shouldLoadAllFilters.test.ts +117 -0
  39. package/src/store/dashboard.reducer.ts +2 -1
@@ -3,7 +3,9 @@ import {
3
3
  getToFetch,
4
4
  getFilterValues,
5
5
  getLoadingFilterMemo,
6
- getParentParams
6
+ getParentParams,
7
+ setActiveNestedDropdown,
8
+ setActiveMultiDropdown
7
9
  } from '../apiFilterHelpers'
8
10
  import _ from 'lodash'
9
11
  import type { APIFilterDropdowns } from '../../components/DashboardFilters'
@@ -18,7 +20,7 @@ describe('getLoadingFilterMemo', () => {
18
20
  }
19
21
  const expectedOutput: APIFilterDropdowns = {
20
22
  endpoint1: { text: 'text1', value: 'value1' },
21
- endpoint2: null
23
+ endpoint2: undefined
22
24
  }
23
25
  expect(getLoadingFilterMemo(sharedAPIFilters, apiFilterDropdowns)).toEqual(expectedOutput)
24
26
  })
@@ -36,8 +38,8 @@ describe('getLoadingFilterMemo', () => {
36
38
  const sharedAPIFilters = ['endpoint1', 'endpoint2']
37
39
  const apiFilterDropdowns: APIFilterDropdowns = {}
38
40
  const expectedOutput: APIFilterDropdowns = {
39
- endpoint1: null,
40
- endpoint2: null
41
+ endpoint1: undefined,
42
+ endpoint2: undefined
41
43
  }
42
44
  expect(getLoadingFilterMemo(sharedAPIFilters, apiFilterDropdowns)).toEqual(expectedOutput)
43
45
  })
@@ -280,6 +282,85 @@ describe('getToFetch', () => {
280
282
  })
281
283
  })
282
284
 
285
+ describe('setActiveNestedDropdown', () => {
286
+ const dropdownOptions = [
287
+ { value: 'option1', subOptions: [{ value: 'subOption1' }], label: 'Option 1' },
288
+ { value: 'option2', subOptions: [{ value: 'subOption2' }], label: 'Option 2' }
289
+ ]
290
+
291
+ const sharedFilters = [
292
+ {
293
+ key: 'filter1',
294
+ active: null,
295
+ filterStyle: FILTER_STYLE.nestedDropdown,
296
+ subGrouping: {},
297
+ queuedActive: null,
298
+ parents: []
299
+ },
300
+ {
301
+ key: 'filter2',
302
+ active: null,
303
+ setByQueryParameter: 'group',
304
+ filterStyle: FILTER_STYLE.nestedDropdown,
305
+ subGrouping: { setByQueryParameter: 'subgroup' },
306
+ queuedActive: null,
307
+ parents: ['filter1']
308
+ }
309
+ ] as SharedFilter[]
310
+
311
+ it('should set the active value for a nested dropdown', () => {
312
+ setActiveNestedDropdown(dropdownOptions, sharedFilters[0])
313
+ expect(sharedFilters[0].active).toEqual('option1')
314
+ expect(sharedFilters[0].subGrouping.active).toEqual('subOption1')
315
+ })
316
+ it('should set the active value for nested dropdown with query parameters', () => {
317
+ delete window.location
318
+ window.location = new URL('https://www.example.com?group=option2&subgroup=subOption2')
319
+ setActiveNestedDropdown(dropdownOptions, sharedFilters[1])
320
+ expect(sharedFilters[1].active).toEqual('option2')
321
+ expect(sharedFilters[1].subGrouping.active).toEqual('subOption2')
322
+ })
323
+ })
324
+
325
+ describe('setActiveMultiDropdown', () => {
326
+ const dropdownOptions = [
327
+ { value: 'option1', label: 'Option 1' },
328
+ { value: 'option2', label: 'Option 2' }
329
+ ]
330
+
331
+ const sharedFilters = [
332
+ {
333
+ key: 'filter1',
334
+ active: null,
335
+ filterStyle: FILTER_STYLE.multiSelect,
336
+ queuedActive: null,
337
+ parents: []
338
+ },
339
+ {
340
+ key: 'filter2',
341
+ active: null,
342
+ filterStyle: FILTER_STYLE.multiSelect,
343
+ setByQueryParameter: 'group',
344
+ queuedActive: null,
345
+ parents: ['filter1']
346
+ }
347
+ ] as SharedFilter[]
348
+ it('should set the active value for a multi dropdown', () => {
349
+ setActiveMultiDropdown(dropdownOptions, sharedFilters[0])
350
+ expect(sharedFilters[0].active).toEqual(['option1'])
351
+ })
352
+ it('should set the active value for a multi dropdown with queryParameters', () => {
353
+ delete window.location
354
+ window.location = new URL('https://www.example.com?group=option1&group=option2')
355
+ setActiveMultiDropdown(dropdownOptions, sharedFilters[1])
356
+ expect(sharedFilters[1].active).toEqual(['option1', 'option2'])
357
+ delete window.location
358
+ window.location = new URL('https://www.example.com?group=option1,option2')
359
+ setActiveMultiDropdown(dropdownOptions, sharedFilters[1])
360
+ expect(sharedFilters[1].active).toEqual(['option1', 'option2'])
361
+ })
362
+ })
363
+
283
364
  describe('setAutoLoadDefaultValue', () => {
284
365
  const dropdownOptions = [
285
366
  { value: 'option1', label: 'Option 1' },
@@ -91,7 +91,7 @@ describe('loadAPIFiltersFactory', () => {
91
91
  it('loadAPIFilters() load dropdowns for children when parent is selected', async () => {
92
92
  const newSharedFilters = await loadAPIFilters(sharedFilters, apiFilterDropdowns)
93
93
  expect(dispatch).toHaveBeenCalledTimes(1)
94
- expect(setAPIFilterDropdowns).toHaveBeenCalledTimes(1)
94
+ expect(setAPIFilterDropdowns).toHaveBeenCalledTimes(2)
95
95
 
96
96
  const expectedDropdowns = {
97
97
  'cdc.gov/filters/Quarter': [
@@ -133,7 +133,7 @@ describe('loadAPIFiltersFactory', () => {
133
133
  sharedFilters[1].active = 'Q1'
134
134
  const newSharedFilters = await loadAPIFilters(sharedFilters, apiFilterDropdowns)
135
135
  expect(dispatch).toHaveBeenCalledTimes(1)
136
- expect(setAPIFilterDropdowns).toHaveBeenCalledTimes(1)
136
+ expect(setAPIFilterDropdowns).toHaveBeenCalledTimes(2)
137
137
 
138
138
  const expectedDropdowns = {
139
139
  'cdc.gov/filters/Quarter': [
@@ -214,8 +214,13 @@ describe('loadAPIFiltersFactory', () => {
214
214
  }
215
215
  const newSharedFilters = await loadAPIFilters(_sharedFilters, apiDropdownsLoaded)
216
216
  expect(dispatch).toHaveBeenCalledTimes(1)
217
- expect(setAPIFilterDropdowns).toHaveBeenCalledTimes(1)
217
+ expect(setAPIFilterDropdowns).toHaveBeenCalledTimes(2)
218
218
 
219
219
  expect(newSharedFilters[2].active).toEqual(['2020', '2021'])
220
220
  })
221
+ it('loads All filters if loadAll is true', async () => {
222
+ const _sharedFilters = _.cloneDeep(sharedFilters)
223
+ const newSharedFilters = await loadAPIFilters(_sharedFilters, {}, true)
224
+ expect(newSharedFilters[2].active).toEqual([2020])
225
+ })
221
226
  })
@@ -203,30 +203,36 @@ describe('filterUsedByDataUrl', () => {
203
203
  it('should return true when filter has no usedBy property', () => {
204
204
  const filter = { datasetKey: 'dataset1' }
205
205
  const datasetKey = 'dataset1'
206
- expect(filterUsedByDataUrl(filter, datasetKey, visualizations)).toBe(true)
206
+ expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(true)
207
207
  })
208
208
 
209
209
  it('should return true when filter has an empty usedBy array', () => {
210
210
  const filter = { usedBy: [], datasetKey: 'dataset1' }
211
211
  const datasetKey = 'dataset1'
212
- expect(filterUsedByDataUrl(filter, datasetKey, visualizations)).toBe(true)
212
+ expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(true)
213
213
  })
214
214
 
215
215
  it('should return true when filter has usedBy array with visualization keys that match the datasetKey', () => {
216
216
  const filter = { usedBy: ['viz1', 'viz3'], datasetKey: 'dataset1' }
217
217
  const datasetKey = 'dataset1'
218
- expect(filterUsedByDataUrl(filter, datasetKey, visualizations)).toBe(true)
218
+ expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(true)
219
219
  })
220
220
 
221
221
  it('should return false when filter has usedBy array with visualization keys that do not match the datasetKey', () => {
222
222
  const filter = { usedBy: ['viz2'], datasetKey: 'dataset1' }
223
223
  const datasetKey = 'dataset1'
224
- expect(filterUsedByDataUrl(filter, datasetKey, visualizations)).toBe(false)
224
+ expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(false)
225
225
  })
226
226
 
227
227
  it('should return true when filter has usedBy array with a mix of matching and non-matching visualization keys', () => {
228
228
  const filter = { usedBy: ['viz1', 'viz2'], datasetKey: 'dataset1' }
229
229
  const datasetKey = 'dataset1'
230
- expect(filterUsedByDataUrl(filter, datasetKey, visualizations)).toBe(true)
230
+ expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(true)
231
+ })
232
+
233
+ it('should return true when used by a row', () => {
234
+ const filter = { usedBy: ['viz1', 'viz2', 2], datasetKey: 'dataset1' }
235
+ const datasetKey = 'dataset1'
236
+ expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [{}, {}, { dataKey: 'dataset1' }])).toBe(true)
231
237
  })
232
238
  })
@@ -0,0 +1,117 @@
1
+ import { shouldLoadAllFilters } from '../shouldLoadAllFilters'
2
+
3
+ describe('shouldLoadAllFilters', () => {
4
+ it('returns false if not autoloading', () => {
5
+ delete window.location
6
+ window.location = new URL('https://www.example.com')
7
+ expect(shouldLoadAllFilters({ rows: [], visualizations: {}, dashboard: {} })).toBe(false)
8
+ })
9
+ it('returns true if missing data', () => {
10
+ delete window.location
11
+ window.location = new URL('https://www.example.com?cove-auto-load=true')
12
+ const config = {
13
+ dashboard: {},
14
+ visualizations: {
15
+ abc: {
16
+ dataKey: 'abcd'
17
+ }
18
+ },
19
+ rows: [],
20
+ datasets: {
21
+ abcd: {
22
+ data: []
23
+ }
24
+ }
25
+ }
26
+ expect(shouldLoadAllFilters(config)).toBe(true)
27
+ const config2 = {
28
+ multiDashboards: [
29
+ {
30
+ dashboard: {},
31
+ visualizations: {
32
+ abc: {
33
+ dataKey: 'abcd'
34
+ }
35
+ },
36
+ rows: [],
37
+ datasets: {
38
+ abcd: {
39
+ data: []
40
+ }
41
+ }
42
+ }
43
+ ],
44
+ datasets: {
45
+ abcd: {
46
+ data: []
47
+ }
48
+ },
49
+ activeDashboard: 0
50
+ }
51
+ expect(shouldLoadAllFilters(config2)).toBe(true)
52
+ })
53
+ it('returns false if no missing data', () => {
54
+ delete window.location
55
+ window.location = new URL('https://www.example.com?cove-auto-load=true')
56
+ const config = {
57
+ dashboard: {},
58
+ visualizations: {
59
+ abc: {
60
+ dataKey: 'abcd'
61
+ }
62
+ },
63
+ rows: [],
64
+ datasets: {
65
+ abcd: {
66
+ data: [{}]
67
+ }
68
+ }
69
+ }
70
+ expect(shouldLoadAllFilters(config)).toBe(false)
71
+ })
72
+ it('returns true when theres an autoloading filter and no apply filters', () => {
73
+ delete window.location
74
+ window.location = new URL('https://www.example.com')
75
+ const config = {
76
+ dashboard: {},
77
+ visualizations: {
78
+ abc: {
79
+ visualizationType: 'dashboardFilters',
80
+ dataKey: 'abcd',
81
+ autoLoad: true
82
+ }
83
+ },
84
+ rows: [],
85
+ datasets: {
86
+ abcd: { data: [] }
87
+ }
88
+ }
89
+ expect(shouldLoadAllFilters(config)).toBe(true)
90
+ })
91
+
92
+ it('returns false when theres an autoloading filter and apply filters', () => {
93
+ delete window.location
94
+ window.location = new URL('https://www.example.com')
95
+ const config = {
96
+ dashboard: {},
97
+ visualizations: {
98
+ abc: {
99
+ visualizationType: 'dashboardFilters',
100
+ dataKey: 'abcd',
101
+ autoLoad: true
102
+ },
103
+ abc2: {
104
+ visualizationType: 'dashboardFilters',
105
+ dataKey: 'abcde',
106
+ filterBehavior: 'Apply Button'
107
+ }
108
+ },
109
+ rows: [],
110
+ datasets: {
111
+ abcd: { data: [] },
112
+ abcde: { data: [] }
113
+ }
114
+ }
115
+ expect(shouldLoadAllFilters(config)).toBe(false)
116
+ })
117
+ })
@@ -150,7 +150,8 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
150
150
  case 'SWITCH_CONFIG': {
151
151
  const slot = action.payload
152
152
  const newConfigFields = state.config.multiDashboards[slot]
153
- return { ...state, config: { ...state.config, ...newConfigFields, activeDashboard: slot } }
153
+ const _newDatasets = _.cloneDeep(state.data)
154
+ return { ...state, data: _newDatasets, config: { ...state.config, ...newConfigFields, activeDashboard: slot } }
154
155
  }
155
156
  case 'TOGGLE_ROW': {
156
157
  const { rowIndex, colIndex } = action.payload