@cdc/dashboard 4.24.12 → 4.25.2-25
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 +74365 -72646
- package/examples/all-components.json +529 -4607
- package/examples/dashboard-gallery.json +397 -397
- package/examples/private/DEV-10120.json +1294 -0
- package/examples/private/DEV-10527.json +564 -0
- package/examples/private/DEV-10586.json +54319 -0
- package/examples/private/DEV-10856.json +54319 -0
- package/examples/private/DEV-9989.json +229 -0
- package/examples/private/art-dashboard.json +2 -2
- package/examples/private/bird-flu-2.json +440 -0
- package/examples/private/bird-flu.json +413 -0
- package/examples/private/dashboard-config-ehdi.json +29915 -0
- package/examples/private/dashboard-map-filter.json +815 -0
- package/examples/private/dashboard-margins.js +15 -0
- package/examples/private/dataset.json +1452 -0
- package/examples/private/dev-10856-2.json +1348 -0
- package/examples/private/ehdi-data.json +29502 -0
- package/examples/private/exposure-source-h5-data.csv +26 -0
- package/examples/private/feelings.json +1 -0
- package/examples/private/nhis.json +1792 -0
- package/examples/private/workforce.json +2041 -0
- package/index.html +5 -8
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +5 -8
- package/src/CdcDashboardComponent.tsx +70 -60
- package/src/_stories/Dashboard.stories.tsx +63 -0
- package/src/_stories/_mock/dashboard-filter-asc.json +551 -0
- package/src/_stories/_mock/data-bite-dash-test.json +1 -0
- package/src/_stories/_mock/data-bite-dash-test_1.json +1 -0
- package/src/_stories/_mock/data-bite-dash-test_1_1.json +1 -0
- package/src/_stories/_mock/data-bite-dash-test_1_1_1.json +1 -0
- package/src/components/CollapsibleVisualizationRow.tsx +3 -3
- package/src/components/Column.tsx +12 -1
- package/src/components/DashboardFilters/DashboardFilters.tsx +14 -9
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +23 -8
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +13 -3
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +130 -41
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +10 -7
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +11 -12
- package/src/components/DashboardFilters/dashboardfilter.styles.css +2 -2
- package/src/components/ExpandCollapseButtons.tsx +1 -1
- package/src/components/Header/Header.tsx +1 -2
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +2 -2
- package/src/components/MultiConfigTabs/MultiTabs.tsx +1 -1
- package/src/components/VisualizationRow.tsx +13 -3
- package/src/components/Widget.tsx +9 -3
- package/src/helpers/addValuesToDashboardFilters.ts +6 -5
- package/src/helpers/apiFilterHelpers.ts +11 -6
- package/src/helpers/changeFilterActive.ts +17 -4
- package/src/helpers/getFilteredData.ts +13 -4
- package/src/helpers/getUpdateConfig.ts +11 -4
- package/src/helpers/loadAPIFilters.ts +6 -4
- package/src/helpers/tests/updatesChildFilters.test.ts +56 -0
- package/src/helpers/updateChildFilters.ts +50 -0
- package/src/index.tsx +1 -0
- package/src/scss/main.scss +1 -15
- package/src/store/dashboard.actions.ts +2 -2
- package/src/store/dashboard.reducer.ts +60 -29
- package/src/types/DashboardConfig.ts +2 -0
- package/src/types/SharedFilter.ts +1 -1
|
@@ -33,12 +33,13 @@ const labelHash = {
|
|
|
33
33
|
|
|
34
34
|
type WidgetConfig = AnyVisualization & { rowIdx: number; colIdx: number }
|
|
35
35
|
type WidgetProps = {
|
|
36
|
+
title: string
|
|
36
37
|
widgetConfig?: WidgetConfig
|
|
37
38
|
addVisualization?: Function
|
|
38
39
|
type: string
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
const Widget = ({ widgetConfig, addVisualization, type }: WidgetProps) => {
|
|
42
|
+
const Widget = ({ title, widgetConfig, addVisualization, type }: WidgetProps) => {
|
|
42
43
|
const { overlay } = useGlobalContext()
|
|
43
44
|
const { config, data } = useContext(DashboardContext)
|
|
44
45
|
const dispatch = useContext(DashboardDispatchContext)
|
|
@@ -74,9 +75,10 @@ const Widget = ({ widgetConfig, addVisualization, type }: WidgetProps) => {
|
|
|
74
75
|
|
|
75
76
|
const deleteWidget = () => {
|
|
76
77
|
if (!widgetConfig) return
|
|
78
|
+
|
|
77
79
|
dispatch({
|
|
78
80
|
type: 'DELETE_WIDGET',
|
|
79
|
-
payload: {
|
|
81
|
+
payload: { uid: widgetConfig.uid as string }
|
|
80
82
|
})
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -102,7 +104,10 @@ const Widget = ({ widgetConfig, addVisualization, type }: WidgetProps) => {
|
|
|
102
104
|
|
|
103
105
|
const editWidget = () => {
|
|
104
106
|
if (!widgetConfig) return
|
|
105
|
-
dispatch({
|
|
107
|
+
dispatch({
|
|
108
|
+
type: 'UPDATE_VISUALIZATION',
|
|
109
|
+
payload: { vizKey: widgetConfig.uid as string, configureData: { editing: true } }
|
|
110
|
+
})
|
|
106
111
|
loadSampleData()
|
|
107
112
|
}
|
|
108
113
|
|
|
@@ -156,6 +161,7 @@ const Widget = ({ widgetConfig, addVisualization, type }: WidgetProps) => {
|
|
|
156
161
|
)}
|
|
157
162
|
{iconHash[type]}
|
|
158
163
|
<span>{labelHash[type]}</span>
|
|
164
|
+
<span>{title}</span>
|
|
159
165
|
{widgetConfig?.newViz && type !== 'dashboardFilters' && (
|
|
160
166
|
<span onClick={editWidget} className='config-needed'>
|
|
161
167
|
Configuration needed
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
2
|
import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
|
|
3
3
|
import { SharedFilter } from '../types/SharedFilter'
|
|
4
|
+
import { handleSorting } from '@cdc/core/components/Filters'
|
|
4
5
|
|
|
5
6
|
// Gets filter values from dataset
|
|
6
7
|
const generateValuesForFilter = (columnName: string, data: Record<string, any[]>) => {
|
|
@@ -38,8 +39,8 @@ export const addValuesToDashboardFilters = (
|
|
|
38
39
|
if (filter.type === 'urlfilter') return filter
|
|
39
40
|
const filterCopy = _.cloneDeep(filter)
|
|
40
41
|
const filterValues = generateValuesForFilter(getSelector(filter), data)
|
|
41
|
-
|
|
42
42
|
filterCopy.values = filterValues
|
|
43
|
+
|
|
43
44
|
if (filterValues.length > 0) {
|
|
44
45
|
const queryStringFilterValue = getQueryStringFilterValue(filterCopy)
|
|
45
46
|
if (queryStringFilterValue) {
|
|
@@ -49,11 +50,11 @@ export const addValuesToDashboardFilters = (
|
|
|
49
50
|
const active: string[] = Array.isArray(filterCopy.active) ? filterCopy.active : [filterCopy.active]
|
|
50
51
|
filterCopy.active = active.filter(val => defaultValues.includes(val))
|
|
51
52
|
} else {
|
|
52
|
-
const
|
|
53
|
-
const defaultValue =
|
|
54
|
-
filterCopy.active = defaultValue
|
|
53
|
+
const hasResetLabel = filters.find(filter => filter.resetLabel)
|
|
54
|
+
const defaultValue = hasResetLabel ? hasResetLabel.resetLabel : filterCopy.active || filterCopy.values[0]
|
|
55
|
+
filterCopy.active = filterCopy.defaultValue || defaultValue
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
|
-
return filterCopy
|
|
58
|
+
return handleSorting(filterCopy)
|
|
58
59
|
})
|
|
59
60
|
}
|
|
@@ -134,10 +134,16 @@ export const setAutoLoadDefaultValue = (
|
|
|
134
134
|
): SharedFilter => {
|
|
135
135
|
const sharedFiltersCopy = _.cloneDeep(sharedFilters)
|
|
136
136
|
const sharedFilter = _.cloneDeep(sharedFiltersCopy[sharedFilterIndex])
|
|
137
|
-
|
|
138
|
-
const hasQueryParameter = sharedFilter.setByQueryParameter
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
|
|
138
|
+
const hasQueryParameter = sharedFilter.setByQueryParameter ? defaultQueryParamValue !== undefined : false
|
|
139
|
+
if (!autoLoadFilterIndexes.length || !dropdownOptions?.length) {
|
|
140
|
+
if (hasQueryParameter && sharedFilter.apiFilter) {
|
|
141
|
+
const subQueryValue = getQueryParam(sharedFilter.subGrouping?.setByQueryParameter)
|
|
142
|
+
const isNestedDropdown = subQueryValue !== undefined
|
|
143
|
+
sharedFilter.queuedActive = isNestedDropdown ? [defaultQueryParamValue, subQueryValue] : defaultQueryParamValue
|
|
144
|
+
}
|
|
145
|
+
return sharedFilter // no autoLoading happening
|
|
146
|
+
}
|
|
141
147
|
if (autoLoadFilterIndexes.includes(sharedFilterIndex) || hasQueryParameter) {
|
|
142
148
|
const filterParents = sharedFiltersCopy.filter(f => sharedFilter.parents?.includes(f.key))
|
|
143
149
|
const notAllParentFiltersSelected = filterParents.some(p => !(p.active || p.queuedActive))
|
|
@@ -148,11 +154,10 @@ export const setAutoLoadDefaultValue = (
|
|
|
148
154
|
setActiveNestedDropdown(dropdownOptions, sharedFilter)
|
|
149
155
|
} else {
|
|
150
156
|
const defaultValue = dropdownOptions[0]?.value
|
|
151
|
-
const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
|
|
152
157
|
if (!sharedFilter.active) {
|
|
153
158
|
sharedFilter.active = defaultQueryParamValue || defaultValue
|
|
154
159
|
} else {
|
|
155
|
-
const currentOption = dropdownOptions.find(option => option.value
|
|
160
|
+
const currentOption = dropdownOptions.find(option => option.value == sharedFilter.active) // loose equality required: 2017 should equal '2017'
|
|
156
161
|
sharedFilter.active = currentOption ? currentOption.value : defaultValue
|
|
157
162
|
}
|
|
158
163
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
2
|
import { FilterBehavior } from '../helpers/FilterBehavior'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getQueryParams,
|
|
5
|
+
removeQueryParam,
|
|
6
|
+
updateQueryParam,
|
|
7
|
+
updateQueryString
|
|
8
|
+
} from '@cdc/core/helpers/queryStringUtils'
|
|
4
9
|
import { SharedFilter } from '../types/SharedFilter'
|
|
5
10
|
import { DashboardFilters } from '../types/DashboardFilters'
|
|
6
11
|
import { FILTER_STYLE } from '../types/FilterStyles'
|
|
@@ -12,9 +17,11 @@ const handleChildren = (sharedFilters: SharedFilter[], parentIndex: number) => {
|
|
|
12
17
|
.filter(i => i !== null)
|
|
13
18
|
if (childFilterIndexes.length) {
|
|
14
19
|
childFilterIndexes.forEach(filterIndex => {
|
|
15
|
-
sharedFilters[filterIndex]
|
|
16
|
-
if (
|
|
17
|
-
|
|
20
|
+
const cur = sharedFilters[filterIndex]
|
|
21
|
+
if (cur.setByQueryParameter) removeQueryParam(cur.setByQueryParameter)
|
|
22
|
+
cur.active = ''
|
|
23
|
+
if (cur.subGrouping) {
|
|
24
|
+
cur.subGrouping.active = ''
|
|
18
25
|
}
|
|
19
26
|
})
|
|
20
27
|
}
|
|
@@ -45,7 +52,13 @@ export const changeFilterActive = (
|
|
|
45
52
|
updateQueryString(queryParams)
|
|
46
53
|
}
|
|
47
54
|
}
|
|
55
|
+
} else if (currentFilter.subGrouping) {
|
|
56
|
+
updateQueryParam(currentFilter.setByQueryParameter, value[0])
|
|
57
|
+
updateQueryParam(currentFilter.subGrouping.setByQueryParameter, value[1])
|
|
58
|
+
sharedFiltersCopy[filterIndex].queuedActive = value
|
|
48
59
|
} else {
|
|
60
|
+
const paramVal = Array.isArray(value) ? value.join(',') : value
|
|
61
|
+
if (currentFilter.setByQueryParameter) updateQueryParam(currentFilter.setByQueryParameter, paramVal)
|
|
49
62
|
sharedFiltersCopy[filterIndex].queuedActive = value
|
|
50
63
|
}
|
|
51
64
|
return [sharedFiltersCopy, handleChildren(sharedFiltersCopy, filterIndex)]
|
|
@@ -6,11 +6,18 @@ import { getFormattedData } from './getFormattedData'
|
|
|
6
6
|
import { getVizKeys } from './getVizKeys'
|
|
7
7
|
|
|
8
8
|
export const getApplicableFilters = (dashboard: Dashboard, key: string | number): false | SharedFilter[] => {
|
|
9
|
-
const c = dashboard.sharedFilters?.filter(
|
|
9
|
+
const c = dashboard.sharedFilters?.filter(
|
|
10
|
+
sharedFilter =>
|
|
11
|
+
(sharedFilter.usedBy && sharedFilter.usedBy.indexOf(`${key}`) !== -1) || sharedFilter.usedBy?.indexOf(key) !== -1
|
|
12
|
+
)
|
|
10
13
|
return c?.length > 0 ? c : false
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
export const getFilteredData = (
|
|
16
|
+
export const getFilteredData = (
|
|
17
|
+
state: DashboardState,
|
|
18
|
+
initialFilteredData?: Record<string, any>,
|
|
19
|
+
dataOverride?: Object
|
|
20
|
+
) => {
|
|
14
21
|
const newFilteredData = initialFilteredData || {}
|
|
15
22
|
const { config } = state
|
|
16
23
|
getVizKeys(config).forEach(key => {
|
|
@@ -18,7 +25,8 @@ export const getFilteredData = (state: DashboardState, initialFilteredData?: Rec
|
|
|
18
25
|
if (applicableFilters) {
|
|
19
26
|
const { dataKey, data, dataDescription } = config.visualizations[key]
|
|
20
27
|
const _data = (dataOverride || state.data)[dataKey] || data
|
|
21
|
-
const formattedData =
|
|
28
|
+
const formattedData =
|
|
29
|
+
dataOverride?.[dataKey] || (dataDescription ? getFormattedData(_data, dataDescription) : _data)
|
|
22
30
|
|
|
23
31
|
newFilteredData[key] = filterData(applicableFilters, formattedData)
|
|
24
32
|
}
|
|
@@ -29,7 +37,8 @@ export const getFilteredData = (state: DashboardState, initialFilteredData?: Rec
|
|
|
29
37
|
const { dataKey, data, dataDescription } = row
|
|
30
38
|
const _data = (dataOverride || state.data)[dataKey] || data
|
|
31
39
|
if (applicableFilters) {
|
|
32
|
-
const formattedData =
|
|
40
|
+
const formattedData =
|
|
41
|
+
dataOverride?.[dataKey] ?? dataDescription ? getFormattedData(_data, dataDescription) : _data
|
|
33
42
|
|
|
34
43
|
newFilteredData[index] = filterData(applicableFilters, formattedData)
|
|
35
44
|
} else {
|
|
@@ -31,7 +31,7 @@ export const getUpdateConfig =
|
|
|
31
31
|
const defaultValues = _filter.pivot ? _filter.values : _filter.values[0]
|
|
32
32
|
|
|
33
33
|
const queryStringFilterValue = getQueryStringFilterValue(_filter)
|
|
34
|
-
if(queryStringFilterValue){
|
|
34
|
+
if (queryStringFilterValue) {
|
|
35
35
|
_filter.active = queryStringFilterValue
|
|
36
36
|
} else {
|
|
37
37
|
_filter.active = _filter.active || defaultValues
|
|
@@ -57,19 +57,26 @@ export const getUpdateConfig =
|
|
|
57
57
|
visualizationKeys.forEach(visualizationKey => {
|
|
58
58
|
const row = vizRowColumnLocator[visualizationKey]
|
|
59
59
|
if (newConfig.rows[row]?.datakey) return // data configured on the row level
|
|
60
|
-
const applicableFilters = newConfig.dashboard.sharedFilters.filter(
|
|
60
|
+
const applicableFilters = newConfig.dashboard.sharedFilters.filter(
|
|
61
|
+
sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) !== -1
|
|
62
|
+
)
|
|
61
63
|
|
|
62
64
|
if (applicableFilters.length > 0) {
|
|
63
65
|
const visualization = newConfig.visualizations[visualizationKey]
|
|
64
66
|
const _newConfigDataSet = newConfig.datasets[visualization.dataKey]
|
|
65
|
-
const formattedData = getFormattedData(
|
|
67
|
+
const formattedData = getFormattedData(
|
|
68
|
+
_newConfigDataSet?.data || visualization.data,
|
|
69
|
+
visualization.dataDescription
|
|
70
|
+
)
|
|
66
71
|
const _data = formattedData || (dataOverride || state.data)[visualization.dataKey]
|
|
67
72
|
newFilteredData[visualizationKey] = filterData(applicableFilters, _data)
|
|
68
73
|
}
|
|
69
74
|
})
|
|
70
75
|
|
|
71
76
|
newConfig.rows.forEach((row, rowIndex) => {
|
|
72
|
-
const applicableFilters = newConfig.dashboard.sharedFilters.filter(
|
|
77
|
+
const applicableFilters = newConfig.dashboard.sharedFilters.filter(
|
|
78
|
+
sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(rowIndex) !== -1
|
|
79
|
+
)
|
|
73
80
|
|
|
74
81
|
if (applicableFilters.length > 0) {
|
|
75
82
|
const formattedData = getFormattedData(row.data, row.dataDescription)
|
|
@@ -42,7 +42,7 @@ export const loadAPIFiltersFactory = (
|
|
|
42
42
|
return Promise.all(
|
|
43
43
|
Object.keys(toFetch).map(
|
|
44
44
|
endpoint =>
|
|
45
|
-
new Promise<
|
|
45
|
+
new Promise<{ error: boolean }>(resolve => {
|
|
46
46
|
fetch(endpoint)
|
|
47
47
|
.then(resp => resp.json())
|
|
48
48
|
.then(data => {
|
|
@@ -67,13 +67,15 @@ export const loadAPIFiltersFactory = (
|
|
|
67
67
|
type: 'ADD_ERROR_MESSAGE',
|
|
68
68
|
payload: 'There was a problem returning data. Please try again.'
|
|
69
69
|
})
|
|
70
|
+
resolve({ error: true })
|
|
70
71
|
})
|
|
71
72
|
.finally(() => {
|
|
72
|
-
resolve()
|
|
73
|
+
resolve({ error: false })
|
|
73
74
|
})
|
|
74
75
|
})
|
|
75
76
|
)
|
|
76
|
-
).then(
|
|
77
|
+
).then(responses => {
|
|
78
|
+
const hasError = responses.some(({ error }) => error)
|
|
77
79
|
const toLoad = sharedFilters.reduce((acc, curr, index) => {
|
|
78
80
|
// the filter is autoloading and it hasn't finished yet
|
|
79
81
|
if (_autoLoadFilterIndexes.includes(index) && !curr.active) {
|
|
@@ -84,7 +86,7 @@ export const loadAPIFiltersFactory = (
|
|
|
84
86
|
}
|
|
85
87
|
return acc
|
|
86
88
|
}, [])
|
|
87
|
-
if (!toLoad.length || recursiveLimit === 0) {
|
|
89
|
+
if (hasError || !toLoad.length || recursiveLimit === 0) {
|
|
88
90
|
setAPIFilterDropdowns(newDropdowns)
|
|
89
91
|
dispatch({ type: 'SET_SHARED_FILTERS', payload: sharedFilters })
|
|
90
92
|
return sharedFilters
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SharedFilter } from '../../types/SharedFilter'
|
|
2
|
+
import { updateChildFilters } from '../updateChildFilters'
|
|
3
|
+
|
|
4
|
+
describe('updateChildFilters', () => {
|
|
5
|
+
it('should filter data based on the provided filters', () => {
|
|
6
|
+
const filters = [
|
|
7
|
+
{
|
|
8
|
+
tier: 1,
|
|
9
|
+
columnName: 'name',
|
|
10
|
+
active: 'John',
|
|
11
|
+
key: 'Parent Filter',
|
|
12
|
+
values: ['John', 'Kelly', 'Norman', 'Jane']
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
tier: 2,
|
|
16
|
+
columnName: 'lastName',
|
|
17
|
+
active: '',
|
|
18
|
+
key: 'Child Filter',
|
|
19
|
+
parents: 'Parent Filter',
|
|
20
|
+
values: ['Deer', 'Roberts']
|
|
21
|
+
}
|
|
22
|
+
] as SharedFilter[]
|
|
23
|
+
const data = {
|
|
24
|
+
vizKey: [
|
|
25
|
+
[
|
|
26
|
+
{ name: 'John', lastName: 'Deer' },
|
|
27
|
+
{ name: 'John', lastName: 'Roberts' },
|
|
28
|
+
{ name: 'Kelly', lastName: 'Adams' },
|
|
29
|
+
{ name: 'Norman', lastName: 'Sally' },
|
|
30
|
+
{ name: 'Jane', lastName: 'Gorman' }
|
|
31
|
+
]
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let exprectedResult = [
|
|
36
|
+
{
|
|
37
|
+
tier: 1,
|
|
38
|
+
columnName: 'name',
|
|
39
|
+
active: 'John',
|
|
40
|
+
key: 'Parent Filter',
|
|
41
|
+
values: ['John', 'Kelly', 'Norman', 'Jane']
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
tier: 2,
|
|
45
|
+
columnName: 'lastName',
|
|
46
|
+
active: '',
|
|
47
|
+
key: 'Child Filter',
|
|
48
|
+
parents: 'Parent Filter',
|
|
49
|
+
values: ['Deer', 'Roberts'] // updated values only
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
const result = updateChildFilters(filters, data)
|
|
53
|
+
|
|
54
|
+
expect(result).toEqual(exprectedResult)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { SharedFilter } from '../types/SharedFilter'
|
|
2
|
+
import _ from 'lodash'
|
|
3
|
+
|
|
4
|
+
export const updateChildFilters = (newSharedFilters: SharedFilter[], data: Record<string, any>): SharedFilter[] => {
|
|
5
|
+
const dataSet = Object.values(data).flat()
|
|
6
|
+
|
|
7
|
+
// Find indexes of all child filters
|
|
8
|
+
const childFilterIndexes: number[] = newSharedFilters
|
|
9
|
+
.map((filter, index) => (filter.type === 'datafilter' && filter.parents ? index : -1))
|
|
10
|
+
.filter(index => index !== -1)
|
|
11
|
+
if (childFilterIndexes.length === 0) return newSharedFilters
|
|
12
|
+
|
|
13
|
+
// deep copy of the shared filters
|
|
14
|
+
const updatedFilters = _.cloneDeep(newSharedFilters)
|
|
15
|
+
|
|
16
|
+
// Update each child filter
|
|
17
|
+
childFilterIndexes.forEach(childIndex => {
|
|
18
|
+
const childFilter: SharedFilter = newSharedFilters[childIndex]
|
|
19
|
+
const parentFilter: SharedFilter = newSharedFilters.find(
|
|
20
|
+
filter => String(childFilter.parents) === String(filter.key)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if (parentFilter) {
|
|
24
|
+
// Filter dataset based on parent's active value
|
|
25
|
+
const parentsActiveValues: string[] = dataSet.filter((d: Record<string, any>) => {
|
|
26
|
+
return parentFilter.active?.includes(d[parentFilter.columnName])
|
|
27
|
+
})
|
|
28
|
+
// Get unique active values for the child filter
|
|
29
|
+
const childFilterValues = _.uniq(parentsActiveValues.map(d => d[childFilter.columnName]).filter(Boolean))
|
|
30
|
+
|
|
31
|
+
// Update the child filter if unique values exist
|
|
32
|
+
if (childFilterValues.length > 0) {
|
|
33
|
+
const isChildMultiSelect = childFilter.filterStyle === 'multi-select'
|
|
34
|
+
const activeValue = isChildMultiSelect
|
|
35
|
+
? childFilterValues
|
|
36
|
+
: childFilter.active
|
|
37
|
+
? childFilter.active
|
|
38
|
+
: childFilter.defaultValue
|
|
39
|
+
? childFilter.defaultValue
|
|
40
|
+
: childFilterValues[0]
|
|
41
|
+
updatedFilters[childIndex] = {
|
|
42
|
+
...childFilter,
|
|
43
|
+
values: childFilterValues,
|
|
44
|
+
active: activeValue
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
return updatedFilters
|
|
50
|
+
}
|
package/src/index.tsx
CHANGED
package/src/scss/main.scss
CHANGED
|
@@ -138,7 +138,6 @@
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
.btn {
|
|
141
|
-
|
|
142
141
|
// Expand and Collapse Buttons for Multiviz Dashboard
|
|
143
142
|
&.expand-collapse-buttons {
|
|
144
143
|
background-color: var(--lightestGray);
|
|
@@ -163,13 +162,6 @@
|
|
|
163
162
|
margin: 15px 0 0;
|
|
164
163
|
}
|
|
165
164
|
|
|
166
|
-
.data-table-container {
|
|
167
|
-
margin: 20px 0 0;
|
|
168
|
-
&.download-link-above {
|
|
169
|
-
margin: 0;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
165
|
.collapsable-multiviz-container {
|
|
174
166
|
position: relative;
|
|
175
167
|
border: var(--lightGray) 1px solid;
|
|
@@ -193,16 +185,12 @@
|
|
|
193
185
|
position: relative;
|
|
194
186
|
}
|
|
195
187
|
@include breakpoint(xs) {
|
|
196
|
-
|
|
197
|
-
font-size: $font-small + 0.2em;
|
|
188
|
+
font-size: 0.9em;
|
|
198
189
|
}
|
|
199
190
|
}
|
|
200
191
|
.data-table-heading {
|
|
201
192
|
display: none;
|
|
202
193
|
}
|
|
203
|
-
.table-container {
|
|
204
|
-
margin: 0 1em;
|
|
205
|
-
}
|
|
206
194
|
}
|
|
207
195
|
|
|
208
196
|
.dashboard-download-link {
|
|
@@ -283,8 +271,6 @@
|
|
|
283
271
|
}
|
|
284
272
|
|
|
285
273
|
.cdc-dashboard-inner-container {
|
|
286
|
-
margin: 1em;
|
|
287
|
-
|
|
288
274
|
&.is-editor {
|
|
289
275
|
margin-top: 7em;
|
|
290
276
|
}
|
|
@@ -9,12 +9,12 @@ import { SharedFilter } from '../types/SharedFilter'
|
|
|
9
9
|
type ADD_FOOTNOTE = Action<'ADD_FOOTNOTE', { id: string; rowIndex: number; config: Footnotes }>
|
|
10
10
|
type ADD_VISUALIZATION = Action<'ADD_VISUALIZATION', { rowIdx: number; colIdx: number; newViz: AnyVisualization }>
|
|
11
11
|
type APPLY_CONFIG = Action<'APPLY_CONFIG', [Config, Object?]>
|
|
12
|
-
type DELETE_WIDGET = Action<'DELETE_WIDGET', {
|
|
12
|
+
type DELETE_WIDGET = Action<'DELETE_WIDGET', { uid: string }>
|
|
13
13
|
type MOVE_VISUALIZATION = Action<
|
|
14
14
|
'MOVE_VISUALIZATION',
|
|
15
15
|
{ rowIdx: number; colIdx: number; widget: AnyVisualization & { rowIdx: number; colIdx: number } }
|
|
16
16
|
>
|
|
17
|
-
type SET_CONFIG = Action<'SET_CONFIG', Partial<Config
|
|
17
|
+
type SET_CONFIG = Action<'SET_CONFIG', Partial<Config> & { activeDashboard?: number }>
|
|
18
18
|
type UPDATE_CONFIG = Action<'UPDATE_CONFIG', [Config, Object?]>
|
|
19
19
|
type SET_DATA = Action<'SET_DATA', Record<string, any[]>>
|
|
20
20
|
type SET_LOADING = Action<'SET_LOADING', boolean>
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
2
|
import { getUpdateConfig } from '../helpers/getUpdateConfig'
|
|
3
|
-
import { MultiDashboardConfig } from '../types/MultiDashboard'
|
|
3
|
+
import { MultiDashboard, MultiDashboardConfig } from '../types/MultiDashboard'
|
|
4
4
|
import DashboardActions from './dashboard.actions'
|
|
5
5
|
import { devToolsWrapper } from '@cdc/core/helpers/withDevTools'
|
|
6
6
|
import { Tab } from '../types/Tab'
|
|
7
|
-
import {
|
|
7
|
+
import { Dashboard } from '../types/Dashboard'
|
|
8
8
|
import { ConfigRow } from '../types/ConfigRow'
|
|
9
9
|
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
10
10
|
import { initialState } from '../DashboardContext'
|
|
11
11
|
|
|
12
12
|
type BlankMultiConfig = {
|
|
13
|
-
dashboard: Partial<
|
|
13
|
+
dashboard: Partial<Dashboard>
|
|
14
14
|
rows: Partial<ConfigRow>[]
|
|
15
15
|
visualizations: Record<string, Object>
|
|
16
16
|
table: Object
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const createBlankDashboard: () => BlankMultiConfig = () => ({
|
|
20
|
-
dashboard: {},
|
|
20
|
+
dashboard: { sharedFilters: [] },
|
|
21
21
|
rows: [{ columns: [{ width: 12 }] }],
|
|
22
22
|
visualizations: {},
|
|
23
23
|
table: {
|
|
@@ -44,7 +44,10 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
44
44
|
const newRows = state.config.rows.map((row, i) => (i === rowIndex ? { ...row, footnotesId: id } : row))
|
|
45
45
|
return {
|
|
46
46
|
...state,
|
|
47
|
-
config:
|
|
47
|
+
config: saveMultiChanges(
|
|
48
|
+
{ ...state.config, rows: newRows, visualizations: { ...state.config.visualizations, [id]: config } },
|
|
49
|
+
state.config.activeDashboard
|
|
50
|
+
)
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
case 'ADD_NEW_DASHBOARD': {
|
|
@@ -55,7 +58,7 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
55
58
|
}
|
|
56
59
|
case 'UPDATE_CONFIG': {
|
|
57
60
|
const [config, filteredData] = getUpdateConfig(state)(...action.payload)
|
|
58
|
-
return { ...state, config, filteredData }
|
|
61
|
+
return { ...state, config: saveMultiChanges(config, state.config.activeDashboard), filteredData }
|
|
59
62
|
}
|
|
60
63
|
case 'APPLY_CONFIG': {
|
|
61
64
|
// using advanced editor. Wipe all existing data and apply new config
|
|
@@ -68,10 +71,18 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
68
71
|
if (data) acc[key] = data
|
|
69
72
|
return acc
|
|
70
73
|
}, {})
|
|
71
|
-
return { ...initialState, config, filteredData, data }
|
|
74
|
+
return { ...initialState, config: saveMultiChanges(config, state.config.activeDashboard), filteredData, data }
|
|
72
75
|
}
|
|
73
76
|
case 'SET_CONFIG': {
|
|
74
|
-
|
|
77
|
+
if (
|
|
78
|
+
action.payload.activeDashboard === undefined ||
|
|
79
|
+
state.config.activeDashboard === action.payload.activeDashboard
|
|
80
|
+
) {
|
|
81
|
+
return {
|
|
82
|
+
...state,
|
|
83
|
+
config: saveMultiChanges({ ...state.config, ...action.payload }, action.payload.activeDashboard)
|
|
84
|
+
}
|
|
85
|
+
} else return state // ignore SET_CONFIG calls that have the wrong activeDashboard due to async api fetching
|
|
75
86
|
}
|
|
76
87
|
case 'SET_DATA': {
|
|
77
88
|
return { ...state, data: action.payload }
|
|
@@ -88,14 +99,10 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
88
99
|
case 'SET_SHARED_FILTERS': {
|
|
89
100
|
const newSharedFilters = action.payload
|
|
90
101
|
const newDashboardConfig = { ...state.config.dashboard, sharedFilters: newSharedFilters }
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
newMultiDashboards[saveSlot].dashboard = newDashboardConfig
|
|
95
|
-
const newState = applyMultiDashboards(state, newMultiDashboards)
|
|
96
|
-
return { ...newState, config: { ...newState.config, dashboard: newDashboardConfig } }
|
|
102
|
+
return {
|
|
103
|
+
...state,
|
|
104
|
+
config: saveMultiChanges({ ...state.config, dashboard: newDashboardConfig }, state.config.activeDashboard)
|
|
97
105
|
}
|
|
98
|
-
return { ...state, config: { ...state.config, dashboard: newDashboardConfig } }
|
|
99
106
|
}
|
|
100
107
|
case 'SET_TAB_SELECTED': {
|
|
101
108
|
return { ...state, tabSelected: action.payload }
|
|
@@ -138,7 +145,8 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
138
145
|
const label = newMultiDashboards[saveSlot].label
|
|
139
146
|
const toSave = _.pick(state.config, ['dashboard', 'visualizations', 'rows'])
|
|
140
147
|
newMultiDashboards[saveSlot] = { ...toSave, label }
|
|
141
|
-
|
|
148
|
+
const newConfig = saveMultiChanges(state.config, saveSlot)
|
|
149
|
+
return { ...state, config: newConfig }
|
|
142
150
|
}
|
|
143
151
|
case 'INITIALIZE_MULTIDASHBOARDS': {
|
|
144
152
|
const label = 'New Dashboard 1'
|
|
@@ -171,7 +179,10 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
171
179
|
newRows[rowIdx].columns[colIdx].widget = vizKey
|
|
172
180
|
return {
|
|
173
181
|
...state,
|
|
174
|
-
config:
|
|
182
|
+
config: saveMultiChanges(
|
|
183
|
+
{ ...state.config, visualizations: { ...state.config.visualizations, [vizKey]: newViz }, rows: newRows },
|
|
184
|
+
state.config.activeDashboard
|
|
185
|
+
)
|
|
175
186
|
}
|
|
176
187
|
}
|
|
177
188
|
case 'MOVE_VISUALIZATION': {
|
|
@@ -181,7 +192,7 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
181
192
|
newRows[rowIdx].columns[colIdx].widget = widget.uid
|
|
182
193
|
return {
|
|
183
194
|
...state,
|
|
184
|
-
config: { ...state.config, rows: newRows }
|
|
195
|
+
config: saveMultiChanges({ ...state.config, rows: newRows }, state.config.activeDashboard)
|
|
185
196
|
}
|
|
186
197
|
}
|
|
187
198
|
case 'UPDATE_VISUALIZATION': {
|
|
@@ -189,7 +200,10 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
189
200
|
const updatedViz = { ...state.config.visualizations[vizKey], ...configureData } as AnyVisualization
|
|
190
201
|
return {
|
|
191
202
|
...state,
|
|
192
|
-
config:
|
|
203
|
+
config: saveMultiChanges(
|
|
204
|
+
{ ...state.config, visualizations: { ...state.config.visualizations, [vizKey]: updatedViz } },
|
|
205
|
+
state.config.activeDashboard
|
|
206
|
+
)
|
|
193
207
|
}
|
|
194
208
|
}
|
|
195
209
|
case 'UPDATE_ROW': {
|
|
@@ -200,12 +214,11 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
200
214
|
}
|
|
201
215
|
return row
|
|
202
216
|
})
|
|
203
|
-
return { ...state, config: { ...state.config, rows: newRows } }
|
|
217
|
+
return { ...state, config: saveMultiChanges({ ...state.config, rows: newRows }, state.config.activeDashboard) }
|
|
204
218
|
}
|
|
205
219
|
case 'DELETE_WIDGET': {
|
|
206
|
-
const {
|
|
220
|
+
const { uid } = action.payload
|
|
207
221
|
const newRows = _.cloneDeep(state.config.rows)
|
|
208
|
-
newRows[rowIdx].columns[colIdx].widget = null
|
|
209
222
|
const newVisualizations = _.cloneDeep(state.config.visualizations)
|
|
210
223
|
delete newVisualizations[uid]
|
|
211
224
|
const newSharedFilters = _.cloneDeep(state.config.dashboard.sharedFilters)
|
|
@@ -216,14 +229,23 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
216
229
|
}
|
|
217
230
|
})
|
|
218
231
|
}
|
|
232
|
+
|
|
233
|
+
const filteredRows = _.map(newRows, row => ({
|
|
234
|
+
...row,
|
|
235
|
+
columns: _.filter(row.columns, column => column.widget !== uid)
|
|
236
|
+
}))
|
|
237
|
+
|
|
219
238
|
return {
|
|
220
239
|
...state,
|
|
221
|
-
config:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
240
|
+
config: saveMultiChanges(
|
|
241
|
+
{
|
|
242
|
+
...state.config,
|
|
243
|
+
dashboard: { ...state.config.dashboard, sharedFilters: newSharedFilters },
|
|
244
|
+
visualizations: newVisualizations,
|
|
245
|
+
rows: filteredRows
|
|
246
|
+
},
|
|
247
|
+
state.config.activeDashboard
|
|
248
|
+
)
|
|
227
249
|
}
|
|
228
250
|
}
|
|
229
251
|
default:
|
|
@@ -231,7 +253,16 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
231
253
|
}
|
|
232
254
|
}
|
|
233
255
|
|
|
234
|
-
const
|
|
256
|
+
const saveMultiChanges = (config: MultiDashboardConfig, saveSlot?: number): MultiDashboardConfig => {
|
|
257
|
+
if (saveSlot === undefined || !config.multiDashboards) return config
|
|
258
|
+
const newMultiDashboards = [...config.multiDashboards]
|
|
259
|
+
const label = newMultiDashboards[saveSlot].label
|
|
260
|
+
const toSave = _.pick(config, ['dashboard', 'visualizations', 'rows'])
|
|
261
|
+
newMultiDashboards[saveSlot] = { ...toSave, label }
|
|
262
|
+
return { ...config, multiDashboards: newMultiDashboards }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const applyMultiDashboards = (state: DashboardState, newMultiDashboards: MultiDashboard[]): DashboardState => ({
|
|
235
266
|
...state,
|
|
236
267
|
config: { ...state.config, multiDashboards: newMultiDashboards }
|
|
237
268
|
})
|
|
@@ -5,6 +5,7 @@ import { ConfigRow } from './ConfigRow'
|
|
|
5
5
|
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
6
6
|
import { Table } from '@cdc/core/types/Table'
|
|
7
7
|
import { Dashboard } from './Dashboard'
|
|
8
|
+
import { Version } from '@cdc/core/types/Version'
|
|
8
9
|
|
|
9
10
|
export type DashboardConfig = DataSet & {
|
|
10
11
|
dashboard: Dashboard
|
|
@@ -18,4 +19,5 @@ export type DashboardConfig = DataSet & {
|
|
|
18
19
|
runtime: Runtime
|
|
19
20
|
downloadImageButton: boolean
|
|
20
21
|
downloadPdfButton: boolean
|
|
22
|
+
version: Version
|
|
21
23
|
}
|