@cdc/dashboard 4.26.3 → 4.26.5
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/CONFIG.md +219 -0
- package/README.md +60 -20
- package/dist/cdcdashboard-CY9IcPSi.es.js +6 -0
- package/dist/cdcdashboard-DlpiY3fQ.es.js +4 -0
- package/dist/cdcdashboard.js +61559 -58048
- package/examples/__data__/data-2.json +6 -0
- package/examples/__data__/data.json +6 -0
- package/examples/dashboard-conditions-filters-incomplete.json +221 -0
- package/examples/dashboard-missing-datasets-multi.json +174 -0
- package/examples/dashboard-missing-datasets-single.json +121 -0
- package/examples/dashboard-multi-dashboard-version-regression.json +146 -0
- package/examples/dashboard-shared-filter-row-delete-cleanup.json +186 -0
- package/examples/dashboard-stale-dataset-keys.json +181 -0
- package/examples/dashboard-tiered-filter-regression.json +190 -0
- package/examples/legend-issue.json +1 -1
- package/examples/minimal-example.json +34 -0
- package/examples/private/cfa-dashboard.json +651 -0
- package/examples/private/data-bite-wrap.json +6936 -0
- package/examples/private/dengue.json +4640 -0
- package/examples/private/link_to_file.json +16662 -0
- package/examples/private/multi-dash-fix.json +16963 -0
- package/examples/private/versions.json +41612 -0
- package/examples/sankey.json +3 -3
- package/examples/test-api-filter-reset.json +4 -4
- package/examples/tp5-test.json +86 -4
- package/examples/us-map-filter-example.json +1074 -0
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +6 -2
- package/src/CdcDashboardComponent.tsx +179 -88
- package/src/DashboardCopyPasteContext.test.tsx +33 -0
- package/src/DashboardCopyPasteContext.tsx +48 -0
- package/src/_stories/Dashboard.EditorRegression.stories.tsx +72 -0
- package/src/_stories/Dashboard.Regression.stories.tsx +196 -0
- package/src/_stories/Dashboard.Zoom.stories.tsx +88 -0
- package/src/_stories/Dashboard.smoke.stories.tsx +33 -0
- package/src/_stories/Dashboard.stories.tsx +337 -2
- package/src/_stories/FilteredTextMigrationComparison.stories.tsx +87 -0
- package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
- package/src/_stories/_mock/tp5-test.json +86 -5
- package/src/components/Column.test.tsx +176 -0
- package/src/components/Column.tsx +214 -13
- package/src/components/DashboardConditionModal.test.tsx +420 -0
- package/src/components/DashboardConditionModal.tsx +367 -0
- package/src/components/DashboardConditionSummary.tsx +59 -0
- package/src/components/DashboardEditors.tsx +23 -0
- package/src/components/DashboardFilters/DashboardFilters.test.tsx +267 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +193 -172
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.test.tsx +164 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +46 -6
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +5 -3
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +59 -58
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +304 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +43 -36
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +2 -2
- package/src/components/DashboardFilters/DashboardFiltersWrapper.test.tsx +142 -0
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +32 -27
- package/src/components/DashboardFilters/dashboardfilter.styles.css +42 -27
- package/src/components/DataDesignerModal.tsx +2 -1
- package/src/components/ExpandCollapseButtons.tsx +6 -4
- package/src/components/Grid.tsx +12 -7
- package/src/components/Header/Header.tsx +36 -17
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
- package/src/components/Row.test.tsx +228 -0
- package/src/components/Row.tsx +104 -28
- package/src/components/VisualizationRow.test.tsx +396 -0
- package/src/components/VisualizationRow.tsx +177 -51
- package/src/components/VisualizationsPanel/VisualizationsPanel.test.tsx +49 -0
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +14 -13
- package/src/components/Widget/Widget.test.tsx +218 -0
- package/src/components/Widget/Widget.tsx +123 -20
- package/src/components/Widget/widget.styles.css +58 -14
- package/src/components/dashboard-condition-modal.css +76 -0
- package/src/components/dashboard-condition-summary.css +87 -0
- package/src/data/initial-state.js +1 -0
- package/src/helpers/addValuesToDashboardFilters.ts +3 -5
- package/src/helpers/addVisualization.ts +17 -4
- package/src/helpers/cloneDashboardWidget.ts +127 -0
- package/src/helpers/dashboardColumnWidgets.ts +99 -0
- package/src/helpers/dashboardConditionUi.ts +47 -0
- package/src/helpers/dashboardConditions.ts +200 -0
- package/src/helpers/dashboardFilterTargets.ts +156 -0
- package/src/helpers/filterData.ts +4 -9
- package/src/helpers/filterVisibility.ts +20 -0
- package/src/helpers/formatConfigBeforeSave.ts +2 -2
- package/src/helpers/getFilteredData.ts +18 -5
- package/src/helpers/getUpdateConfig.ts +43 -12
- package/src/helpers/getVizRowColumnLocator.ts +11 -1
- package/src/helpers/iconHash.tsx +9 -3
- package/src/helpers/mapDataToConfig.ts +31 -29
- package/src/helpers/reloadURLHelpers.ts +25 -5
- package/src/helpers/removeDashboardFilter.ts +33 -33
- package/src/helpers/tests/addVisualization.test.ts +53 -9
- package/src/helpers/tests/cloneDashboardWidget.test.ts +136 -0
- package/src/helpers/tests/dashboardColumnWidgets.test.ts +99 -0
- package/src/helpers/tests/dashboardConditionUi.test.ts +41 -0
- package/src/helpers/tests/dashboardConditions.test.ts +428 -0
- package/src/helpers/tests/formatConfigBeforeSave.test.ts +51 -0
- package/src/helpers/tests/getFilteredData.test.ts +265 -86
- package/src/helpers/tests/getUpdateConfig.test.ts +338 -0
- package/src/helpers/tests/reloadURLHelpers.test.ts +394 -238
- package/src/index.tsx +6 -3
- package/src/scss/grid.scss +281 -22
- package/src/scss/main.scss +215 -64
- package/src/store/dashboard.actions.ts +17 -4
- package/src/store/dashboard.reducer.test.ts +538 -0
- package/src/store/dashboard.reducer.ts +136 -22
- package/src/test/CdcDashboard.test.jsx +24 -0
- package/src/test/CdcDashboard.test.tsx +148 -0
- package/src/test/CdcDashboardComponent.test.tsx +935 -2
- package/src/types/ConfigRow.ts +15 -0
- package/src/types/DashboardFilters.ts +4 -0
- package/src/types/SharedFilter.ts +2 -0
- package/tests/fixtures/dashboard-config-with-metadata.json +1 -1
- package/dist/cdcdashboard-vr9HZwRt.es.js +0 -6
- package/examples/DEV-6574.json +0 -2224
- package/examples/api-dashboard-data.json +0 -272
- package/examples/api-dashboard-years.json +0 -11
- package/examples/api-geographies-data.json +0 -11
- package/examples/chart-data.json +0 -5409
- package/examples/custom/css/respiratory.css +0 -236
- package/examples/custom/js/respiratory.js +0 -242
- package/examples/default-data.json +0 -368
- package/examples/default-filter-control.json +0 -209
- package/examples/default-multi-dataset-shared-filter.json +0 -1729
- package/examples/default-multi-dataset.json +0 -506
- package/examples/ed-visits-county-file.json +0 -402
- package/examples/filters/Alabama.json +0 -72
- package/examples/filters/Alaska.json +0 -1737
- package/examples/filters/Arkansas.json +0 -4713
- package/examples/filters/California.json +0 -212
- package/examples/filters/Colorado.json +0 -1500
- package/examples/filters/Connecticut.json +0 -559
- package/examples/filters/Delaware.json +0 -63
- package/examples/filters/DistrictofColumbia.json +0 -63
- package/examples/filters/Florida.json +0 -4217
- package/examples/filters/States.json +0 -146
- package/examples/state-level.json +0 -90136
- package/examples/state-points.json +0 -10474
- package/examples/temp-example-data.json +0 -130
- package/examples/test-dashboard-simple.json +0 -503
- package/examples/test-example.json +0 -752
- package/examples/test-file.json +0 -147
- package/examples/test.json +0 -752
- package/examples/testing.json +0 -94456
- /package/examples/{data → __data__}/data-with-metadata.json +0 -0
- /package/examples/{legend-issue-data.json → __data__/legend-issue-data.json} +0 -0
- /package/examples/api-test/{categories.json → __data__/categories.json} +0 -0
- /package/examples/api-test/{chart-data.json → __data__/chart-data.json} +0 -0
- /package/examples/api-test/{topics.json → __data__/topics.json} +0 -0
- /package/examples/api-test/{years.json → __data__/years.json} +0 -0
- /package/src/_stories/{Dashboard.Pages.stories.tsx → Dashboard.Pages.smoke.stories.tsx} +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { ConfigRow, DashboardCondition } from '../types/ConfigRow'
|
|
2
|
+
import { DashboardConfig } from '../types/DashboardConfig'
|
|
3
|
+
import { Dashboard } from '../types/Dashboard'
|
|
4
|
+
import { SharedFilter } from '../types/SharedFilter'
|
|
5
|
+
import { getConditionalWidgets, hasConditionalWidgets } from './dashboardColumnWidgets'
|
|
6
|
+
import { getVizRowColumnLocator } from './getVizRowColumnLocator'
|
|
7
|
+
|
|
8
|
+
export type SharedFilterTarget = string | number
|
|
9
|
+
|
|
10
|
+
export type SharedFilterTargetOptions = {
|
|
11
|
+
nameLookup: Record<string, string>
|
|
12
|
+
options: SharedFilterTarget[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type DashboardConditionTarget = {
|
|
16
|
+
id: string
|
|
17
|
+
dashboardCondition: DashboardCondition
|
|
18
|
+
rowIndex: number
|
|
19
|
+
filterTarget: SharedFilterTarget
|
|
20
|
+
columnIndex?: number
|
|
21
|
+
entryIndex?: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const dashboardConditionsSupportedForRow = (row?: ConfigRow) =>
|
|
25
|
+
!!row && !row.toggle && !row.multiVizColumn && !row.originalMultiVizColumn
|
|
26
|
+
|
|
27
|
+
const normalizeTarget = (target: SharedFilterTarget) => `${target}`
|
|
28
|
+
|
|
29
|
+
const dedupeSharedFilterTargets = (targets: SharedFilterTarget[]) =>
|
|
30
|
+
targets.filter(
|
|
31
|
+
(value, index, values) => values.findIndex(entry => normalizeTarget(entry) === normalizeTarget(value)) === index
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
export const matchesSharedFilterTarget = (usedBy: SharedFilter['usedBy'], target: SharedFilterTarget) =>
|
|
35
|
+
!!usedBy?.some(entry => normalizeTarget(entry) === normalizeTarget(target))
|
|
36
|
+
|
|
37
|
+
export const getDashboardConditionTargets = (rows: ConfigRow[]): DashboardConditionTarget[] =>
|
|
38
|
+
rows.reduce((targets, row, rowIndex) => {
|
|
39
|
+
if (!dashboardConditionsSupportedForRow(row)) return targets
|
|
40
|
+
|
|
41
|
+
if (row.dashboardCondition?.id) {
|
|
42
|
+
targets.push({
|
|
43
|
+
id: row.dashboardCondition.id,
|
|
44
|
+
dashboardCondition: row.dashboardCondition,
|
|
45
|
+
rowIndex,
|
|
46
|
+
filterTarget: rowIndex
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
row.columns?.forEach((column, columnIndex) => {
|
|
51
|
+
if (hasConditionalWidgets(column)) {
|
|
52
|
+
getConditionalWidgets(column).forEach((entry, entryIndex) => {
|
|
53
|
+
if (!entry.dashboardCondition?.id) return
|
|
54
|
+
targets.push({
|
|
55
|
+
id: entry.dashboardCondition.id,
|
|
56
|
+
dashboardCondition: entry.dashboardCondition,
|
|
57
|
+
rowIndex,
|
|
58
|
+
filterTarget: row.dataKey ? rowIndex : entry.widget,
|
|
59
|
+
columnIndex,
|
|
60
|
+
entryIndex
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return targets
|
|
68
|
+
}, [] as DashboardConditionTarget[])
|
|
69
|
+
|
|
70
|
+
export const getSharedFilterTargetOptions = (
|
|
71
|
+
config: DashboardConfig,
|
|
72
|
+
filter: Pick<SharedFilter, 'usedBy' | 'setBy'>
|
|
73
|
+
): SharedFilterTargetOptions => {
|
|
74
|
+
const nameLookup: Record<string, string> = {}
|
|
75
|
+
const options: SharedFilterTarget[] = []
|
|
76
|
+
const vizRowColumnLocator = getVizRowColumnLocator(config.rows)
|
|
77
|
+
|
|
78
|
+
Object.keys(config.visualizations).forEach(vizKey => {
|
|
79
|
+
const vizLookup = vizRowColumnLocator[vizKey]
|
|
80
|
+
if (!vizLookup) return
|
|
81
|
+
|
|
82
|
+
const viz = config.visualizations[vizKey] as any
|
|
83
|
+
if (viz.type === 'dashboardFilters') return
|
|
84
|
+
|
|
85
|
+
const vizName =
|
|
86
|
+
viz.visualizationType === 'markup-include'
|
|
87
|
+
? viz.contentEditor?.title || vizKey
|
|
88
|
+
: viz.general?.title || viz.title || vizKey
|
|
89
|
+
const dataConfiguredOnRow = config.rows[vizLookup.row]?.dataKey
|
|
90
|
+
|
|
91
|
+
nameLookup[vizKey] = vizName
|
|
92
|
+
if (filter.setBy !== vizKey && !viz.usesSharedFilter && !dataConfiguredOnRow) {
|
|
93
|
+
options.push(vizKey)
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
config.rows.forEach((row, rowIndex) => {
|
|
98
|
+
if (row.dataKey || (dashboardConditionsSupportedForRow(row) && row.dashboardCondition)) {
|
|
99
|
+
nameLookup[`${rowIndex}`] = `Row ${rowIndex + 1}`
|
|
100
|
+
options.push(rowIndex)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Preserve legacy row targets that are no longer offered as new selectable targets.
|
|
105
|
+
filter.usedBy?.forEach(target => {
|
|
106
|
+
const isNumericRowTarget = typeof target === 'number' || (typeof target === 'string' && /^-?\d+$/.test(target))
|
|
107
|
+
if (isNumericRowTarget) {
|
|
108
|
+
const rowIndex = Number(target)
|
|
109
|
+
if (!config.rows[rowIndex]) return
|
|
110
|
+
nameLookup[`${target}`] = `Row ${rowIndex + 1}`
|
|
111
|
+
options.push(target)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
nameLookup,
|
|
117
|
+
options: dedupeSharedFilterTargets(options)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const remapRowTargetsInSharedFilters = (
|
|
122
|
+
sharedFilters: SharedFilter[],
|
|
123
|
+
remapRowIndex: (rowIndex: number) => number | null
|
|
124
|
+
): SharedFilter[] =>
|
|
125
|
+
sharedFilters.map(sharedFilter => {
|
|
126
|
+
if (!sharedFilter.usedBy) return sharedFilter
|
|
127
|
+
|
|
128
|
+
const nextUsedBy = dedupeSharedFilterTargets(
|
|
129
|
+
sharedFilter.usedBy.flatMap(target => {
|
|
130
|
+
const isNumericRowTarget = typeof target === 'number' || (typeof target === 'string' && /^-?\d+$/.test(target))
|
|
131
|
+
if (!isNumericRowTarget) return [target]
|
|
132
|
+
|
|
133
|
+
const mappedRowIndex = remapRowIndex(Number(target))
|
|
134
|
+
return mappedRowIndex === null ? [] : [typeof target === 'number' ? mappedRowIndex : `${mappedRowIndex}`]
|
|
135
|
+
})
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return nextUsedBy === sharedFilter.usedBy ? sharedFilter : { ...sharedFilter, usedBy: nextUsedBy }
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
export const getApplicableFiltersForTarget = (
|
|
142
|
+
dashboard: Dashboard,
|
|
143
|
+
target: SharedFilterTarget,
|
|
144
|
+
options?: { includeUnscoped?: boolean }
|
|
145
|
+
): SharedFilter[] | false => {
|
|
146
|
+
const applicableFilters =
|
|
147
|
+
dashboard.sharedFilters?.filter(sharedFilter => {
|
|
148
|
+
if (options?.includeUnscoped && (!sharedFilter.usedBy || sharedFilter.usedBy.length === 0)) {
|
|
149
|
+
return true
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return matchesSharedFilterTarget(sharedFilter.usedBy, target)
|
|
153
|
+
}) || []
|
|
154
|
+
|
|
155
|
+
return applicableFilters.length > 0 ? applicableFilters : false
|
|
156
|
+
}
|
|
@@ -108,14 +108,9 @@ export const filterData = (filters: SharedFilter[], _data: Object[]): Object[] =
|
|
|
108
108
|
return []
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const filteredData = filterDataByTier(_data, filters, i + 1)
|
|
115
|
-
|
|
116
|
-
if (lastIteration) {
|
|
117
|
-
// not sure if this last run of filterDataByTier() function is necessary.
|
|
118
|
-
return filterDataByTier(filteredData, filters, maxTier - 1)
|
|
119
|
-
}
|
|
111
|
+
let filteredData = _data
|
|
112
|
+
for (let tier = 1; tier <= maxTier; tier++) {
|
|
113
|
+
filteredData = filterDataByTier(filteredData, filters, tier)
|
|
120
114
|
}
|
|
115
|
+
return filteredData
|
|
121
116
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FILTER_STYLE } from '../types/FilterStyles'
|
|
2
|
+
import { SharedFilter } from '../types/SharedFilter'
|
|
3
|
+
|
|
4
|
+
export const isVisibleDashboardFilter = (filter?: SharedFilter | null): boolean =>
|
|
5
|
+
Boolean(
|
|
6
|
+
filter &&
|
|
7
|
+
(filter.type === 'urlfilter' ||
|
|
8
|
+
filter.showDropdown ||
|
|
9
|
+
filter.filterStyle === FILTER_STYLE.nestedDropdown ||
|
|
10
|
+
filter.filterStyle === FILTER_STYLE.tabSimple)
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
export const hasVisibleDashboardFiltersForIndexes = (
|
|
14
|
+
sharedFilters?: SharedFilter[] | null,
|
|
15
|
+
sharedFilterIndexes?: (number | string)[] | null
|
|
16
|
+
): boolean =>
|
|
17
|
+
Boolean(
|
|
18
|
+
sharedFilterIndexes?.length &&
|
|
19
|
+
sharedFilterIndexes.map(Number).some(filterIndex => isVisibleDashboardFilter(sharedFilters?.[filterIndex]))
|
|
20
|
+
)
|
|
@@ -51,9 +51,9 @@ export const cleanSharedFilters = (config: DashboardConfig) => {
|
|
|
51
51
|
if (config.dashboard?.sharedFilters) {
|
|
52
52
|
const recursiveRemoveFilters = (sharedFilters, visualizations: Record<string, AnyVisualization>) => {
|
|
53
53
|
const usedFilters = _.uniq(
|
|
54
|
-
Object.values(visualizations).reduce((acc, viz) => {
|
|
54
|
+
Object.values(visualizations).reduce((acc: number[], viz) => {
|
|
55
55
|
if (viz.type === 'dashboardFilters') {
|
|
56
|
-
acc = acc.concat(viz.sharedFilterIndexes)
|
|
56
|
+
acc = acc.concat((viz.sharedFilterIndexes ?? []).map(Number))
|
|
57
57
|
}
|
|
58
58
|
return acc
|
|
59
59
|
}, [])
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { DashboardState } from '../store/dashboard.reducer'
|
|
2
2
|
import { Dashboard } from '../types/Dashboard'
|
|
3
3
|
import { SharedFilter } from '../types/SharedFilter'
|
|
4
|
+
import { getDashboardConditionFilteredData } from './dashboardConditions'
|
|
5
|
+
import { getApplicableFiltersForTarget, getDashboardConditionTargets } from './dashboardFilterTargets'
|
|
4
6
|
import { filterData } from './filterData'
|
|
5
7
|
import { getFormattedData } from './getFormattedData'
|
|
6
8
|
import { getVizKeys } from './getVizKeys'
|
|
7
9
|
|
|
8
10
|
export const getApplicableFilters = (dashboard: Dashboard, key: string | number): false | SharedFilter[] => {
|
|
9
|
-
|
|
10
|
-
sharedFilter =>
|
|
11
|
-
(sharedFilter.usedBy && sharedFilter.usedBy.indexOf(`${key}`) !== -1) || sharedFilter.usedBy?.indexOf(key) !== -1
|
|
12
|
-
)
|
|
13
|
-
return c?.length > 0 ? c : false
|
|
11
|
+
return getApplicableFiltersForTarget(dashboard, key, { includeUnscoped: true })
|
|
14
12
|
}
|
|
15
13
|
export const getFilteredData = (
|
|
16
14
|
state: DashboardState,
|
|
@@ -45,5 +43,20 @@ export const getFilteredData = (
|
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
})
|
|
46
|
+
const dashboardConditionTargets = getDashboardConditionTargets(config.rows)
|
|
47
|
+
dashboardConditionTargets.forEach(conditionTarget => {
|
|
48
|
+
delete newFilteredData[conditionTarget.id]
|
|
49
|
+
|
|
50
|
+
const filteredData = getDashboardConditionFilteredData(
|
|
51
|
+
conditionTarget.dashboardCondition,
|
|
52
|
+
config.dashboard,
|
|
53
|
+
(dataOverride || state.data) as Record<string, any[]>,
|
|
54
|
+
conditionTarget.filterTarget
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if (filteredData !== undefined) {
|
|
58
|
+
newFilteredData[conditionTarget.id] = filteredData
|
|
59
|
+
}
|
|
60
|
+
})
|
|
48
61
|
return newFilteredData
|
|
49
62
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { DashboardState } from '../store/dashboard.reducer'
|
|
2
2
|
import { DashboardConfig as Config, DashboardConfig } from '../types/DashboardConfig'
|
|
3
|
+
import { ensureRowConditionIds, getDashboardConditionFilteredData } from './dashboardConditions'
|
|
4
|
+
import { getApplicableFiltersForTarget, getDashboardConditionTargets } from './dashboardFilterTargets'
|
|
3
5
|
import { filterData } from './filterData'
|
|
4
6
|
import { generateValuesForFilter } from './generateValuesForFilter'
|
|
5
7
|
import { getFormattedData } from './getFormattedData'
|
|
@@ -16,6 +18,7 @@ export const getUpdateConfig =
|
|
|
16
18
|
(state: UpdateState) =>
|
|
17
19
|
(newConfig, dataOverride?: Object): [Config, Object] => {
|
|
18
20
|
let newFilteredData = {}
|
|
21
|
+
newConfig.rows = ensureRowConditionIds(newConfig.rows)
|
|
19
22
|
let visualizationKeys = getVizKeys(newConfig)
|
|
20
23
|
|
|
21
24
|
const vizRowColumnLocator = getVizRowColumnLocator(newConfig.rows)
|
|
@@ -55,13 +58,13 @@ export const getUpdateConfig =
|
|
|
55
58
|
})
|
|
56
59
|
|
|
57
60
|
visualizationKeys.forEach(visualizationKey => {
|
|
58
|
-
const
|
|
59
|
-
if (newConfig.rows[row]?.
|
|
60
|
-
const applicableFilters = newConfig.dashboard
|
|
61
|
-
|
|
62
|
-
)
|
|
61
|
+
const locator = vizRowColumnLocator[visualizationKey]
|
|
62
|
+
if (newConfig.rows[locator?.row]?.dataKey) return // data configured on the row level
|
|
63
|
+
const applicableFilters = getApplicableFiltersForTarget(newConfig.dashboard, visualizationKey, {
|
|
64
|
+
includeUnscoped: true
|
|
65
|
+
})
|
|
63
66
|
|
|
64
|
-
if (applicableFilters
|
|
67
|
+
if (applicableFilters) {
|
|
65
68
|
const visualization = newConfig.visualizations[visualizationKey]
|
|
66
69
|
const _newConfigDataSet = newConfig.datasets[visualization.dataKey]
|
|
67
70
|
const formattedData = getFormattedData(
|
|
@@ -74,16 +77,44 @@ export const getUpdateConfig =
|
|
|
74
77
|
})
|
|
75
78
|
|
|
76
79
|
newConfig.rows.forEach((row, rowIndex) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
if (!row.dataKey) return
|
|
81
|
+
|
|
82
|
+
const applicableFilters = getApplicableFiltersForTarget(newConfig.dashboard, rowIndex, {
|
|
83
|
+
includeUnscoped: true
|
|
84
|
+
})
|
|
80
85
|
|
|
81
|
-
if (applicableFilters
|
|
82
|
-
const
|
|
83
|
-
const
|
|
86
|
+
if (applicableFilters) {
|
|
87
|
+
const datasetData = newConfig.datasets?.[row.dataKey]?.data
|
|
88
|
+
const formattedData = getFormattedData(row.data || datasetData, row.dataDescription)
|
|
89
|
+
const _data = formattedData || (dataOverride || state.data)[row.dataKey]
|
|
84
90
|
newFilteredData[rowIndex] = filterData(applicableFilters, _data)
|
|
85
91
|
}
|
|
86
92
|
})
|
|
93
|
+
|
|
94
|
+
const conditionDataSource = {
|
|
95
|
+
...(state.data || {}),
|
|
96
|
+
...(dataOverride || {}),
|
|
97
|
+
...Object.keys(newConfig.datasets || {}).reduce((acc, datasetKey) => {
|
|
98
|
+
const datasetData = newConfig.datasets[datasetKey]?.data
|
|
99
|
+
if (datasetData) {
|
|
100
|
+
acc[datasetKey] = datasetData
|
|
101
|
+
}
|
|
102
|
+
return acc
|
|
103
|
+
}, {} as Record<string, any[]>)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getDashboardConditionTargets(newConfig.rows).forEach(conditionTarget => {
|
|
107
|
+
const filteredData = getDashboardConditionFilteredData(
|
|
108
|
+
conditionTarget.dashboardCondition,
|
|
109
|
+
newConfig.dashboard,
|
|
110
|
+
conditionDataSource,
|
|
111
|
+
conditionTarget.filterTarget
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if (filteredData !== undefined) {
|
|
115
|
+
newFilteredData[conditionTarget.id] = filteredData
|
|
116
|
+
}
|
|
117
|
+
})
|
|
87
118
|
}
|
|
88
119
|
//Enforce default values that need to be calculated at runtime
|
|
89
120
|
newConfig.runtime = {}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { ConfigRow } from '../types/ConfigRow'
|
|
2
|
+
import { getConditionalWidgets, hasConditionalWidgets } from './dashboardColumnWidgets'
|
|
2
3
|
|
|
3
4
|
// returns a dictionary of widget names and their corresponding row and column index
|
|
4
|
-
export const getVizRowColumnLocator = (
|
|
5
|
+
export const getVizRowColumnLocator = (
|
|
6
|
+
rows: ConfigRow[]
|
|
7
|
+
): Record<string, { row: number; column: number; entry?: number }> =>
|
|
5
8
|
rows.reduce((acc, curr, index) => {
|
|
6
9
|
curr.columns?.forEach((column, columnIndex) => {
|
|
10
|
+
if (hasConditionalWidgets(column)) {
|
|
11
|
+
getConditionalWidgets(column).forEach((entry, entryIndex) => {
|
|
12
|
+
acc[entry.widget] = { row: index, column: columnIndex, entry: entryIndex }
|
|
13
|
+
})
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
if (column.widget !== undefined) acc[column.widget] = { row: index, column: columnIndex }
|
|
8
18
|
})
|
|
9
19
|
return acc
|
package/src/helpers/iconHash.tsx
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
import React from 'react'
|
|
1
2
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
2
3
|
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
const waffleAliases = ['TP5 Waffle', 'Waffle', 'TP5 Gauge', 'Gauge']
|
|
6
|
+
const waffleIcon = <Icon display='grid' base />
|
|
7
|
+
|
|
8
|
+
export const iconHash: Record<string, React.ReactNode> = {
|
|
5
9
|
'data-bite': <Icon display='databite' base />,
|
|
6
10
|
Bar: <Icon display='chartBar' base />,
|
|
7
11
|
'Spark Line': <Icon display='chartLine' />,
|
|
8
12
|
'Bump Chart': <Icon display='chartLine' />,
|
|
9
|
-
'waffle-chart':
|
|
13
|
+
'waffle-chart': waffleIcon,
|
|
10
14
|
'markup-include': <Icon display='code' base />,
|
|
15
|
+
condition: <Icon display='condition' base />,
|
|
11
16
|
Line: <Icon display='chartLine' base />,
|
|
12
17
|
Pie: <Icon display='chartPie' base />,
|
|
13
18
|
us: <Icon display='mapUsa' base />,
|
|
@@ -29,7 +34,8 @@ export const iconHash = {
|
|
|
29
34
|
'Box Plot': <Icon display='chartBar' base />,
|
|
30
35
|
'Forest Plot': <Icon display='chartBar' base />,
|
|
31
36
|
Forecasting: <Icon display='chartLine' base />,
|
|
32
|
-
'Warming Stripes': <Icon display='chartBar' base
|
|
37
|
+
'Warming Stripes': <Icon display='chartBar' base />,
|
|
38
|
+
...Object.fromEntries(waffleAliases.map(alias => [alias, waffleIcon]))
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
export const getIcon = (visualization: AnyVisualization) => {
|
|
@@ -1,29 +1,31 @@
|
|
|
1
|
-
import { getFormattedData } from './getFormattedData'
|
|
2
|
-
import { DashboardConfig } from '../types/DashboardConfig'
|
|
3
|
-
|
|
4
|
-
const mapDataToVisualizations = (config: DashboardConfig) => {
|
|
5
|
-
Object.keys(config.visualizations).forEach(
|
|
6
|
-
const viz = config.visualizations[vizKey]
|
|
7
|
-
if (viz.dataKey && !viz.data) {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
config.visualizations[vizKey].
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
config.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
import { getFormattedData } from './getFormattedData'
|
|
2
|
+
import { DashboardConfig } from '../types/DashboardConfig'
|
|
3
|
+
|
|
4
|
+
const mapDataToVisualizations = (config: DashboardConfig) => {
|
|
5
|
+
Object.keys(config.visualizations).forEach(vizKey => {
|
|
6
|
+
const viz = config.visualizations[vizKey]
|
|
7
|
+
if (viz.dataKey && !viz.data) {
|
|
8
|
+
const dataset = config.datasets[viz.dataKey]
|
|
9
|
+
if (!dataset) return
|
|
10
|
+
config.visualizations[vizKey].data = dataset.data
|
|
11
|
+
config.visualizations[vizKey].formattedData = getFormattedData(dataset.data, viz.dataDescription)
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const mapDataToRows = (config: DashboardConfig) => {
|
|
17
|
+
config.rows.forEach((row, i) => {
|
|
18
|
+
if (row.dataKey && !row.data) {
|
|
19
|
+
const dataset = config.datasets[row.dataKey]
|
|
20
|
+
if (!dataset) return
|
|
21
|
+
config.rows[i].data = dataset.data
|
|
22
|
+
config.rows[i].formattedData = getFormattedData(dataset.data, row.dataDescription)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const mapDataToConfig = (config: DashboardConfig) => {
|
|
28
|
+
mapDataToVisualizations(config)
|
|
29
|
+
mapDataToRows(config)
|
|
30
|
+
return config
|
|
31
|
+
}
|
|
@@ -6,6 +6,8 @@ import _ from 'lodash'
|
|
|
6
6
|
import { DashboardConfig } from '../types/DashboardConfig'
|
|
7
7
|
import { ConfigRow } from '../types/ConfigRow'
|
|
8
8
|
import { getVizRowColumnLocator } from './getVizRowColumnLocator'
|
|
9
|
+
import { getDashboardConditionDatasetKeys } from './dashboardConditions'
|
|
10
|
+
import { getDashboardConditionTargets } from './dashboardFilterTargets'
|
|
9
11
|
|
|
10
12
|
export const isUpdateNeeded = (
|
|
11
13
|
filters: SharedFilter[],
|
|
@@ -28,14 +30,26 @@ export const isUpdateNeeded = (
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
type GetDatasetKeysParams = Pick<DashboardConfig, 'visualizations' | 'datasets' | 'rows'>
|
|
31
|
-
|
|
33
|
+
type GetDatasetKeysOptions = {
|
|
34
|
+
includeDashboardConditionDatasetKeys?: boolean
|
|
35
|
+
}
|
|
36
|
+
export const getDatasetKeys = (
|
|
37
|
+
{ visualizations, datasets, rows }: GetDatasetKeysParams,
|
|
38
|
+
{ includeDashboardConditionDatasetKeys = true }: GetDatasetKeysOptions = {}
|
|
39
|
+
): string[] => {
|
|
32
40
|
const vizDataKeys = Object.values(visualizations).map(viz => viz.dataKey)
|
|
33
41
|
const rowDataKeys = rows.map(row => row.dataKey)
|
|
42
|
+
const dashboardConditionDataKeys = includeDashboardConditionDatasetKeys ? getDashboardConditionDatasetKeys(rows) : []
|
|
34
43
|
const footnoteDataKeys = Object.values(visualizations)
|
|
35
44
|
.map(viz => viz.footnotes?.dataKey)
|
|
36
45
|
.filter(Boolean)
|
|
37
46
|
// ensure to only load datasets for the specific dashboard tab.
|
|
38
|
-
const datasetsUsedByDashboard = _.uniq([
|
|
47
|
+
const datasetsUsedByDashboard = _.uniq([
|
|
48
|
+
...vizDataKeys,
|
|
49
|
+
...rowDataKeys,
|
|
50
|
+
...dashboardConditionDataKeys,
|
|
51
|
+
...footnoteDataKeys
|
|
52
|
+
])
|
|
39
53
|
return Object.keys(datasets).filter(datasetKey => datasetsUsedByDashboard.includes(datasetKey))
|
|
40
54
|
}
|
|
41
55
|
|
|
@@ -113,12 +127,18 @@ export const filterUsedByDataUrl = (
|
|
|
113
127
|
rows: ConfigRow[]
|
|
114
128
|
) => {
|
|
115
129
|
if (!filter.usedBy || !filter.usedBy.length) return true
|
|
116
|
-
const
|
|
130
|
+
const dashboardConditionTargets = getDashboardConditionTargets(rows)
|
|
117
131
|
|
|
118
|
-
return
|
|
132
|
+
return filter.usedBy.some(vizOrRowKey => {
|
|
133
|
+
const viz = visualizations[vizOrRowKey] || rows[vizOrRowKey]
|
|
119
134
|
const usedByViz = viz?.dataKey === datasetKey
|
|
120
135
|
// datasetKey might be a key to a dynamic footnotes URL
|
|
121
136
|
const usedByVizFootnote = viz?.footnotes?.dataKey === datasetKey
|
|
122
|
-
|
|
137
|
+
const usedByDashboardCondition = dashboardConditionTargets.some(
|
|
138
|
+
conditionTarget =>
|
|
139
|
+
conditionTarget.dashboardCondition.datasetKey === datasetKey &&
|
|
140
|
+
`${conditionTarget.filterTarget}` === `${vizOrRowKey}`
|
|
141
|
+
)
|
|
142
|
+
return usedByViz || usedByVizFootnote || usedByDashboardCondition
|
|
123
143
|
})
|
|
124
144
|
}
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
2
|
-
import _ from 'lodash'
|
|
3
|
-
import { SharedFilter } from '../types/SharedFilter'
|
|
4
|
-
|
|
5
|
-
type Viz = Record<string, AnyVisualization>
|
|
6
|
-
|
|
7
|
-
export const removeDashboardFilter = (
|
|
8
|
-
index,
|
|
9
|
-
sharedFilters: SharedFilter[],
|
|
10
|
-
visualizations: Viz
|
|
11
|
-
): [SharedFilter[], Viz] => {
|
|
12
|
-
const newSharedFilters = _.cloneDeep(sharedFilters)
|
|
13
|
-
|
|
14
|
-
newSharedFilters.splice(index, 1)
|
|
15
|
-
const shiftDownIndexes = Object.keys(sharedFilters).slice(index + 1)
|
|
16
|
-
const newVisualizations: Viz = _.cloneDeep(visualizations)
|
|
17
|
-
Object.keys(newVisualizations).forEach(vizKey => {
|
|
18
|
-
const viz = newVisualizations[vizKey]
|
|
19
|
-
if (viz.type === 'dashboardFilters') {
|
|
20
|
-
// shift the indexes down
|
|
21
|
-
const sharedFilterIndexes = viz.sharedFilterIndexes
|
|
22
|
-
.filter(filterIndex => filterIndex != index)
|
|
23
|
-
.map(filterIndex => {
|
|
24
|
-
if (shiftDownIndexes.includes(filterIndex.toString())) {
|
|
25
|
-
return filterIndex - 1
|
|
26
|
-
}
|
|
27
|
-
return filterIndex
|
|
28
|
-
})
|
|
29
|
-
newVisualizations[vizKey].sharedFilterIndexes = sharedFilterIndexes
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
return [newSharedFilters, newVisualizations]
|
|
33
|
-
}
|
|
1
|
+
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
2
|
+
import _ from 'lodash'
|
|
3
|
+
import { SharedFilter } from '../types/SharedFilter'
|
|
4
|
+
|
|
5
|
+
type Viz = Record<string, AnyVisualization>
|
|
6
|
+
|
|
7
|
+
export const removeDashboardFilter = (
|
|
8
|
+
index,
|
|
9
|
+
sharedFilters: SharedFilter[],
|
|
10
|
+
visualizations: Viz
|
|
11
|
+
): [SharedFilter[], Viz] => {
|
|
12
|
+
const newSharedFilters = _.cloneDeep(sharedFilters)
|
|
13
|
+
|
|
14
|
+
newSharedFilters.splice(index, 1)
|
|
15
|
+
const shiftDownIndexes = Object.keys(sharedFilters).slice(index + 1)
|
|
16
|
+
const newVisualizations: Viz = _.cloneDeep(visualizations)
|
|
17
|
+
Object.keys(newVisualizations).forEach(vizKey => {
|
|
18
|
+
const viz = newVisualizations[vizKey]
|
|
19
|
+
if (viz.type === 'dashboardFilters') {
|
|
20
|
+
// shift the indexes down
|
|
21
|
+
const sharedFilterIndexes = (viz.sharedFilterIndexes ?? [])
|
|
22
|
+
.filter(filterIndex => filterIndex != index)
|
|
23
|
+
.map(filterIndex => {
|
|
24
|
+
if (shiftDownIndexes.includes(filterIndex.toString())) {
|
|
25
|
+
return filterIndex - 1
|
|
26
|
+
}
|
|
27
|
+
return filterIndex
|
|
28
|
+
})
|
|
29
|
+
newVisualizations[vizKey].sharedFilterIndexes = sharedFilterIndexes
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
return [newSharedFilters, newVisualizations]
|
|
33
|
+
}
|