@cdc/dashboard 4.24.10 → 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 (58) hide show
  1. package/dist/cdcdashboard.js +51165 -49100
  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-9644.json +20092 -0
  6. package/examples/private/DEV-9684.json +2135 -0
  7. package/examples/private/DEV-9989.json +229 -0
  8. package/examples/private/art-dashboard.json +18174 -0
  9. package/examples/private/art-scratch.json +2406 -0
  10. package/examples/private/dashboard-config-ehdi.json +29915 -0
  11. package/examples/private/dashboard-margins.js +15 -0
  12. package/examples/private/dataset.json +1452 -0
  13. package/examples/private/ehdi-data.json +29502 -0
  14. package/examples/private/gaza-issue.json +1214 -0
  15. package/examples/private/workforce.json +2041 -0
  16. package/package.json +9 -9
  17. package/src/CdcDashboard.tsx +43 -29
  18. package/src/CdcDashboardComponent.tsx +91 -52
  19. package/src/DashboardContext.tsx +2 -0
  20. package/src/_stories/Dashboard.stories.tsx +8 -0
  21. package/src/_stories/_mock/api-filter-error.json +55 -0
  22. package/src/_stories/_mock/group-pivot-filter.json +10 -5
  23. package/src/components/CollapsibleVisualizationRow.tsx +8 -2
  24. package/src/components/DashboardFilters/DashboardFilters.tsx +121 -58
  25. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +3 -1
  26. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +54 -50
  27. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +13 -7
  28. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +21 -0
  29. package/src/components/DashboardFilters/dashboardfilter.styles.css +27 -0
  30. package/src/components/Grid.tsx +1 -1
  31. package/src/components/Header/Header.tsx +71 -10
  32. package/src/components/Header/index.scss +0 -5
  33. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +28 -6
  34. package/src/components/MultiConfigTabs/MultiTabs.tsx +2 -0
  35. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +4 -11
  36. package/src/components/Row.tsx +59 -13
  37. package/src/components/VisualizationRow.tsx +30 -22
  38. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +0 -1
  39. package/src/components/Widget.tsx +23 -1
  40. package/src/data/initial-state.js +2 -1
  41. package/src/helpers/addValuesToDashboardFilters.ts +4 -2
  42. package/src/helpers/apiFilterHelpers.ts +55 -20
  43. package/src/helpers/changeFilterActive.ts +3 -0
  44. package/src/helpers/filterData.ts +1 -1
  45. package/src/helpers/getVizRowColumnLocator.ts +1 -0
  46. package/src/helpers/loadAPIFilters.ts +32 -10
  47. package/src/helpers/reloadURLHelpers.ts +9 -2
  48. package/src/helpers/shouldLoadAllFilters.ts +30 -0
  49. package/src/helpers/tests/apiFilterHelpers.test.ts +85 -4
  50. package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +10 -4
  51. package/src/helpers/tests/reloadURLHelpers.test.ts +11 -5
  52. package/src/helpers/tests/shouldLoadAllFilters.test.ts +117 -0
  53. package/src/scss/editor-panel.scss +0 -3
  54. package/src/scss/grid.scss +22 -23
  55. package/src/scss/main.scss +0 -27
  56. package/src/store/dashboard.reducer.ts +9 -2
  57. package/src/store/errorMessage/errorMessage.actions.ts +7 -0
  58. package/src/store/errorMessage/errorMessage.reducer.ts +24 -0
@@ -3,29 +3,41 @@ import { APIFilterDropdowns } from '../components/DashboardFilters'
3
3
  import { SharedFilter } from '../types/SharedFilter'
4
4
  import * as apiFilterHelpers from './apiFilterHelpers'
5
5
  import { APIFilter } from '../types/APIFilter'
6
+ import { getParentParams, notAllParentsSelected } from './apiFilterHelpers'
6
7
 
7
8
  export const loadAPIFiltersFactory = (
8
9
  dispatch: Function,
10
+ dispatchErrorMessages: Function,
9
11
  setAPIFilterDropdowns: Function,
10
12
  autoLoadFilterIndexes: number[]
11
13
  ) => {
12
14
  const loadAPIFilters = (
13
15
  sharedFilters: SharedFilter[],
14
16
  dropdowns: APIFilterDropdowns,
15
- recursiveLimit = 3
17
+ loadAll?: boolean,
18
+ recursiveLimit = 50
16
19
  ): Promise<SharedFilter[]> => {
17
20
  if (!sharedFilters) return
21
+ const allIndexes = sharedFilters.map((_, index) => index)
22
+ const _autoLoadFilterIndexes = loadAll ? allIndexes : autoLoadFilterIndexes
18
23
  sharedFilters = sharedFilters.map((filter, index) =>
19
24
  apiFilterHelpers.setAutoLoadDefaultValue(
20
25
  index,
21
26
  dropdowns[filter.apiFilter?.apiEndpoint],
22
27
  sharedFilters,
23
- autoLoadFilterIndexes
28
+ _autoLoadFilterIndexes
24
29
  )
25
30
  )
26
31
  const sharedAPIFilters = sharedFilters.filter(f => f.apiFilter)
27
32
  const filterLookup = new Map(sharedAPIFilters.map(filter => [filter.apiFilter.apiEndpoint, filter.apiFilter]))
28
33
  const toFetch = apiFilterHelpers.getToFetch(sharedFilters, dropdowns)
34
+ const loadingDropdowns = Object.values(toFetch).reduce(
35
+ (acc, [dropdownsKey]) => ({ ...acc, [dropdownsKey]: null }),
36
+ {}
37
+ )
38
+ setAPIFilterDropdowns(currentState => {
39
+ return { ...currentState, ...loadingDropdowns }
40
+ })
29
41
  const newDropdowns = _.cloneDeep(dropdowns)
30
42
  return Promise.all(
31
43
  Object.keys(toFetch).map(
@@ -36,7 +48,6 @@ export const loadAPIFiltersFactory = (
36
48
  .then(data => {
37
49
  if (!Array.isArray(data)) {
38
50
  console.error('COVE only supports response data in the shape Array<Object>')
39
- return
40
51
  }
41
52
  const [_key, index] = toFetch[endpoint]
42
53
  const apiFilter = filterLookup.get(_key) as APIFilter
@@ -47,27 +58,38 @@ export const loadAPIFiltersFactory = (
47
58
  index,
48
59
  _filterValues,
49
60
  sharedFilters,
50
- autoLoadFilterIndexes
61
+ _autoLoadFilterIndexes
51
62
  )
52
63
  sharedFilters[index] = newDefaultSelectedFilter
53
64
  })
54
- .catch(console.error)
65
+ .catch(() => {
66
+ dispatchErrorMessages({
67
+ type: 'ADD_ERROR_MESSAGE',
68
+ payload: 'There was a problem returning data. Please try again.'
69
+ })
70
+ })
55
71
  .finally(() => {
56
72
  resolve()
57
73
  })
58
74
  })
59
75
  )
60
76
  ).then(() => {
61
- const finishedLoading = sharedFilters.reduce((acc, curr, index) => {
62
- if (autoLoadFilterIndexes.includes(index) && !curr.active) return false
77
+ const toLoad = sharedFilters.reduce((acc, curr, index) => {
78
+ // the filter is autoloading and it hasn't finished yet
79
+ if (_autoLoadFilterIndexes.includes(index) && !curr.active) {
80
+ if (notAllParentsSelected(getParentParams(curr, sharedFilters))) {
81
+ return acc
82
+ }
83
+ return [...acc, index]
84
+ }
63
85
  return acc
64
- }, true)
65
- if (finishedLoading || recursiveLimit === 0) {
86
+ }, [])
87
+ if (!toLoad.length || recursiveLimit === 0) {
66
88
  setAPIFilterDropdowns(newDropdowns)
67
89
  dispatch({ type: 'SET_SHARED_FILTERS', payload: sharedFilters })
68
90
  return sharedFilters
69
91
  } else {
70
- return loadAPIFilters(sharedFilters, newDropdowns, recursiveLimit - 1)
92
+ return loadAPIFilters(sharedFilters, newDropdowns, loadAll, recursiveLimit - 1)
71
93
  }
72
94
  })
73
95
  }
@@ -4,6 +4,7 @@ import { capitalizeSplitAndJoin } from '@cdc/core/helpers/cove/string'
4
4
  import { AnyVisualization, Visualization } from '@cdc/core/types/Visualization'
5
5
  import _ from 'lodash'
6
6
  import { DashboardConfig } from '../types/DashboardConfig'
7
+ import { ConfigRow } from '../types/ConfigRow'
7
8
 
8
9
  export const isUpdateNeeded = (
9
10
  filters: SharedFilter[],
@@ -104,9 +105,15 @@ export const getVisualizationsWithFormattedData = (visualizations: Record<string
104
105
  export const filterUsedByDataUrl = (
105
106
  filter: SharedFilter,
106
107
  datasetKey: string,
107
- visualizations: Record<string, AnyVisualization>
108
+ visualizations: Record<string, AnyVisualization>,
109
+ rows: ConfigRow[]
108
110
  ) => {
109
111
  if (!filter.usedBy || !filter.usedBy.length) return true
110
- const vizUsingFilters = filter.usedBy?.map(vizKey => visualizations[vizKey])
112
+ const vizUsingFilters = filter.usedBy?.map(vizOrRowKey => visualizations[vizOrRowKey] || rows[vizOrRowKey])
113
+ // push any footnotes which are using the filter also
114
+ filter.usedBy?.forEach(vizOrRowKey => {
115
+ if (rows[vizOrRowKey] && rows[vizOrRowKey].footnotesId)
116
+ return vizUsingFilters.push(visualizations[rows[vizOrRowKey].footnotesId])
117
+ })
111
118
  return vizUsingFilters?.some(viz => viz?.dataKey === datasetKey)
112
119
  }
@@ -0,0 +1,30 @@
1
+ import { getQueryParam } from '@cdc/core/helpers/queryStringUtils'
2
+ import { Visualization } from '@cdc/core/types/Visualization'
3
+
4
+ export const shouldLoadAllFilters = (config, isEditorPanel): boolean => {
5
+ const autoLoad = Boolean(getQueryParam('cove-auto-load'))
6
+ const activeConfig = config.multiDashboards ? config.multiDashboards[config.activeDashboard] : config
7
+ const hasFilterByFileNameFunctionality = activeConfig.dashboard.sharedFilters?.some(
8
+ filter => filter.filterBy === 'File Name'
9
+ )
10
+ const isAutoLoadTab = Object.values(activeConfig.visualizations).reduce((acc, viz: Visualization) => {
11
+ if (acc === false) return acc
12
+ if (viz.visualizationType === 'dashboardFilters') {
13
+ if (viz.filterBehavior === 'Apply Button') return false
14
+ if (viz.autoLoad) {
15
+ return true
16
+ }
17
+ }
18
+ return acc
19
+ }, undefined)
20
+ if (autoLoad || isAutoLoadTab || hasFilterByFileNameFunctionality || isEditorPanel) {
21
+ const rowDataSetKeys = activeConfig.rows.map(row => row.dataKey).filter(Boolean)
22
+ const dataKeys = Object.values(activeConfig.visualizations)
23
+ .map((visualization: Visualization) => visualization.dataKey)
24
+ .filter(Boolean)
25
+ .concat(rowDataSetKeys)
26
+ const missingData = dataKeys.find(dataset => !config.datasets[dataset].data?.length)
27
+ return Boolean(missingData)
28
+ }
29
+ return false
30
+ }
@@ -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' },
@@ -66,6 +66,7 @@ global.fetch = fetch
66
66
 
67
67
  describe('loadAPIFiltersFactory', () => {
68
68
  const dispatch = vi.fn()
69
+ const dispatchErrorMessages = vi.fn()
69
70
  const setAPIFilterDropdowns = vi.fn()
70
71
  const apiFilterDropdowns = {
71
72
  'cdc.gov/filters/Sex': [
@@ -76,7 +77,7 @@ describe('loadAPIFiltersFactory', () => {
76
77
  afterEach(() => {
77
78
  vi.restoreAllMocks()
78
79
  })
79
- const loadAPIFilters = loadAPIFiltersFactory(dispatch, setAPIFilterDropdowns, [2])
80
+ const loadAPIFilters = loadAPIFiltersFactory(dispatch, dispatchErrorMessages, setAPIFilterDropdowns, [2])
80
81
  it('creates a function', () => {
81
82
  expect(typeof loadAPIFilters).toEqual('function')
82
83
  })
@@ -90,7 +91,7 @@ describe('loadAPIFiltersFactory', () => {
90
91
  it('loadAPIFilters() load dropdowns for children when parent is selected', async () => {
91
92
  const newSharedFilters = await loadAPIFilters(sharedFilters, apiFilterDropdowns)
92
93
  expect(dispatch).toHaveBeenCalledTimes(1)
93
- expect(setAPIFilterDropdowns).toHaveBeenCalledTimes(1)
94
+ expect(setAPIFilterDropdowns).toHaveBeenCalledTimes(2)
94
95
 
95
96
  const expectedDropdowns = {
96
97
  'cdc.gov/filters/Quarter': [
@@ -132,7 +133,7 @@ describe('loadAPIFiltersFactory', () => {
132
133
  sharedFilters[1].active = 'Q1'
133
134
  const newSharedFilters = await loadAPIFilters(sharedFilters, apiFilterDropdowns)
134
135
  expect(dispatch).toHaveBeenCalledTimes(1)
135
- expect(setAPIFilterDropdowns).toHaveBeenCalledTimes(1)
136
+ expect(setAPIFilterDropdowns).toHaveBeenCalledTimes(2)
136
137
 
137
138
  const expectedDropdowns = {
138
139
  'cdc.gov/filters/Quarter': [
@@ -213,8 +214,13 @@ describe('loadAPIFiltersFactory', () => {
213
214
  }
214
215
  const newSharedFilters = await loadAPIFilters(_sharedFilters, apiDropdownsLoaded)
215
216
  expect(dispatch).toHaveBeenCalledTimes(1)
216
- expect(setAPIFilterDropdowns).toHaveBeenCalledTimes(1)
217
+ expect(setAPIFilterDropdowns).toHaveBeenCalledTimes(2)
217
218
 
218
219
  expect(newSharedFilters[2].active).toEqual(['2020', '2021'])
219
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
+ })
220
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
+ })
@@ -384,9 +384,6 @@
384
384
  font-weight: normal;
385
385
  }
386
386
 
387
- .btn {
388
- margin-top: 1em;
389
- }
390
387
  .sort-list {
391
388
  list-style: none;
392
389
  > li {
@@ -51,27 +51,25 @@ $red: #f74242;
51
51
  margin-top: 2em;
52
52
  }
53
53
 
54
- .row-menu__btn:hover .row-menu__flyout {
55
- transition: width 0.2s cubic-bezier(0.16, 1, 0.3, 1);
56
- width: 180px;
57
-
58
- li {
59
- display: flex;
60
- }
61
54
 
62
- li + li {
63
- margin-left: 0.3em;
64
- }
65
- }
66
55
 
67
56
  .row-menu__flyout {
57
+ background-color: var(--blue);
58
+ $blue: #005eaa;
59
+ background-color: #c2c2c2;
60
+ border-radius: 0.2em 0.2em 0 0;
61
+ outline: none;
62
+
63
+ padding: 0.2em 0.3em;
64
+ fill: #fff;
68
65
  list-style: none;
69
66
  display: flex;
70
67
  justify-content: flex-start;
71
68
  overflow: hidden;
69
+ transition: background-color 300ms cubic-bezier(0.16, 1, 0.3, 1);
72
70
  transition: width 0.2s cubic-bezier(0.16, 1, 0.3, 1);
73
71
  z-index: 1;
74
- width: 25px;
72
+ width: 35px;
75
73
 
76
74
  li:not(.current) {
77
75
  display: none;
@@ -89,6 +87,18 @@ $red: #f74242;
89
87
  .row-menu__list--item {
90
88
  display: flex;
91
89
  }
90
+ &:hover {
91
+ transition: width 0.2s cubic-bezier(0.16, 1, 0.3, 1);
92
+ width: 180px;
93
+ background-color: lighten($blue, 8%);
94
+ li {
95
+ display: flex;
96
+ }
97
+
98
+ li + li {
99
+ margin-left: 0.3em;
100
+ }
101
+ }
92
102
  }
93
103
 
94
104
  .row-menu__btn {
@@ -96,7 +106,6 @@ $red: #f74242;
96
106
  border-radius: 0.2em 0.2em 0 0;
97
107
  outline: none;
98
108
  transition: background-color 300ms cubic-bezier(0.16, 1, 0.3, 1);
99
- cursor: pointer;
100
109
  padding: 0.2em 0.3em;
101
110
  display: flex;
102
111
  fill: #fff;
@@ -294,11 +303,6 @@ $red: #f74242;
294
303
  }
295
304
  }
296
305
 
297
- .btn.add-row {
298
- font-size: 1.1em;
299
- width: 100%;
300
- }
301
-
302
306
  .btn--fluid {
303
307
  @extend .btn;
304
308
  width: 100%;
@@ -359,11 +363,6 @@ $red: #f74242;
359
363
 
360
364
  &:hover {
361
365
  .row-menu .row-menu__btn {
362
- background-color: var(--blue);
363
- $blue: #005eaa;
364
- &:hover {
365
- background-color: lighten($blue, 8%);
366
- }
367
366
 
368
367
  &.row-menu__btn--edit {
369
368
  background-color: transparent;
@@ -138,21 +138,6 @@
138
138
  }
139
139
 
140
140
  .btn {
141
- background: #005eaa;
142
- color: #fff;
143
- border: 0;
144
- padding: 0.4em 0.8em;
145
- font-size: 0.9em;
146
- display: block;
147
- border-radius: 5px;
148
- transition: 0.1s all;
149
- cursor: pointer;
150
-
151
- &[disabled] {
152
- opacity: 0.5;
153
- z-index: -1;
154
- position: relative;
155
- }
156
141
 
157
142
  // Expand and Collapse Buttons for Multiviz Dashboard
158
143
  &.expand-collapse-buttons {
@@ -260,15 +245,6 @@
260
245
  width: 100%;
261
246
  }
262
247
 
263
- .cove-dashboard-filters-container {
264
- z-index: 5;
265
- }
266
-
267
- .cove-dashboard-filters {
268
- display: inline-flex;
269
- margin: 1em;
270
- }
271
-
272
248
  @include breakpointClass(md) {
273
249
  .dashboard-row {
274
250
  flex-direction: row;
@@ -301,9 +277,6 @@
301
277
  }
302
278
  }
303
279
 
304
- .dashboard-filters-section {
305
- margin: 0 0 1em;
306
- }
307
280
  .builder-grid .editor-heading {
308
281
  position: relative;
309
282
  right: -2em;
@@ -105,8 +105,14 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
105
105
  _.remove(newMultiDashboards, (_, index) => {
106
106
  return index === action.payload
107
107
  })
108
+ const config = {
109
+ ...state.config,
110
+ multiDashboards: newMultiDashboards,
111
+ ...newMultiDashboards[0],
112
+ activeDashboard: 0
113
+ }
108
114
  if (newMultiDashboards.length === 0) return { ...state, config: _.omit(state.config, 'multiDashboards') }
109
- return applyMultiDashboards(state, newMultiDashboards)
115
+ return applyMultiDashboards({ ...state, config }, newMultiDashboards)
110
116
  }
111
117
  case 'RENAME_DASHBOARD_TAB': {
112
118
  const newMultiDashboards = state.config.multiDashboards.map(dashboard => {
@@ -144,7 +150,8 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
144
150
  case 'SWITCH_CONFIG': {
145
151
  const slot = action.payload
146
152
  const newConfigFields = state.config.multiDashboards[slot]
147
- 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 } }
148
155
  }
149
156
  case 'TOGGLE_ROW': {
150
157
  const { rowIndex, colIndex } = action.payload
@@ -0,0 +1,7 @@
1
+ import { Action } from '@cdc/core/types/Action'
2
+
3
+ type ADD_ERROR_MESSAGE = Action<'ADD_ERROR_MESSAGE', string>
4
+ type DISMISS_ERROR_MESSAGE = Action<'DISMISS_ERROR_MESSAGE', number>
5
+
6
+ type errorMessagesActions = ADD_ERROR_MESSAGE | DISMISS_ERROR_MESSAGE
7
+ export default errorMessagesActions
@@ -0,0 +1,24 @@
1
+ import _ from 'lodash'
2
+ import errorMessagesActions from './errorMessage.actions'
3
+ import { devToolsWrapper } from '@cdc/core/helpers/withDevTools'
4
+
5
+ export type errorMessagesState = string[]
6
+
7
+ const reducer = (state: errorMessagesState, action: errorMessagesActions): errorMessagesState => {
8
+ switch (action.type) {
9
+ case 'ADD_ERROR_MESSAGE': {
10
+ const message = action.payload
11
+ return [...state, message]
12
+ }
13
+
14
+ case 'DISMISS_ERROR_MESSAGE': {
15
+ const messages = [...state]
16
+ _.remove(messages, (_, index) => {
17
+ return index === action.payload
18
+ })
19
+ return messages
20
+ }
21
+ }
22
+ }
23
+
24
+ export default devToolsWrapper<errorMessagesState, errorMessagesActions>(reducer)