@cdc/dashboard 4.24.2 → 4.24.4

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 (52) hide show
  1. package/dist/cdcdashboard.js +128512 -99417
  2. package/examples/chart-data.json +5409 -0
  3. package/examples/full-dash-test.json +14643 -0
  4. package/examples/full-dashboard.json +10036 -0
  5. package/examples/sankey.json +5218 -0
  6. package/index.html +4 -3
  7. package/package.json +11 -10
  8. package/src/CdcDashboard.tsx +129 -124
  9. package/src/CdcDashboardComponent.tsx +316 -441
  10. package/src/DashboardContext.tsx +4 -1
  11. package/src/_stories/Dashboard.stories.tsx +79 -36
  12. package/src/_stories/_mock/api-filter-chart.json +11 -35
  13. package/src/_stories/_mock/api-filter-map.json +17 -31
  14. package/src/_stories/_mock/dashboard-gallery.json +523 -534
  15. package/src/_stories/_mock/multi-viz.json +378 -0
  16. package/src/_stories/_mock/pivot-filter.json +161 -0
  17. package/src/_stories/_mock/standalone-table.json +122 -0
  18. package/src/_stories/_mock/toggle-example.json +4035 -0
  19. package/src/components/DataDesignerModal.tsx +145 -0
  20. package/src/components/EditorWrapper/EditorWrapper.tsx +52 -0
  21. package/src/components/EditorWrapper/editor-wrapper.style.css +13 -0
  22. package/src/components/Filters.tsx +88 -0
  23. package/src/components/Grid.tsx +3 -1
  24. package/src/components/Header/FilterModal.tsx +506 -0
  25. package/src/components/Header/Header.tsx +25 -465
  26. package/src/components/Row.tsx +65 -29
  27. package/src/components/Toggle/Toggle.tsx +36 -0
  28. package/src/components/Toggle/index.tsx +1 -0
  29. package/src/components/Toggle/toggle-style.css +34 -0
  30. package/src/components/VisualizationRow.tsx +174 -0
  31. package/src/components/VisualizationsPanel.tsx +13 -3
  32. package/src/components/Widget.tsx +28 -126
  33. package/src/helpers/filterData.ts +75 -50
  34. package/src/helpers/generateValuesForFilter.ts +2 -12
  35. package/src/helpers/getApiFilterKey.ts +5 -0
  36. package/src/helpers/getFilteredData.ts +39 -0
  37. package/src/helpers/getUpdateConfig.ts +39 -22
  38. package/src/helpers/getVizConfig.ts +31 -0
  39. package/src/helpers/getVizRowColumnLocator.ts +9 -0
  40. package/src/helpers/iconHash.tsx +34 -0
  41. package/src/helpers/tests/filterData.test.ts +149 -0
  42. package/src/images/icon-toggle.svg +1 -0
  43. package/src/scss/grid.scss +10 -3
  44. package/src/scss/main.scss +11 -0
  45. package/src/store/dashboard.actions.ts +35 -3
  46. package/src/store/dashboard.reducer.ts +33 -2
  47. package/src/types/APIFilter.ts +4 -5
  48. package/src/types/ConfigRow.ts +13 -2
  49. package/src/types/DataSet.ts +11 -8
  50. package/src/types/InitialState.ts +2 -1
  51. package/src/types/SharedFilter.ts +6 -3
  52. package/src/types/Tab.ts +1 -0
@@ -1,73 +1,98 @@
1
+ import _ from 'lodash'
1
2
  import { SharedFilter } from '../types/SharedFilter'
2
- import { generateValuesForFilter } from './generateValuesForFilter'
3
3
 
4
4
  const findFilterTier = (filters: SharedFilter[], sharedFilter: SharedFilter) => {
5
5
  if (!sharedFilter.parents?.length) {
6
6
  return 1
7
7
  } else {
8
- let parent = filters.find(filter => sharedFilter.parents!.includes(filter.key))
8
+ const parent = filters.find(filter => sharedFilter.parents!.includes(filter.key))
9
9
  if (!parent) return 1
10
10
  return 1 + findFilterTier(filters, parent)
11
11
  }
12
12
  }
13
13
 
14
- export const filterData = (filters: SharedFilter[], _data: Object[], filterBehavior) => {
15
- if (_data) {
16
- let maxTier = 1
17
- filters.forEach(sharedFilter => {
18
- sharedFilter.tier = findFilterTier(filters, sharedFilter)
19
- })
20
-
21
- filters.forEach(sharedFilter => {
22
- if (sharedFilter.tier && sharedFilter.tier > maxTier) {
23
- maxTier = sharedFilter.tier
24
- }
25
- })
26
-
27
- let filteredData = _data
28
- // TODO triple loop??
29
- for (let i = 0; i < maxTier; i++) {
30
- let filteredDataSubTier: any[] = []
31
-
32
- filteredData.forEach(row => {
33
- let add = true
14
+ function getMaxTierAndSetFilterTiers(filters: SharedFilter[]): number {
15
+ let maxTier = 1
16
+ filters.forEach(sharedFilter => {
17
+ sharedFilter.tier = findFilterTier(filters, sharedFilter)
18
+ if (sharedFilter.tier > maxTier) {
19
+ maxTier = sharedFilter.tier
20
+ }
21
+ })
22
+ return maxTier
23
+ }
34
24
 
35
- filters.forEach(filter => {
36
- // eslint-disable-next-line eqeqeq
37
- if (filter.type !== 'urlfilter' && ((!filter.tier && i === 0) || filter.tier === i + 1) && (filter.queuedActive || filter.active) && row[filter.columnName!] != (filter.queuedActive || filter.active)) {
38
- add = false
25
+ function filter(data, filters, condition) {
26
+ return data
27
+ ? data.filter(row => {
28
+ const found = filters.find(filter => {
29
+ if (filter.pivot) return false
30
+ const currentValue = row[filter.columnName]
31
+ const selectedValue = filter.queuedActive || filter.active
32
+ const isNotTheSelectedValue = selectedValue && currentValue != selectedValue
33
+ const isFirstOccurrenceOfTier = filter.tier === condition
34
+ if (filter.type !== 'urlfilter' && isFirstOccurrenceOfTier && isNotTheSelectedValue) {
35
+ return true
39
36
  }
40
37
  })
41
-
42
- if (add) filteredDataSubTier.push(row)
43
- })
44
-
45
- filters.forEach(sharedFilter => {
46
- if (sharedFilter.tier && sharedFilter.tier === i + 2) {
47
- sharedFilter.values = generateValuesForFilter(sharedFilter.columnName, { data: filteredDataSubTier }, filterBehavior)
48
- if (sharedFilter.values.length > 0 && (!sharedFilter.active || sharedFilter.values.indexOf(sharedFilter.active) === -1)) {
49
- sharedFilter.active = sharedFilter.values[0]
50
- }
51
- }
38
+ return !found
52
39
  })
40
+ : []
41
+ }
53
42
 
54
- filteredData = filteredDataSubTier
43
+ function setFilterValuesAndActiveFilter(filters: SharedFilter[], filteredData: Object[], i: number) {
44
+ filters.forEach(sharedFilter => {
45
+ if (sharedFilter.pivot) {
46
+ sharedFilter.values = _.uniq(filteredData.map(row => row[sharedFilter.columnName]))
47
+ } else if (sharedFilter.tier === i + 2 && !Array.isArray(sharedFilter.active)) {
48
+ sharedFilter.values = _.uniq(filteredData.map(row => row[sharedFilter.columnName]))
49
+ const valueAlreadySelected = sharedFilter.values.includes(sharedFilter.active)
50
+ if (!valueAlreadySelected && sharedFilter.values.length > 0) {
51
+ sharedFilter.active = sharedFilter.values[0]
52
+ }
55
53
  }
54
+ })
55
+ }
56
56
 
57
- let filteredDataSubTier: any[] = []
58
- filteredData.forEach(row => {
59
- let add = true
60
-
61
- filters.forEach(filter => {
62
- // eslint-disable-next-line eqeqeq
63
- if (filter.type !== 'urlfilter' && filter.tier && filter.tier === maxTier - 1 && (filter.queuedActive || filter.active) && row[filter.columnName!] != (filter.queuedActive || filter.active)) {
64
- add = false
65
- }
66
- })
57
+ const pivotData = (data, pivotFilter: SharedFilter) => {
58
+ const pivotActive = pivotFilter.active as string[]
59
+ const inactive = pivotFilter.values.filter(value => !pivotActive.includes(value))
60
+ const pivotColumn = pivotFilter.columnName
61
+ const valueColumn = pivotFilter.pivot
62
+ const grouped = _.groupBy(data, val => val[pivotColumn])
63
+ const newData = []
64
+ for (const key in grouped) {
65
+ const group = grouped[key]
67
66
 
68
- if (add) filteredDataSubTier.push(row)
67
+ group.forEach((val, index) => {
68
+ const row = newData[index] || {}
69
+ if (!inactive.includes(key)) row[key] = val[valueColumn]
70
+ const toAdd = _.omit(val, [pivotColumn, valueColumn, ...inactive])
71
+ newData[index] = { ...row, ...toAdd }
69
72
  })
73
+ }
74
+ return newData
75
+ }
70
76
 
71
- return filteredDataSubTier
77
+ /** This function returns filtered data.
78
+ * It also manipulates the filters by adding: tiers, filterOptions, and default selections */
79
+ export const filterData = (filters: SharedFilter[], _data: Object[]): Object[] => {
80
+ const maxTier = getMaxTierAndSetFilterTiers(filters)
81
+
82
+ for (let i = 0; i < maxTier; i++) {
83
+ const lastIteration = i === maxTier - 1
84
+
85
+ const filteredData = filter(_data, filters, i + 1)
86
+
87
+ setFilterValuesAndActiveFilter(_.cloneDeep(filters), filteredData, i)
88
+
89
+ if (lastIteration) {
90
+ const pivotFilter = filters.find(filter => filter.pivot)
91
+ if (pivotFilter) {
92
+ return pivotData(filteredData, pivotFilter)
93
+ }
94
+ // not sure if this last run of filter() function is necessary.
95
+ return filter(filteredData, filters, maxTier - 1)
96
+ }
72
97
  }
73
98
  }
@@ -1,21 +1,11 @@
1
- import { FilterBehavior } from '../components/Header/Header'
2
-
3
- // Gets filter values from API response
4
- export const generateValuesForAPIFilter = (columnName, _data): string[] => {
5
- type Row = { [key: string]: any }
6
- return Object.values(_data)
7
- .filter(row => row && !!(row as Row)[columnName])
8
- .map(row => (row as Row)[columnName])
9
- }
10
-
11
1
  // Gets filter values from dataset
12
- export const generateValuesForFilter = (columnName, _data, filterBehavior) => {
2
+ export const generateValuesForFilter = (columnName, _data) => {
13
3
  const values: string[] = []
14
4
 
15
5
  Object.keys(_data).forEach(key => {
16
6
  _data[key]?.forEach(row => {
17
7
  const value = row[columnName]
18
- if (value && false === values.includes(value)) {
8
+ if (!values.includes(value)) {
19
9
  values.push(value)
20
10
  }
21
11
  })
@@ -0,0 +1,5 @@
1
+ import { APIFilter } from '../types/APIFilter'
2
+
3
+ export const getApiFilterKey = ({ apiEndpoint, heirarchyLookup }: APIFilter) => {
4
+ return apiEndpoint + (heirarchyLookup || '')
5
+ }
@@ -0,0 +1,39 @@
1
+ import { DashboardState } from '../store/dashboard.reducer'
2
+ import { Dashboard } from '../types/Dashboard'
3
+ import { SharedFilter } from '../types/SharedFilter'
4
+ import { filterData } from './filterData'
5
+ import { getFormattedData } from './getFormattedData'
6
+ import { getVizKeys } from './getVizKeys'
7
+
8
+ export const getApplicableFilters = (dashboard: Dashboard, key: string | number): false | SharedFilter[] => {
9
+ const c = dashboard.sharedFilters?.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(`${key}`) !== -1)
10
+ return c?.length > 0 ? c : false
11
+ }
12
+
13
+ export const getFilteredData = (state: DashboardState, initialFilteredData = {}, dataOverride?: Object) => {
14
+ const newFilteredData = initialFilteredData
15
+ const { config } = state
16
+ getVizKeys(config).forEach(key => {
17
+ const applicableFilters = getApplicableFilters(config.dashboard, key)
18
+ if (applicableFilters) {
19
+ const { dataKey, data, dataDescription } = config.visualizations[key]
20
+ const _data = state.data[dataKey] || data
21
+ const formattedData = dataOverride?.[dataKey] || (dataDescription ? getFormattedData(_data, dataDescription) : _data)
22
+
23
+ newFilteredData[key] = filterData(applicableFilters, formattedData)
24
+ }
25
+ })
26
+ config.rows.forEach((row, index) => {
27
+ if (row.dataKey) {
28
+ const applicableFilters = getApplicableFilters(config.dashboard, index)
29
+ if (applicableFilters) {
30
+ const { dataKey, data, dataDescription } = row
31
+ const _data = state.data[dataKey] || data
32
+ const formattedData = dataOverride?.[dataKey] ?? dataDescription ? getFormattedData(_data, dataDescription) : _data
33
+
34
+ newFilteredData[index] = filterData(applicableFilters, formattedData)
35
+ }
36
+ }
37
+ })
38
+ return newFilteredData
39
+ }
@@ -4,6 +4,9 @@ import { filterData } from './filterData'
4
4
  import { generateValuesForFilter } from './generateValuesForFilter'
5
5
  import { getFormattedData } from './getFormattedData'
6
6
  import { getVizKeys } from './getVizKeys'
7
+ import { getVizRowColumnLocator } from './getVizRowColumnLocator'
8
+
9
+ import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
7
10
 
8
11
  type UpdateState = Omit<DashboardState, 'config'> & {
9
12
  config?: DashboardConfig
@@ -14,17 +17,30 @@ export const getUpdateConfig =
14
17
  (newConfig, dataOverride?: Object): [Config, Object] => {
15
18
  let newFilteredData = {}
16
19
  let visualizationKeys = getVizKeys(newConfig)
17
- const setFilter = (filterIndex: number, key: string, value: any) => {
18
- newConfig.dashboard.sharedFilters[filterIndex][key] = value
19
- }
20
+
21
+ const vizRowColumnLocator = getVizRowColumnLocator(newConfig.rows)
22
+
20
23
  if (newConfig.dashboard.sharedFilters) {
21
24
  newConfig.dashboard.sharedFilters.forEach((filter, i) => {
22
25
  const filterIsSetByVizData = !!visualizationKeys.find(key => key === filter.setBy)
23
- let _filter = newConfig.dashboard.sharedFilters[i]
26
+ const _filter = newConfig.dashboard.sharedFilters[i]
24
27
 
25
- if (filterIsSetByVizData) {
26
- const filterValues = generateValuesForFilter(filter.columnName, dataOverride || state.data, state.config?.filterBehavior)
28
+ const setValuesAndActive = filterValues => {
29
+ _filter.values = filterValues
30
+ if (filterValues.length > 0) {
31
+ const defaultValues = _filter.pivot ? _filter.values : _filter.values[0]
27
32
 
33
+ const queryStringFilterValue = getQueryStringFilterValue(_filter)
34
+ if(queryStringFilterValue){
35
+ _filter.active = queryStringFilterValue
36
+ } else {
37
+ _filter.active = _filter.active || defaultValues
38
+ }
39
+ }
40
+ }
41
+
42
+ const filterValues = generateValuesForFilter(filter.columnName, dataOverride || state.data)
43
+ if (filterIsSetByVizData) {
28
44
  if (_filter.order === 'asc') {
29
45
  filterValues.sort()
30
46
  }
@@ -32,32 +48,33 @@ export const getUpdateConfig =
32
48
  filterValues.sort().reverse()
33
49
  }
34
50
 
35
- setFilter(i, 'values', filterValues)
36
- _filter = newConfig.dashboard.sharedFilters[i]
37
- if (filterValues.length > 0) {
38
- setFilter(i, 'active', _filter.active || _filter.values[0])
39
- }
40
- }
41
-
42
- if ((!filter.values || filter.values.length === 0) && filter.showDropdown) {
43
- const generatedValues = generateValuesForFilter(filter.columnName, dataOverride || state.data, state.config?.filterBehavior)
44
- setFilter(i, 'values', generatedValues)
45
- const _filter = newConfig.dashboard.sharedFilters[i]
46
- if (_filter.values.length > 0) {
47
- setFilter(i, 'active', filter.active || _filter.values[0])
48
- }
51
+ setValuesAndActive(filterValues)
52
+ } else if ((!filter.values || filter.values.length === 0) && filter.showDropdown) {
53
+ setValuesAndActive(filterValues)
49
54
  }
50
55
  })
51
56
 
52
57
  visualizationKeys.forEach(visualizationKey => {
53
- let applicableFilters = newConfig.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) !== -1)
58
+ const row = vizRowColumnLocator[visualizationKey]
59
+ if (newConfig.rows[row]?.datakey) return // data configured on the row level
60
+ const applicableFilters = newConfig.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) !== -1)
54
61
 
55
62
  if (applicableFilters.length > 0) {
56
63
  const visualization = newConfig.visualizations[visualizationKey]
57
64
  const _newConfigDataSet = newConfig.datasets[visualization.dataKey]
58
65
  const formattedData = getFormattedData(_newConfigDataSet?.data || visualization.data, visualization.dataDescription)
59
66
  const _data = formattedData || (dataOverride || state.data)[visualization.dataKey]
60
- newFilteredData[visualizationKey] = filterData(applicableFilters, _data, state.config?.filterBehavior)
67
+ newFilteredData[visualizationKey] = filterData(applicableFilters, _data)
68
+ }
69
+ })
70
+
71
+ newConfig.rows.forEach((row, rowIndex) => {
72
+ const applicableFilters = newConfig.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(rowIndex) !== -1)
73
+
74
+ if (applicableFilters.length > 0) {
75
+ const formattedData = getFormattedData(row.data, row.dataDescription)
76
+ const _data = formattedData || (dataOverride || state.data)[rowIndex]
77
+ newFilteredData[rowIndex] = filterData(applicableFilters, _data)
61
78
  }
62
79
  })
63
80
  }
@@ -0,0 +1,31 @@
1
+ import _ from 'lodash'
2
+ import { MultiDashboardConfig } from '../types/MultiDashboard'
3
+ import DataTransform from '@cdc/core/helpers/DataTransform'
4
+
5
+ const transform = new DataTransform()
6
+
7
+ export const getVizConfig = (visualizationKey: string, rowNumber: number, config: MultiDashboardConfig, data: Object, filteredData?: Object) => {
8
+ const visualizationConfig = _.cloneDeep(config.visualizations[visualizationKey])
9
+ const rowData = config.rows[rowNumber]
10
+ if (rowData?.dataKey) {
11
+ // data configured on the row
12
+ Object.assign(visualizationConfig, _.pick(rowData, ['dataKey', 'dataDescription', 'formattedData', 'data']))
13
+ }
14
+
15
+ if (visualizationConfig.formattedData) visualizationConfig.originalFormattedData = visualizationConfig.formattedData
16
+ const filteredVizData = filteredData?.[rowNumber] ?? filteredData?.[visualizationKey]
17
+
18
+ if (filteredVizData) {
19
+ visualizationConfig.data = filteredVizData
20
+ if (visualizationConfig.formattedData) {
21
+ visualizationConfig.formattedData = visualizationConfig.data
22
+ }
23
+ } else {
24
+ const dataKey = visualizationConfig.dataKey || 'backwards-compatibility'
25
+ visualizationConfig.data = data[dataKey]
26
+ if (visualizationConfig.formattedData) {
27
+ visualizationConfig.formattedData = transform.developerStandardize(visualizationConfig.data, visualizationConfig.dataDescription) || visualizationConfig.data
28
+ }
29
+ }
30
+ return visualizationConfig
31
+ }
@@ -0,0 +1,9 @@
1
+ import { ConfigRow } from '../types/ConfigRow'
2
+
3
+ export const getVizRowColumnLocator = (rows: ConfigRow[]) =>
4
+ rows.reduce((acc, curr, index) => {
5
+ curr.columns?.forEach((column, columnIndex) => {
6
+ if (column.widget !== undefined) acc[column.widget] = { row: index, column: columnIndex }
7
+ })
8
+ return acc
9
+ }, {})
@@ -0,0 +1,34 @@
1
+ import Icon from '@cdc/core/components/ui/Icon'
2
+ import { Visualization } from '@cdc/core/types/Visualization'
3
+
4
+ export const iconHash = {
5
+ 'data-bite': <Icon display='databite' base />,
6
+ Bar: <Icon display='chartBar' base />,
7
+ 'Spark Line': <Icon display='chartLine' />,
8
+ 'waffle-chart': <Icon display='grid' base />,
9
+ 'markup-include': <Icon display='code' base />,
10
+ Line: <Icon display='chartLine' base />,
11
+ Pie: <Icon display='chartPie' base />,
12
+ us: <Icon display='mapUsa' base />,
13
+ 'us-county': <Icon display='mapUsa' base />,
14
+ world: <Icon display='mapWorld' base />,
15
+ 'single-state': <Icon display='mapAl' base />,
16
+ gear: <Icon display='gear' base />,
17
+ tools: <Icon display='tools' base />,
18
+ 'filtered-text': <Icon display='filtered-text' base />,
19
+ 'filter-dropdowns': <Icon display='filter-dropdowns' base />,
20
+ table: <Icon display='table' base />,
21
+ Sankey: <Icon display='sankey' base />
22
+ }
23
+
24
+ export const getIcon = (visualization: Visualization) => {
25
+ const { type, visualizationType, general } = visualization
26
+ if (visualizationType) return iconHash[visualizationType]
27
+ if (general?.geoType) {
28
+ // for visualizations, mismatching state and state icon is not desired
29
+ // so instead of showing alabama as the default state icon we show the US icon.
30
+ if (general.geoType === 'single-state') return iconHash['us']
31
+ return iconHash[general.geoType]
32
+ }
33
+ return iconHash[type]
34
+ }
@@ -0,0 +1,149 @@
1
+ import { SharedFilter } from '../../types/SharedFilter'
2
+ import { filterData } from '../filterData'
3
+
4
+ describe('filterData', () => {
5
+ it('should filter data based on the provided filters', () => {
6
+ const filters = [
7
+ { tier: 1, columnName: 'name', active: 'John', queuedActive: 'John', fileName: 'abc', key: 'abc' },
8
+ { tier: 2, columnName: 'age', active: 30, queuedActive: 30, fileName: 'abc', key: 'abc' }
9
+ ] as SharedFilter[]
10
+ const data = [
11
+ { name: 'John', age: 30 },
12
+ { name: 'Jane', age: 25 },
13
+ { name: 'John', age: 35 },
14
+ { name: 'Jane', age: 30 }
15
+ ]
16
+
17
+ const result = filterData(filters, data)
18
+
19
+ expect(result).toEqual([{ name: 'John', age: 30 }])
20
+ })
21
+
22
+ it('filters with parents', () => {
23
+ const filters = [
24
+ { columnName: 'name', active: 'John', queuedActive: 'John', fileName: 'abc', key: 'abc' },
25
+ { columnName: 'age', active: 30, queuedActive: 30, fileName: 'abc', key: 'abc', parents: ['name'] }
26
+ ] as SharedFilter[]
27
+ const data = [
28
+ { name: 'John', age: 30 },
29
+ { name: 'Jane', age: 25 },
30
+ { name: 'John', age: 35 },
31
+ { name: 'Jane', age: 30 }
32
+ ]
33
+
34
+ const result = filterData(filters, data)
35
+
36
+ expect(result).toEqual([{ name: 'John', age: 30 }])
37
+ })
38
+
39
+ it('causes sideEffects to filters', () => {
40
+ // the side effect is not desired, but current functionality depends on the sideEffect.
41
+ // hopefully the side effect will be refactored in the future to be a returned value.
42
+ const filters = [
43
+ { columnName: 'name', active: 'John', queuedActive: 'John', fileName: 'abc', key: 'name' },
44
+ { columnName: 'age', fileName: 'abc', key: 'age' },
45
+ { columnName: 'color', fileName: 'abc', key: 'color', parents: ['age'] }
46
+ ] as SharedFilter[]
47
+ const data = [
48
+ { name: 'John', age: 30, color: 'blue' },
49
+ { name: 'Jane', age: 25, color: 'red' },
50
+ { name: 'John', age: 35, color: 'yellow' },
51
+ { name: 'Jane', age: 30, color: 'green' }
52
+ ]
53
+
54
+ const result = filterData(filters, data)
55
+
56
+ expect(result).toEqual([{ name: 'John', age: 30, color: 'blue' }])
57
+
58
+ const sideEffectOfFiltering = [
59
+ {
60
+ columnName: 'name',
61
+ active: 'John',
62
+ queuedActive: 'John',
63
+ fileName: 'abc',
64
+ key: 'name',
65
+ tier: 1
66
+ },
67
+ { columnName: 'age', fileName: 'abc', key: 'age', tier: 1 },
68
+ {
69
+ columnName: 'color',
70
+ fileName: 'abc',
71
+ key: 'color',
72
+ parents: ['age'],
73
+ tier: 2,
74
+ values: ['blue', 'yellow'],
75
+ active: 'blue'
76
+ }
77
+ ]
78
+ expect(filters).toEqual(sideEffectOfFiltering)
79
+ })
80
+
81
+ it('should not include data that does not meet the filter criteria', () => {
82
+ const filters = [
83
+ //{ columnName: 'apple', fileName: 'abc', key: 'banana' },
84
+ { columnName: 'color', active: 'red', queuedActive: 'red', fileName: 'abc', key: 'color' },
85
+ { columnName: 'name', fileName: 'abc', key: 'name' },
86
+ { columnName: 'age', fileName: 'abc', key: 'age', parents: ['name'] }
87
+ ] as SharedFilter[]
88
+ const data = [
89
+ { name: 'Jane', age: 30, color: 'blue' },
90
+ { name: 'John', age: 25, color: 'red' },
91
+ { name: 'John', age: 25, color: 'green' }
92
+ //{ name: 'John', age: 25, color: 'red', apple: 'banana' }
93
+ ]
94
+
95
+ const result = filterData(filters, data)
96
+ expect(result).toEqual([{ name: 'John', age: 25, color: 'red' }])
97
+ })
98
+
99
+ it('should pivot data based on the provided filters', () => {
100
+ const filters = [{ key: 'Race', type: 'datafilter', showDropdown: true, columnName: 'Race', pivot: 'Age-adjusted rate', usedBy: ['table1707935263149'] }] as SharedFilter[]
101
+ const data = [
102
+ {
103
+ Race: 'Hispanic or Latino',
104
+ 'Age-adjusted rate': '644.2',
105
+ Year: '2016'
106
+ },
107
+ {
108
+ Race: 'Non-Hispanic American Indian',
109
+ 'Age-adjusted rate': '636.1',
110
+ Year: '2016'
111
+ },
112
+ {
113
+ Race: 'Non-Hispanic Black',
114
+ 'Age-adjusted rate': '563.7',
115
+ Year: '2016'
116
+ },
117
+ {
118
+ Race: 'Hispanic or Latino',
119
+ 'Age-adjusted rate': '644.2',
120
+ Year: '2017'
121
+ },
122
+ {
123
+ Race: 'Non-Hispanic American Indian',
124
+ 'Age-adjusted rate': '636.1',
125
+ Year: '2017'
126
+ },
127
+ {
128
+ Race: 'Non-Hispanic Black',
129
+ 'Age-adjusted rate': '563.7',
130
+ Year: '2017'
131
+ }
132
+ ]
133
+
134
+ expect(filterData(filters, data)).toEqual([
135
+ {
136
+ 'Hispanic or Latino': '644.2',
137
+ 'Non-Hispanic American Indian': '636.1',
138
+ 'Non-Hispanic Black': '563.7',
139
+ Year: '2016'
140
+ },
141
+ {
142
+ 'Hispanic or Latino': '644.2',
143
+ 'Non-Hispanic American Indian': '636.1',
144
+ 'Non-Hispanic Black': '563.7',
145
+ Year: '2017'
146
+ }
147
+ ])
148
+ })
149
+ })
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M192 64C86 64 0 150 0 256S86 448 192 448H384c106 0 192-86 192-192s-86-192-192-192H192zm192 96a96 96 0 1 1 0 192 96 96 0 1 1 0-192z"/></svg>
@@ -35,8 +35,7 @@ $red: #f74242;
35
35
  flex-flow: row;
36
36
  width: 100%;
37
37
  position: relative;
38
- top: 0;
39
- padding: 1em;
38
+ padding: 2em 1em 1em;
40
39
  border: 1px solid #c2c2c2;
41
40
  transition: border 300ms cubic-bezier(0.16, 1, 0.3, 1);
42
41
  background-color: #f2f2f2;
@@ -77,7 +76,7 @@ $red: #f74242;
77
76
 
78
77
  .row-menu__btn:hover .row-menu__flyout {
79
78
  transition: width 0.2s cubic-bezier(0.16, 1, 0.3, 1);
80
- width: 144px;
79
+ width: 180px;
81
80
 
82
81
  li {
83
82
  display: flex;
@@ -330,6 +329,14 @@ $red: #f74242;
330
329
  .builder-row {
331
330
  position: relative;
332
331
 
332
+ .btn-configure-row {
333
+ background: none;
334
+ display: block;
335
+ position: absolute;
336
+ right: 1em;
337
+ top: 0;
338
+ }
339
+
333
340
  .widget__content {
334
341
  padding: 0 2em;
335
342
 
@@ -10,6 +10,11 @@
10
10
  margin-top: 20%;
11
11
  }
12
12
 
13
+ > .cove-editor__content {
14
+ width: 100% !important;
15
+ left: 0px;
16
+ }
17
+
13
18
  .editor-heading {
14
19
  background-color: #ddd;
15
20
  border-bottom: #c7c7c7 1px solid;
@@ -187,6 +192,9 @@
187
192
  .dashboard-row {
188
193
  display: flex;
189
194
  flex-direction: column;
195
+ &.toggle {
196
+ display: block;
197
+ }
190
198
  }
191
199
 
192
200
  .dashboard-col {
@@ -197,6 +205,9 @@
197
205
  margin-left: 0;
198
206
  margin-right: 0;
199
207
  }
208
+ &.hidden-toggle {
209
+ display: none;
210
+ }
200
211
  }
201
212
 
202
213
  .dashboard-col-12 {
@@ -1,13 +1,20 @@
1
1
  import type { DashboardConfig as Config } from '../types/DashboardConfig'
2
2
  import { type Action } from '@cdc/core/types/Action'
3
+ import { Tab } from '../types/Tab'
4
+ <<<<<<< HEAD
5
+ import { ConfigureData } from '@cdc/core/types/ConfigureData'
6
+ import { ConfigRow } from '../types/ConfigRow'
7
+ =======
8
+ import { SharedFilter } from '../types/SharedFilter'
9
+ >>>>>>> 35436844 (fixed api dropdowns)
3
10
 
4
- type SET_CONFIG = Action<'SET_CONFIG', Config>
11
+ type SET_CONFIG = Action<'SET_CONFIG', Partial<Config>>
5
12
  type UPDATE_CONFIG = Action<'UPDATE_CONFIG', [Config, Object?]>
6
13
  type SET_DATA = Action<'SET_DATA', Object>
7
14
  type SET_LOADING = Action<'SET_LOADING', boolean>
8
15
  type SET_PREVIEW = Action<'SET_PREVIEW', boolean>
9
16
  type SET_FILTERED_DATA = Action<'SET_FILTERED_DATA', Object>
10
- type SET_TAB_SELECTED = Action<'SET_TAB_SELECTED', number>
17
+ type SET_TAB_SELECTED = Action<'SET_TAB_SELECTED', Tab>
11
18
  type RENAME_DASHBOARD_TAB = Action<'RENAME_DASHBOARD_TAB', { current: string; new: string }>
12
19
  type INITIALIZE_MULTIDASHBOARDS = Action<'INITIALIZE_MULTIDASHBOARDS', undefined>
13
20
  type REMOVE_MULTIDASHBOARD_AT_INDEX = Action<'REMOVE_MULTIDASHBOARD_AT_INDEX', number>
@@ -15,6 +22,31 @@ type REORDER_MULTIDASHBOARDS = Action<'REORDER_MULTIDASHBOARDS', { currentIndex:
15
22
  type ADD_NEW_DASHBOARD = Action<'ADD_NEW_DASHBOARD', undefined>
16
23
  type SAVE_CURRENT_CHANGES = Action<'SAVE_CURRENT_CHANGES', undefined>
17
24
  type SWITCH_CONFIG = Action<'SWITCH_CONFIG', number>
25
+ type TOGGLE_ROW = Action<'TOGGLE_ROW', { rowIndex: number; colIndex: number }>
26
+ <<<<<<< HEAD
27
+ type UPDATE_VISUALIZATION = Action<'UPDATE_VISUALIZATION', { vizKey: string; configureData: Partial<ConfigureData> }>
28
+ type UPDATE_ROW = Action<'UPDATE_ROW', { rowIndex: number; rowData: Partial<ConfigRow> }>
29
+ =======
30
+ type SET_SHARED_FILTERS = Action<'SET_SHARED_FILTERS', SharedFilter[]>
31
+ >>>>>>> 35436844 (fixed api dropdowns)
18
32
 
19
- type DashboardActions = ADD_NEW_DASHBOARD | SET_CONFIG | UPDATE_CONFIG | REMOVE_MULTIDASHBOARD_AT_INDEX | RENAME_DASHBOARD_TAB | REORDER_MULTIDASHBOARDS | SAVE_CURRENT_CHANGES | SET_DATA | SET_LOADING | SET_PREVIEW | SET_FILTERED_DATA | SET_TAB_SELECTED | SWITCH_CONFIG | INITIALIZE_MULTIDASHBOARDS
33
+ type DashboardActions =
34
+ | ADD_NEW_DASHBOARD
35
+ | SET_CONFIG
36
+ | UPDATE_CONFIG
37
+ | REMOVE_MULTIDASHBOARD_AT_INDEX
38
+ | RENAME_DASHBOARD_TAB
39
+ | REORDER_MULTIDASHBOARDS
40
+ | SAVE_CURRENT_CHANGES
41
+ | SET_DATA
42
+ | SET_LOADING
43
+ | SET_PREVIEW
44
+ | SET_FILTERED_DATA
45
+ | SET_SHARED_FILTERS
46
+ | SET_TAB_SELECTED
47
+ | SWITCH_CONFIG
48
+ | INITIALIZE_MULTIDASHBOARDS
49
+ | TOGGLE_ROW
50
+ | UPDATE_VISUALIZATION
51
+ | UPDATE_ROW
20
52
  export default DashboardActions