@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.
- package/dist/cdcdashboard.js +49442 -49230
- package/examples/ed-visits-county-file.json +141 -357
- package/examples/private/DEV-10120.json +1294 -0
- package/examples/private/DEV-9199.json +606 -0
- package/examples/private/DEV-9684.json +2135 -0
- package/examples/private/DEV-9989.json +229 -0
- package/examples/private/art-dashboard.json +18174 -0
- package/examples/private/art-scratch.json +2406 -0
- package/examples/private/dashboard-config-ehdi.json +29915 -0
- package/examples/private/dashboard-margins.js +15 -0
- package/examples/private/dataset.json +1452 -0
- package/examples/private/ehdi-data.json +29502 -0
- package/examples/private/gaza-issue.json +1214 -0
- package/examples/private/workforce.json +2041 -0
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +8 -15
- package/src/CdcDashboardComponent.tsx +53 -38
- package/src/DashboardContext.tsx +2 -0
- package/src/components/CollapsibleVisualizationRow.tsx +8 -2
- package/src/components/DashboardFilters/DashboardFilters.tsx +107 -59
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +2 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +54 -50
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +6 -2
- package/src/components/DashboardFilters/dashboardfilter.styles.css +16 -0
- package/src/components/VisualizationRow.tsx +30 -20
- package/src/components/Widget.tsx +1 -1
- package/src/data/initial-state.js +2 -1
- package/src/helpers/addValuesToDashboardFilters.ts +4 -2
- package/src/helpers/apiFilterHelpers.ts +55 -20
- package/src/helpers/changeFilterActive.ts +3 -0
- package/src/helpers/filterData.ts +1 -1
- package/src/helpers/loadAPIFilters.ts +25 -8
- package/src/helpers/reloadURLHelpers.ts +9 -2
- package/src/helpers/shouldLoadAllFilters.ts +30 -0
- package/src/helpers/tests/apiFilterHelpers.test.ts +85 -4
- package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +8 -3
- package/src/helpers/tests/reloadURLHelpers.test.ts +11 -5
- package/src/helpers/tests/shouldLoadAllFilters.test.ts +117 -0
- 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:
|
|
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:
|
|
40
|
-
endpoint2:
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|