@cdc/dashboard 4.25.11 → 4.26.1
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/Dynamic_Data.md +66 -0
- package/dist/cdcdashboard.js +78783 -76370
- package/examples/api-dashboard-data.json +272 -0
- package/examples/api-dashboard-years.json +11 -0
- package/examples/api-geographies-data.json +11 -0
- package/examples/private/cat-y.json +1235 -0
- package/examples/private/chronic-dash.json +1584 -0
- package/examples/private/map-issue.json +2260 -0
- package/examples/private/mpinc-state-reports.json +2260 -0
- package/examples/private/nwss/rsv.json +1240 -0
- package/examples/private/simple-dash.json +490 -0
- package/examples/private/test-dash.json +0 -0
- package/examples/private/test123.json +491 -0
- package/examples/test-dashboard-simple.json +503 -0
- package/index.html +24 -25
- package/package.json +12 -11
- package/src/CdcDashboardComponent.tsx +18 -2
- package/src/_stories/Dashboard.DataSetup.stories.tsx +203 -0
- package/src/_stories/Dashboard.stories.tsx +385 -1
- package/src/_stories/_mock/filter-cascade.json +3350 -0
- package/src/_stories/_mock/gallery-data-bite-dashboard.json +3500 -0
- package/src/_stories/_mock/nested-parent-child-filters.json +392 -0
- package/src/_stories/_mock/parent-child-filters.json +233 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +20 -11
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +89 -38
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +51 -29
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +146 -9
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +11 -7
- package/src/components/DataDesignerModal.tsx +6 -1
- package/src/components/Header/Header.tsx +51 -20
- package/src/components/VisualizationRow.tsx +71 -5
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +2 -3
- package/src/components/Widget/Widget.tsx +1 -1
- package/src/data/initial-state.js +1 -0
- package/src/helpers/addValuesToDashboardFilters.ts +15 -22
- package/src/helpers/changeFilterActive.ts +67 -65
- package/src/helpers/formatConfigBeforeSave.ts +6 -5
- package/src/helpers/getUpdateConfig.ts +91 -91
- package/src/helpers/tests/updatesChildFilters.test.ts +53 -22
- package/src/helpers/updateChildFilters.ts +50 -27
- package/src/scss/main.scss +141 -1
- package/src/test/CdcDashboard.test.jsx +9 -4
- package/src/types/Dashboard.ts +1 -0
- package/src/types/FilterStyles.ts +8 -7
- package/src/types/SharedFilter.ts +13 -0
- package/LICENSE +0 -201
- package/examples/private/DEV-10538.json +0 -407
- package/examples/private/DEV-11072.json +0 -7591
- package/examples/private/DEV-11405.json +0 -39112
- package/examples/private/delete.json +0 -32919
- package/examples/private/pedro.json +0 -1
|
@@ -93,6 +93,63 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
93
93
|
if (row.toggle) setToggled(0)
|
|
94
94
|
}, [config.activeDashboard, index])
|
|
95
95
|
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
// Trigger window resize event when tab changes to force chart re-render
|
|
98
|
+
if (row.toggle && toggledRow !== undefined) {
|
|
99
|
+
// Use setTimeout to ensure the d-none class has been removed first
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
window.dispatchEvent(new Event('resize'))
|
|
102
|
+
}, 50)
|
|
103
|
+
}
|
|
104
|
+
}, [toggledRow, row.toggle])
|
|
105
|
+
|
|
106
|
+
// Equalize TP5 data bite title heights in the same row
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
const rowElement = document.querySelector(`[data-row-index="${index}"]`)
|
|
109
|
+
if (!rowElement) return
|
|
110
|
+
|
|
111
|
+
const tp5Titles = Array.from(rowElement.querySelectorAll('.bite__style--tp5 .cdc-callout__heading'))
|
|
112
|
+
if (tp5Titles.length <= 1) return // No need to equalize if there's only one or none
|
|
113
|
+
|
|
114
|
+
const equalizeTP5Titles = () => {
|
|
115
|
+
// Reset heights first
|
|
116
|
+
tp5Titles.forEach((title: HTMLElement) => {
|
|
117
|
+
title.style.minHeight = ''
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// Calculate max height after reset
|
|
121
|
+
let maxHeight = 0
|
|
122
|
+
tp5Titles.forEach((title: HTMLElement) => {
|
|
123
|
+
const height = title.offsetHeight
|
|
124
|
+
if (height > maxHeight) maxHeight = height
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Apply max height to all titles
|
|
128
|
+
if (maxHeight > 0) {
|
|
129
|
+
tp5Titles.forEach((title: HTMLElement) => {
|
|
130
|
+
title.style.minHeight = `${maxHeight}px`
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Initial equalization
|
|
136
|
+
equalizeTP5Titles()
|
|
137
|
+
|
|
138
|
+
// Use ResizeObserver to watch for size changes in any of the titles
|
|
139
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
140
|
+
equalizeTP5Titles()
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Observe all titles
|
|
144
|
+
tp5Titles.forEach(title => {
|
|
145
|
+
resizeObserver.observe(title as Element)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
return () => {
|
|
149
|
+
resizeObserver.disconnect()
|
|
150
|
+
}
|
|
151
|
+
}, [index, row, config, filteredDataOverride])
|
|
152
|
+
|
|
96
153
|
const show = useMemo(() => {
|
|
97
154
|
if (row.toggle) {
|
|
98
155
|
return row.columns.map((col, i) => i === toggledRow)
|
|
@@ -166,13 +223,18 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
166
223
|
}
|
|
167
224
|
|
|
168
225
|
return (
|
|
169
|
-
<div
|
|
226
|
+
<div
|
|
227
|
+
className={`row${row.equalHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`}
|
|
228
|
+
key={`row__${index}`}
|
|
229
|
+
data-row-index={index}
|
|
230
|
+
>
|
|
170
231
|
{row.toggle && !inNoDataState && (
|
|
171
232
|
<Toggle row={row} visualizations={config.visualizations} active={toggledRow} setToggled={setToggled} />
|
|
172
233
|
)}
|
|
173
234
|
{row.columns.map((col, colIndex) => {
|
|
174
235
|
if (col.width) {
|
|
175
|
-
if (!col.widget)
|
|
236
|
+
if (!col.widget)
|
|
237
|
+
return <div key={`row__${index}__col__${colIndex}`} className={`col-12 col-md-${col.width}`}></div>
|
|
176
238
|
|
|
177
239
|
const visualizationConfig = getVizConfig(
|
|
178
240
|
col.widget,
|
|
@@ -212,9 +274,14 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
212
274
|
</a>
|
|
213
275
|
)
|
|
214
276
|
|
|
277
|
+
// Markup-includes with external URLs don't depend on dashboard data
|
|
278
|
+
const isMarkupIncludeWithoutDataDependency =
|
|
279
|
+
type === 'markup-include' && !visualizationConfig.dataKey && !visualizationConfig.data?.length
|
|
280
|
+
|
|
215
281
|
const hideVisualization =
|
|
216
282
|
inNoDataState &&
|
|
217
283
|
filterBehavior !== 'Apply Button' &&
|
|
284
|
+
!isMarkupIncludeWithoutDataDependency &&
|
|
218
285
|
(type !== 'dashboardFilters' || applyButtonNotClicked(visualizationConfig))
|
|
219
286
|
|
|
220
287
|
const shouldShow = row.toggle === undefined || (row.toggle && show[colIndex])
|
|
@@ -224,11 +291,10 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
224
291
|
sharedFilterIndexes &&
|
|
225
292
|
sharedFilterIndexes.filter(idx => config.dashboard.sharedFilters?.[idx]?.showDropdown === false).length ===
|
|
226
293
|
sharedFilterIndexes.length
|
|
227
|
-
const hasMarginBottom = !isLastRow && !hiddenDashboardFilters
|
|
228
294
|
|
|
229
295
|
const vizWrapperClass = `col-12 col-md-${col.width}${!shouldShow ? ' d-none' : ''}${
|
|
230
|
-
hideVisualization ? ' hide-parent-visualization' :
|
|
231
|
-
}`
|
|
296
|
+
hideVisualization ? ' hide-parent-visualization' : ''
|
|
297
|
+
}${hiddenDashboardFilters ? ' hidden-dashboard-filters' : ''}`
|
|
232
298
|
const link =
|
|
233
299
|
config.table && config.table.show && config.datasets && table && table.showDataTableLink
|
|
234
300
|
? tableLink
|
|
@@ -51,7 +51,6 @@ const addVisualization = (type, subType) => {
|
|
|
51
51
|
markupVariables: [],
|
|
52
52
|
showHeader: true,
|
|
53
53
|
srcUrl: '#example',
|
|
54
|
-
title: 'Markup Include',
|
|
55
54
|
useInlineHTML: true
|
|
56
55
|
}
|
|
57
56
|
newVisualizationConfig.theme = 'theme-blue'
|
|
@@ -81,7 +80,7 @@ const addVisualization = (type, subType) => {
|
|
|
81
80
|
|
|
82
81
|
const VisualizationsPanel = () => {
|
|
83
82
|
const [advancedEditing, setAdvancedEditing] = useState(false)
|
|
84
|
-
const { config } = useContext(DashboardContext)
|
|
83
|
+
const { config, isEditor } = useContext(DashboardContext)
|
|
85
84
|
const dispatch = useContext(DashboardDispatchContext)
|
|
86
85
|
const loadConfig = incomingConfig => {
|
|
87
86
|
const newConfig = !incomingConfig.multiDashboards
|
|
@@ -124,7 +123,7 @@ const VisualizationsPanel = () => {
|
|
|
124
123
|
loadConfig={loadConfig}
|
|
125
124
|
config={config}
|
|
126
125
|
convertStateToConfig={() => undefined}
|
|
127
|
-
stripConfig={stripConfig}
|
|
126
|
+
stripConfig={cfg => stripConfig(cfg, isEditor)}
|
|
128
127
|
onExpandCollapse={() => {
|
|
129
128
|
setAdvancedEditing(!advancedEditing)
|
|
130
129
|
}}
|
|
@@ -123,7 +123,7 @@ const Widget = ({
|
|
|
123
123
|
if (!widgetConfig) return
|
|
124
124
|
dispatch({
|
|
125
125
|
type: 'UPDATE_VISUALIZATION',
|
|
126
|
-
payload: { vizKey: widgetConfig.uid as string, configureData: { editing: true } }
|
|
126
|
+
payload: { vizKey: widgetConfig.uid as string, configureData: { editing: true, showEditorPanel: true } }
|
|
127
127
|
})
|
|
128
128
|
loadSampleData()
|
|
129
129
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
|
-
import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
|
|
2
|
+
import { getQueryStringFilterValue, isFilterHiddenByQuery } from '@cdc/core/helpers/queryStringUtils'
|
|
3
3
|
import { SharedFilter } from '../types/SharedFilter'
|
|
4
4
|
import { handleSorting } from '@cdc/core/components/Filters'
|
|
5
5
|
import { mergeCustomOrderValues } from '@cdc/core/helpers/mergeCustomOrderValues'
|
|
@@ -39,7 +39,11 @@ export const addValuesToDashboardFilters = (
|
|
|
39
39
|
if (filtersToSkip.includes(index)) return filter
|
|
40
40
|
if (filter.type === 'urlfilter') return filter
|
|
41
41
|
const filterCopy = _.cloneDeep(filter)
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
// Only generate values from data if not pre-configured
|
|
44
|
+
const hasPreConfiguredValues = filter.values && filter.values.length > 0
|
|
45
|
+
const filterValues = hasPreConfiguredValues ? filter.values : generateValuesForFilter(getSelector(filter), data)
|
|
46
|
+
|
|
43
47
|
filterCopy.values = filterValues
|
|
44
48
|
|
|
45
49
|
// Merge new values with existing custom order (fixes DEV-11740 & DEV-11376)
|
|
@@ -54,32 +58,21 @@ export const addValuesToDashboardFilters = (
|
|
|
54
58
|
const active: string[] = Array.isArray(filterCopy.active) ? filterCopy.active : [filterCopy.active]
|
|
55
59
|
filterCopy.active = active.filter(val => defaultValues.includes(val))
|
|
56
60
|
} else {
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
const isResetLabelValue = currentActive && currentActive === filterCopy.resetLabel
|
|
60
|
-
const isCurrentActiveValid = currentActive && (filterValues.includes(currentActive) || isResetLabelValue)
|
|
61
|
-
|
|
62
|
-
// Check if this is an intentional clear (empty string, but not undefined during initial load)
|
|
63
|
-
const isIntentionalClear = currentActive === ''
|
|
64
|
-
|
|
65
|
-
// Priority: defaultValue > valid current active > reset label > first value
|
|
61
|
+
// Initialize active from defaultValue if not already set
|
|
62
|
+
// OR if defaultValue exists, always use it (overrides stale active from saved config)
|
|
66
63
|
if (filterCopy.defaultValue) {
|
|
67
|
-
// If defaultValue is explicitly set, always use it
|
|
68
64
|
filterCopy.active = filterCopy.defaultValue
|
|
69
|
-
} else if (
|
|
70
|
-
|
|
71
|
-
filterCopy.active = currentActive
|
|
72
|
-
} else if (isIntentionalClear) {
|
|
73
|
-
// Don't override intentional clears
|
|
74
|
-
filterCopy.active = currentActive
|
|
75
|
-
} else {
|
|
76
|
-
// Set to reset label or first value
|
|
77
|
-
const defaultValue = filterCopy.resetLabel || filterCopy.values[0]
|
|
78
|
-
filterCopy.active = defaultValue
|
|
65
|
+
} else if (!filterCopy.active) {
|
|
66
|
+
filterCopy.active = filterCopy.resetLabel || filterCopy.values[0]
|
|
79
67
|
}
|
|
80
68
|
}
|
|
81
69
|
}
|
|
82
70
|
|
|
71
|
+
// Check if filter should be hidden by query parameter
|
|
72
|
+
if (isFilterHiddenByQuery(filterCopy)) {
|
|
73
|
+
filterCopy.showDropdown = false
|
|
74
|
+
}
|
|
75
|
+
|
|
83
76
|
// Handle nested dropdown subGrouping.active property
|
|
84
77
|
if (filterCopy.subGrouping && filterCopy.subGrouping.valuesLookup) {
|
|
85
78
|
const groupName = filterCopy.active as string
|
|
@@ -1,65 +1,67 @@
|
|
|
1
|
-
import _ from 'lodash'
|
|
2
|
-
import { FilterBehavior } from '../helpers/FilterBehavior'
|
|
3
|
-
import {
|
|
4
|
-
getQueryParams,
|
|
5
|
-
removeQueryParam,
|
|
6
|
-
updateQueryParam,
|
|
7
|
-
updateQueryString
|
|
8
|
-
} from '@cdc/core/helpers/queryStringUtils'
|
|
9
|
-
import { SharedFilter } from '../types/SharedFilter'
|
|
10
|
-
import { DashboardFilters } from '../types/DashboardFilters'
|
|
11
|
-
import { FILTER_STYLE } from '../types/FilterStyles'
|
|
12
|
-
|
|
13
|
-
const handleChildren = (sharedFilters: SharedFilter[], parentIndex: number) => {
|
|
14
|
-
const parentKey = sharedFilters[parentIndex].key
|
|
15
|
-
const childFilterIndexes = sharedFilters
|
|
16
|
-
.map((filter, index) => (filter.parents?.includes(parentKey) ? index : null))
|
|
17
|
-
.filter(i => i !== null)
|
|
18
|
-
if (childFilterIndexes.length) {
|
|
19
|
-
childFilterIndexes.forEach(filterIndex => {
|
|
20
|
-
const cur = sharedFilters[filterIndex]
|
|
21
|
-
if (cur.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
cur.subGrouping
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
sharedFiltersCopy[filterIndex].active = value
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
queryParams[currentFilter.setByQueryParameter]
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import { FilterBehavior } from '../helpers/FilterBehavior'
|
|
3
|
+
import {
|
|
4
|
+
getQueryParams,
|
|
5
|
+
removeQueryParam,
|
|
6
|
+
updateQueryParam,
|
|
7
|
+
updateQueryString
|
|
8
|
+
} from '@cdc/core/helpers/queryStringUtils'
|
|
9
|
+
import { SharedFilter } from '../types/SharedFilter'
|
|
10
|
+
import { DashboardFilters } from '../types/DashboardFilters'
|
|
11
|
+
import { FILTER_STYLE } from '../types/FilterStyles'
|
|
12
|
+
|
|
13
|
+
const handleChildren = (sharedFilters: SharedFilter[], parentIndex: number) => {
|
|
14
|
+
const parentKey = sharedFilters[parentIndex].key
|
|
15
|
+
const childFilterIndexes = sharedFilters
|
|
16
|
+
.map((filter, index) => (filter.parents?.includes(parentKey) ? index : null))
|
|
17
|
+
.filter(i => i !== null)
|
|
18
|
+
if (childFilterIndexes.length) {
|
|
19
|
+
childFilterIndexes.forEach(filterIndex => {
|
|
20
|
+
const cur = sharedFilters[filterIndex]
|
|
21
|
+
if (cur.type === 'urlfilter') {
|
|
22
|
+
if (cur.setByQueryParameter) removeQueryParam(cur.setByQueryParameter)
|
|
23
|
+
cur.active = ''
|
|
24
|
+
if (cur.subGrouping) {
|
|
25
|
+
cur.subGrouping.active = ''
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
return childFilterIndexes
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const changeFilterActive = (
|
|
34
|
+
filterIndex: number,
|
|
35
|
+
value: string | string[],
|
|
36
|
+
sharedFilters: SharedFilter[],
|
|
37
|
+
vizConfig: DashboardFilters
|
|
38
|
+
): [SharedFilter[], number[]] => {
|
|
39
|
+
const sharedFiltersCopy = _.cloneDeep(sharedFilters)
|
|
40
|
+
const currentFilter = sharedFiltersCopy[filterIndex]
|
|
41
|
+
if (vizConfig.filterBehavior !== FilterBehavior.Apply || vizConfig.autoLoad) {
|
|
42
|
+
if (currentFilter?.filterStyle === FILTER_STYLE.nestedDropdown) {
|
|
43
|
+
sharedFiltersCopy[filterIndex].active = value[0]
|
|
44
|
+
sharedFiltersCopy[filterIndex].subGrouping.active = value[1]
|
|
45
|
+
} else {
|
|
46
|
+
sharedFiltersCopy[filterIndex].active = value
|
|
47
|
+
handleChildren(sharedFiltersCopy, filterIndex)
|
|
48
|
+
const queryParams = getQueryParams()
|
|
49
|
+
if (
|
|
50
|
+
currentFilter.setByQueryParameter &&
|
|
51
|
+
queryParams[currentFilter.setByQueryParameter] !== currentFilter.active
|
|
52
|
+
) {
|
|
53
|
+
queryParams[currentFilter.setByQueryParameter] = currentFilter.active
|
|
54
|
+
updateQueryString(queryParams)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} else if (currentFilter.subGrouping) {
|
|
58
|
+
updateQueryParam(currentFilter.setByQueryParameter, value[0])
|
|
59
|
+
updateQueryParam(currentFilter.subGrouping.setByQueryParameter, value[1])
|
|
60
|
+
sharedFiltersCopy[filterIndex].queuedActive = value
|
|
61
|
+
} else {
|
|
62
|
+
const paramVal = Array.isArray(value) ? value.join(',') : value
|
|
63
|
+
if (currentFilter.setByQueryParameter) updateQueryParam(currentFilter.setByQueryParameter, paramVal)
|
|
64
|
+
sharedFiltersCopy[filterIndex].queuedActive = value
|
|
65
|
+
}
|
|
66
|
+
return [sharedFiltersCopy, handleChildren(sharedFiltersCopy, filterIndex)]
|
|
67
|
+
}
|
|
@@ -17,11 +17,12 @@ const cleanDashboardFootnotes = (config: DashboardConfig) => {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const cleanDashboardData = (config: DashboardConfig) => {
|
|
20
|
+
const cleanDashboardData = (config: DashboardConfig, isEditor = false) => {
|
|
21
21
|
if (config.datasets) {
|
|
22
22
|
Object.keys(config.datasets).forEach(datasetKey => {
|
|
23
23
|
delete config.datasets[datasetKey].formattedData
|
|
24
|
-
|
|
24
|
+
// Only delete data when not in editor mode
|
|
25
|
+
if (config.datasets[datasetKey].dataUrl && !isEditor) {
|
|
25
26
|
delete config.datasets[datasetKey].data
|
|
26
27
|
}
|
|
27
28
|
})
|
|
@@ -104,12 +105,12 @@ const removeRuntimeDataURLs = (config: DashboardConfig) => {
|
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
export const stripConfig = configToStrip => {
|
|
108
|
+
export const stripConfig = (configToStrip, isEditor = false) => {
|
|
108
109
|
const strippedConfig = cloneConfig(configToStrip)
|
|
109
110
|
if (strippedConfig.type === 'dashboard') {
|
|
110
111
|
if (strippedConfig.multiDashboards) {
|
|
111
112
|
strippedConfig.multiDashboards.forEach((multiDashboard, i) => {
|
|
112
|
-
cleanDashboardData(strippedConfig.multiDashboards[i])
|
|
113
|
+
cleanDashboardData(strippedConfig.multiDashboards[i], isEditor)
|
|
113
114
|
cleanSharedFilters(strippedConfig.multiDashboards[i])
|
|
114
115
|
cleanDashboardFootnotes(strippedConfig.multiDashboards[i])
|
|
115
116
|
cleanVisualizationFilters(strippedConfig.multiDashboards[i])
|
|
@@ -120,7 +121,7 @@ export const stripConfig = configToStrip => {
|
|
|
120
121
|
delete strippedConfig.label
|
|
121
122
|
}
|
|
122
123
|
delete strippedConfig.activeDashboard
|
|
123
|
-
cleanDashboardData(strippedConfig)
|
|
124
|
+
cleanDashboardData(strippedConfig, isEditor)
|
|
124
125
|
cleanSharedFilters(strippedConfig)
|
|
125
126
|
cleanDashboardFootnotes(strippedConfig)
|
|
126
127
|
cleanVisualizationFilters(strippedConfig)
|
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
import { DashboardState } from '../store/dashboard.reducer'
|
|
2
|
-
import { DashboardConfig as Config, DashboardConfig } from '../types/DashboardConfig'
|
|
3
|
-
import { filterData } from './filterData'
|
|
4
|
-
import { generateValuesForFilter } from './generateValuesForFilter'
|
|
5
|
-
import { getFormattedData } from './getFormattedData'
|
|
6
|
-
import { getVizKeys } from './getVizKeys'
|
|
7
|
-
import { getVizRowColumnLocator } from './getVizRowColumnLocator'
|
|
8
|
-
|
|
9
|
-
import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
|
|
10
|
-
|
|
11
|
-
type UpdateState = Omit<DashboardState, 'config'> & {
|
|
12
|
-
config?: DashboardConfig
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const getUpdateConfig =
|
|
16
|
-
(state: UpdateState) =>
|
|
17
|
-
(newConfig, dataOverride?: Object): [Config, Object] => {
|
|
18
|
-
let newFilteredData = {}
|
|
19
|
-
let visualizationKeys = getVizKeys(newConfig)
|
|
20
|
-
|
|
21
|
-
const vizRowColumnLocator = getVizRowColumnLocator(newConfig.rows)
|
|
22
|
-
|
|
23
|
-
if (newConfig.dashboard.sharedFilters) {
|
|
24
|
-
newConfig.dashboard.sharedFilters.forEach((filter, i) => {
|
|
25
|
-
const filterIsSetByVizData = !!visualizationKeys.find(key => key === filter.setBy)
|
|
26
|
-
const _filter = newConfig.dashboard.sharedFilters[i]
|
|
27
|
-
|
|
28
|
-
const setValuesAndActive = filterValues => {
|
|
29
|
-
_filter.values = filterValues
|
|
30
|
-
if (filterValues.length > 0) {
|
|
31
|
-
const defaultValues = _filter.pivot ? _filter.values : _filter.values[0]
|
|
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) {
|
|
44
|
-
if (_filter.order === 'asc') {
|
|
45
|
-
filterValues.sort()
|
|
46
|
-
}
|
|
47
|
-
if (_filter.order === 'desc') {
|
|
48
|
-
filterValues.sort().reverse()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
setValuesAndActive(filterValues)
|
|
52
|
-
} else if ((!filter.values || filter.values.length === 0) && filter.showDropdown) {
|
|
53
|
-
setValuesAndActive(filterValues)
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
visualizationKeys.forEach(visualizationKey => {
|
|
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(
|
|
61
|
-
sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) !== -1
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
if (applicableFilters.length > 0) {
|
|
65
|
-
const visualization = newConfig.visualizations[visualizationKey]
|
|
66
|
-
const _newConfigDataSet = newConfig.datasets[visualization.dataKey]
|
|
67
|
-
const formattedData = getFormattedData(
|
|
68
|
-
_newConfigDataSet?.data || visualization.data,
|
|
69
|
-
visualization.dataDescription
|
|
70
|
-
)
|
|
71
|
-
const _data = formattedData || (dataOverride || state.data)[visualization.dataKey]
|
|
72
|
-
newFilteredData[visualizationKey] = filterData(applicableFilters, _data)
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
newConfig.rows.forEach((row, rowIndex) => {
|
|
77
|
-
const applicableFilters = newConfig.dashboard.sharedFilters.filter(
|
|
78
|
-
sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(rowIndex) !== -1
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
if (applicableFilters.length > 0) {
|
|
82
|
-
const formattedData = getFormattedData(row.data, row.dataDescription)
|
|
83
|
-
const _data = formattedData || (dataOverride || state.data)[rowIndex]
|
|
84
|
-
newFilteredData[rowIndex] = filterData(applicableFilters, _data)
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
//Enforce default values that need to be calculated at runtime
|
|
89
|
-
newConfig.runtime = {}
|
|
90
|
-
return [newConfig, newFilteredData]
|
|
91
|
-
}
|
|
1
|
+
import { DashboardState } from '../store/dashboard.reducer'
|
|
2
|
+
import { DashboardConfig as Config, DashboardConfig } from '../types/DashboardConfig'
|
|
3
|
+
import { filterData } from './filterData'
|
|
4
|
+
import { generateValuesForFilter } from './generateValuesForFilter'
|
|
5
|
+
import { getFormattedData } from './getFormattedData'
|
|
6
|
+
import { getVizKeys } from './getVizKeys'
|
|
7
|
+
import { getVizRowColumnLocator } from './getVizRowColumnLocator'
|
|
8
|
+
|
|
9
|
+
import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
|
|
10
|
+
|
|
11
|
+
type UpdateState = Omit<DashboardState, 'config'> & {
|
|
12
|
+
config?: DashboardConfig
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const getUpdateConfig =
|
|
16
|
+
(state: UpdateState) =>
|
|
17
|
+
(newConfig, dataOverride?: Object): [Config, Object] => {
|
|
18
|
+
let newFilteredData = {}
|
|
19
|
+
let visualizationKeys = getVizKeys(newConfig)
|
|
20
|
+
|
|
21
|
+
const vizRowColumnLocator = getVizRowColumnLocator(newConfig.rows)
|
|
22
|
+
|
|
23
|
+
if (newConfig.dashboard.sharedFilters) {
|
|
24
|
+
newConfig.dashboard.sharedFilters.forEach((filter, i) => {
|
|
25
|
+
const filterIsSetByVizData = !!visualizationKeys.find(key => key === filter.setBy)
|
|
26
|
+
const _filter = newConfig.dashboard.sharedFilters[i]
|
|
27
|
+
|
|
28
|
+
const setValuesAndActive = filterValues => {
|
|
29
|
+
_filter.values = filterValues
|
|
30
|
+
if (filterValues.length > 0) {
|
|
31
|
+
const defaultValues = _filter.pivot ? _filter.values : _filter.values[0]
|
|
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) {
|
|
44
|
+
if (_filter.order === 'asc') {
|
|
45
|
+
filterValues.sort()
|
|
46
|
+
}
|
|
47
|
+
if (_filter.order === 'desc') {
|
|
48
|
+
filterValues.sort().reverse()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setValuesAndActive(filterValues)
|
|
52
|
+
} else if ((!filter.values || filter.values.length === 0) && filter.showDropdown) {
|
|
53
|
+
setValuesAndActive(filterValues)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
visualizationKeys.forEach(visualizationKey => {
|
|
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(
|
|
61
|
+
sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) !== -1
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if (applicableFilters.length > 0) {
|
|
65
|
+
const visualization = newConfig.visualizations[visualizationKey]
|
|
66
|
+
const _newConfigDataSet = newConfig.datasets[visualization.dataKey]
|
|
67
|
+
const formattedData = getFormattedData(
|
|
68
|
+
_newConfigDataSet?.data || visualization.data,
|
|
69
|
+
visualization.dataDescription
|
|
70
|
+
)
|
|
71
|
+
const _data = formattedData || (dataOverride || state.data)[visualization.dataKey]
|
|
72
|
+
newFilteredData[visualizationKey] = filterData(applicableFilters, _data)
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
newConfig.rows.forEach((row, rowIndex) => {
|
|
77
|
+
const applicableFilters = newConfig.dashboard.sharedFilters.filter(
|
|
78
|
+
sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(rowIndex) !== -1
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if (applicableFilters.length > 0) {
|
|
82
|
+
const formattedData = getFormattedData(row.data, row.dataDescription)
|
|
83
|
+
const _data = formattedData || (dataOverride || state.data)[rowIndex]
|
|
84
|
+
newFilteredData[rowIndex] = filterData(applicableFilters, _data)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
//Enforce default values that need to be calculated at runtime
|
|
89
|
+
newConfig.runtime = {}
|
|
90
|
+
return [newConfig, newFilteredData]
|
|
91
|
+
}
|