@cdc/dashboard 4.26.2 → 4.26.3
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/LICENSE +201 -0
- package/dist/cdcdashboard-vr9HZwRt.es.js +6 -0
- package/dist/cdcdashboard.js +53345 -49681
- package/examples/custom/css/respiratory.css +1 -1
- package/examples/data/data-with-metadata.json +18 -0
- package/examples/default.json +7 -36
- package/examples/private/inline-markup.json +775 -0
- package/examples/private/recent-update.json +1456 -0
- package/examples/private/toggle.json +10137 -0
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +2 -1
- package/src/CdcDashboardComponent.tsx +47 -27
- package/src/_stories/Dashboard.DataSetup.stories.tsx +6 -1
- package/src/_stories/Dashboard.Pages.stories.tsx +22 -0
- package/src/_stories/Dashboard.stories.tsx +4406 -7
- package/src/_stories/_mock/tab-simple-filter.json +153 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +19 -3
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +7 -4
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +1 -1
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +1 -2
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +8 -7
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +8 -8
- package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +1 -1
- package/src/components/DashboardFilters/dashboardfilter.styles.css +3 -3
- package/src/components/DataDesignerModal.tsx +2 -2
- package/src/components/Header/Header.tsx +27 -5
- package/src/components/Header/index.scss +1 -1
- package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +6 -6
- package/src/components/Row.tsx +21 -0
- package/src/components/Toggle/toggle-style.css +7 -7
- package/src/components/VisualizationRow.tsx +12 -4
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +1 -54
- package/src/components/VisualizationsPanel/visualizations-panel-styles.css +2 -2
- package/src/components/Widget/Widget.tsx +2 -2
- package/src/components/Widget/widget.styles.css +12 -12
- package/src/data/initial-state.js +1 -1
- package/src/helpers/addVisualization.ts +71 -0
- package/src/helpers/formatConfigBeforeSave.ts +1 -1
- package/src/helpers/getVizConfig.ts +13 -3
- package/src/helpers/iconHash.tsx +45 -36
- package/src/helpers/processDataLegacy.ts +19 -14
- package/src/helpers/tests/addVisualization.test.ts +52 -0
- package/src/helpers/tests/formatConfigBeforeSave.test.ts +81 -1
- package/src/scss/editor-panel.scss +1 -1
- package/src/scss/main.scss +164 -39
- package/src/store/dashboard.reducer.ts +1 -1
- package/src/test/CdcDashboard.test.jsx +2 -2
- package/src/test/CdcDashboardComponent.test.tsx +74 -0
- package/src/types/FilterStyles.ts +2 -1
- package/tests/fixtures/dashboard-config-with-metadata.json +89 -0
- package/vite.config.js +2 -2
- package/dist/cdcdashboard-Cf9_fbQf.es.js +0 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
|
|
2
2
|
import React, { useContext, useEffect, useMemo, useState } from 'react'
|
|
3
3
|
import Toggle from './Toggle'
|
|
4
|
-
import
|
|
4
|
+
import cloneDeep from 'lodash/cloneDeep'
|
|
5
5
|
import { ConfigRow } from '../types/ConfigRow'
|
|
6
6
|
import CdcDataBite from '@cdc/data-bite/src/CdcDataBite'
|
|
7
7
|
import CdcMap from '@cdc/map/src/CdcMapComponent'
|
|
@@ -140,6 +140,8 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
140
140
|
|
|
141
141
|
// Equalize TP5 callout title heights and TP5 gauge message blocks for like visualizations in the same row
|
|
142
142
|
useEffect(() => {
|
|
143
|
+
if (!row.equalHeight) return
|
|
144
|
+
|
|
143
145
|
const rowElement = document.querySelector(`[data-row-index="${index}"]`)
|
|
144
146
|
if (!rowElement) return
|
|
145
147
|
|
|
@@ -155,6 +157,11 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
155
157
|
}
|
|
156
158
|
}, [index, row, config, filteredDataOverride])
|
|
157
159
|
|
|
160
|
+
const isFilterRow = row.columns.some(
|
|
161
|
+
col => col.widget && config.visualizations[col.widget]?.type === 'dashboardFilters'
|
|
162
|
+
)
|
|
163
|
+
const needsEqualHeight = !!row.equalHeight && !isFilterRow
|
|
164
|
+
|
|
158
165
|
const show = useMemo(() => {
|
|
159
166
|
if (row.toggle) {
|
|
160
167
|
return row.columns.map((col, i) => i === toggledRow)
|
|
@@ -201,7 +208,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
201
208
|
)}
|
|
202
209
|
{Object.keys(dataGroups).map(groupName => {
|
|
203
210
|
const dataValue = dataGroups[groupName]
|
|
204
|
-
const _row =
|
|
211
|
+
const _row = cloneDeep(row) // clone the row to avoid mutating the original row
|
|
205
212
|
const originalMultiVizColumn = _row.multiVizColumn // store original value before clearing
|
|
206
213
|
_row.multiVizColumn = undefined // reset the multiVizColumn to avoid passing it to the child components
|
|
207
214
|
_row.originalMultiVizColumn = originalMultiVizColumn // store for footnote filtering
|
|
@@ -229,7 +236,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
229
236
|
|
|
230
237
|
return (
|
|
231
238
|
<div
|
|
232
|
-
className={`row${
|
|
239
|
+
className={`row${needsEqualHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`}
|
|
233
240
|
key={`row__${index}`}
|
|
234
241
|
data-row-index={index}
|
|
235
242
|
>
|
|
@@ -352,6 +359,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
352
359
|
updateChildConfig(col.widget, newConfig)
|
|
353
360
|
}}
|
|
354
361
|
isDashboard={true}
|
|
362
|
+
isEditor={config.editing === true}
|
|
355
363
|
interactionLabel={interactionLabel}
|
|
356
364
|
/>
|
|
357
365
|
)}
|
|
@@ -363,7 +371,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
363
371
|
updateChildConfig(col.widget, newConfig)
|
|
364
372
|
}}
|
|
365
373
|
isDashboard={true}
|
|
366
|
-
interactionLabel={
|
|
374
|
+
interactionLabel={interactionLabel}
|
|
367
375
|
/>
|
|
368
376
|
)}
|
|
369
377
|
{type === 'markup-include' && (
|
|
@@ -1,66 +1,13 @@
|
|
|
1
1
|
import { useContext, useState } from 'react'
|
|
2
|
-
import type { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
3
2
|
import Widget from '../Widget/Widget'
|
|
4
3
|
import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
|
|
5
|
-
import { Table } from '@cdc/core/types/Table'
|
|
6
4
|
import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
|
|
5
|
+
import { addVisualization } from '../../helpers/addVisualization'
|
|
7
6
|
import { mapDataToConfig } from '../../helpers/mapDataToConfig'
|
|
8
7
|
import './visualizations-panel-styles.css'
|
|
9
8
|
import { MultiDashboardConfig } from '../../types/MultiDashboard'
|
|
10
9
|
import { stripConfig } from '../../helpers/formatConfigBeforeSave'
|
|
11
10
|
|
|
12
|
-
const addVisualization = (type, subType) => {
|
|
13
|
-
const modalWillOpen = type !== 'markup-include'
|
|
14
|
-
const newVisualizationConfig: Partial<AnyVisualization> = {
|
|
15
|
-
filters: [],
|
|
16
|
-
filterBehavior: 'Filter Change',
|
|
17
|
-
newViz: type !== 'table',
|
|
18
|
-
openModal: modalWillOpen,
|
|
19
|
-
uid: type + Date.now(),
|
|
20
|
-
type
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
switch (type) {
|
|
24
|
-
case 'chart':
|
|
25
|
-
newVisualizationConfig.visualizationType = subType
|
|
26
|
-
break
|
|
27
|
-
case 'map':
|
|
28
|
-
newVisualizationConfig.general = {}
|
|
29
|
-
newVisualizationConfig.general.geoType = subType
|
|
30
|
-
break
|
|
31
|
-
case 'data-bite' || 'waffle-chart' || 'filtered-text':
|
|
32
|
-
newVisualizationConfig.visualizationType = type
|
|
33
|
-
break
|
|
34
|
-
case 'table':
|
|
35
|
-
const tableConfig: Table = {
|
|
36
|
-
label: 'Data Table',
|
|
37
|
-
show: true,
|
|
38
|
-
showDownloadUrl: false,
|
|
39
|
-
showVertical: true,
|
|
40
|
-
expanded: true,
|
|
41
|
-
collapsible: true
|
|
42
|
-
}
|
|
43
|
-
newVisualizationConfig.table = tableConfig
|
|
44
|
-
newVisualizationConfig.columns = {}
|
|
45
|
-
newVisualizationConfig.dataFormat = {}
|
|
46
|
-
newVisualizationConfig.visualizationType = type
|
|
47
|
-
break
|
|
48
|
-
case 'markup-include':
|
|
49
|
-
newVisualizationConfig.visualizationType = type
|
|
50
|
-
break
|
|
51
|
-
case 'dashboardFilters': {
|
|
52
|
-
newVisualizationConfig.sharedFilterIndexes = []
|
|
53
|
-
newVisualizationConfig.visualizationType = type
|
|
54
|
-
break
|
|
55
|
-
}
|
|
56
|
-
default:
|
|
57
|
-
newVisualizationConfig.visualizationType = type
|
|
58
|
-
break
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return newVisualizationConfig
|
|
62
|
-
}
|
|
63
|
-
|
|
64
11
|
const VisualizationsPanel = () => {
|
|
65
12
|
const [advancedEditing, setAdvancedEditing] = useState(false)
|
|
66
13
|
const { config, isEditor } = useContext(DashboardContext)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
.visualizations-panel {
|
|
2
2
|
background-color: #fff;
|
|
3
|
+
border-right: #c7c7c7 1px solid;
|
|
4
|
+
overflow-y: scroll;
|
|
3
5
|
padding: 1em;
|
|
4
6
|
width: var(--editorWidth);
|
|
5
|
-
border-right: #c7c7c7 1px solid;
|
|
6
7
|
z-index: 1;
|
|
7
|
-
overflow-y: scroll;
|
|
8
8
|
|
|
9
9
|
&.advanced-editor {
|
|
10
10
|
width: 50vw;
|
|
@@ -105,11 +105,11 @@ const Widget = ({
|
|
|
105
105
|
const url = changeDataLimit(dataset.dataUrl, 100)
|
|
106
106
|
if (url) {
|
|
107
107
|
fetchRemoteData(url)
|
|
108
|
-
.then(responseData => {
|
|
108
|
+
.then(({ data: responseData }) => {
|
|
109
109
|
// this sample data is temporary.
|
|
110
110
|
// the HEADER component removes the data when you toggle to the main viz panel.
|
|
111
111
|
// data will be cached only when it's loaded via dashboard preview.
|
|
112
|
-
responseData.sample = true
|
|
112
|
+
;(responseData as any).sample = true
|
|
113
113
|
dispatch({ type: 'SET_DATA', payload: { ...data, [dataKey]: responseData } })
|
|
114
114
|
})
|
|
115
115
|
.catch(error => {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
.widget__toggle-title {
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-direction: column;
|
|
4
|
-
justify-content: center;
|
|
5
2
|
align-items: center;
|
|
6
|
-
width: 100%;
|
|
7
|
-
height: 25%;
|
|
8
3
|
background-color: #005eaa;
|
|
9
4
|
color: white;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
height: 25%;
|
|
8
|
+
justify-content: center;
|
|
10
9
|
padding: 5px;
|
|
10
|
+
width: 100%;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
.widget__edit-title-icon {
|
|
@@ -19,37 +19,37 @@
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
.widget--toggle .widget__content .widget-menu + svg {
|
|
22
|
+
flex-grow: unset;
|
|
22
23
|
height: 35px !important;
|
|
23
24
|
width: 35px !important;
|
|
24
|
-
flex-grow: unset;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
.widget-menu {
|
|
28
28
|
position: absolute;
|
|
29
|
-
top: 6px;
|
|
30
29
|
right: 6px;
|
|
30
|
+
top: 6px;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
.widget-menu {
|
|
34
|
-
display: flex;
|
|
35
34
|
align-items: center;
|
|
35
|
+
display: flex;
|
|
36
36
|
justify-content: space-between;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
.btn-configure {
|
|
40
40
|
background: none;
|
|
41
|
-
width: 20px;
|
|
42
41
|
height: 20px;
|
|
43
|
-
padding: 0;
|
|
44
42
|
margin: 0 5px;
|
|
43
|
+
padding: 0;
|
|
44
|
+
width: 20px;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
.widget-menu-item {
|
|
48
|
+
cursor: pointer;
|
|
48
49
|
display: block;
|
|
49
|
-
width: 20px;
|
|
50
50
|
height: 20px;
|
|
51
|
-
cursor: pointer;
|
|
52
51
|
user-select: none;
|
|
52
|
+
width: 20px;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
.widget-menu-item svg {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
2
|
+
import type { Table } from '@cdc/core/types/Table'
|
|
3
|
+
|
|
4
|
+
export const addVisualization = (type, subType) => {
|
|
5
|
+
const modalWillOpen = type !== 'markup-include'
|
|
6
|
+
const newVisualizationConfig: Partial<AnyVisualization> = {
|
|
7
|
+
filters: [],
|
|
8
|
+
filterBehavior: 'Filter Change',
|
|
9
|
+
newViz: type !== 'table',
|
|
10
|
+
openModal: modalWillOpen,
|
|
11
|
+
uid: type + Date.now(),
|
|
12
|
+
type
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
switch (type) {
|
|
16
|
+
case 'chart':
|
|
17
|
+
newVisualizationConfig.visual = {
|
|
18
|
+
border: false,
|
|
19
|
+
borderColorTheme: false,
|
|
20
|
+
accent: false,
|
|
21
|
+
background: false,
|
|
22
|
+
hideBackgroundColor: false
|
|
23
|
+
}
|
|
24
|
+
newVisualizationConfig.visualizationType = subType
|
|
25
|
+
break
|
|
26
|
+
case 'map':
|
|
27
|
+
newVisualizationConfig.general = {}
|
|
28
|
+
newVisualizationConfig.general.geoType = subType
|
|
29
|
+
newVisualizationConfig.visual = {
|
|
30
|
+
border: false,
|
|
31
|
+
borderColorTheme: false,
|
|
32
|
+
accent: false,
|
|
33
|
+
background: false,
|
|
34
|
+
hideBackgroundColor: false
|
|
35
|
+
}
|
|
36
|
+
break
|
|
37
|
+
case 'data-bite':
|
|
38
|
+
case 'waffle-chart':
|
|
39
|
+
case 'filtered-text':
|
|
40
|
+
newVisualizationConfig.visualizationType = type
|
|
41
|
+
break
|
|
42
|
+
case 'table': {
|
|
43
|
+
const tableConfig: Table = {
|
|
44
|
+
label: 'Data Table',
|
|
45
|
+
show: true,
|
|
46
|
+
showDownloadUrl: false,
|
|
47
|
+
showVertical: true,
|
|
48
|
+
expanded: true,
|
|
49
|
+
collapsible: true
|
|
50
|
+
}
|
|
51
|
+
newVisualizationConfig.table = tableConfig
|
|
52
|
+
newVisualizationConfig.columns = {}
|
|
53
|
+
newVisualizationConfig.dataFormat = {}
|
|
54
|
+
newVisualizationConfig.visualizationType = type
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
case 'markup-include':
|
|
58
|
+
newVisualizationConfig.visualizationType = type
|
|
59
|
+
break
|
|
60
|
+
case 'dashboardFilters': {
|
|
61
|
+
newVisualizationConfig.sharedFilterIndexes = []
|
|
62
|
+
newVisualizationConfig.visualizationType = type
|
|
63
|
+
break
|
|
64
|
+
}
|
|
65
|
+
default:
|
|
66
|
+
newVisualizationConfig.visualizationType = type
|
|
67
|
+
break
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return newVisualizationConfig
|
|
71
|
+
}
|
|
@@ -129,7 +129,7 @@ export const stripConfig = (configToStrip, isEditor = false) => {
|
|
|
129
129
|
} else {
|
|
130
130
|
delete strippedConfig.runtime
|
|
131
131
|
delete strippedConfig.formattedData
|
|
132
|
-
if (strippedConfig.dataUrl) {
|
|
132
|
+
if (strippedConfig.dataUrl && !isEditor) {
|
|
133
133
|
delete strippedConfig.data
|
|
134
134
|
}
|
|
135
135
|
}
|
|
@@ -90,6 +90,9 @@ export const getVizConfig = (
|
|
|
90
90
|
|
|
91
91
|
if (visualizationConfig.formattedData) visualizationConfig.originalFormattedData = visualizationConfig.formattedData
|
|
92
92
|
const filteredVizData = filteredData?.[rowNumber] ?? filteredData?.[visualizationKey]
|
|
93
|
+
const dataKey = visualizationConfig.dataKey || 'backwards-compatibility'
|
|
94
|
+
|
|
95
|
+
visualizationConfig.dataMetadata = config.datasets[dataKey]?.dataMetadata || {}
|
|
93
96
|
|
|
94
97
|
if (filteredVizData) {
|
|
95
98
|
visualizationConfig.data = filteredVizData || []
|
|
@@ -97,9 +100,16 @@ export const getVizConfig = (
|
|
|
97
100
|
visualizationConfig.formattedData = visualizationConfig.data
|
|
98
101
|
}
|
|
99
102
|
} else {
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
+
// Clear data for charts/maps when shared filters exist but filtered data
|
|
104
|
+
// hasn't arrived yet — prevents rendering the full unfiltered dataset as DOM.
|
|
105
|
+
// Lighter types (data-bite, waffle-chart, filtered-text, markup-include) are
|
|
106
|
+
// excluded: they only compute scalars or single elements, and their editor
|
|
107
|
+
// panels need data to populate column dropdowns. Ideally data filters would
|
|
108
|
+
// apply synchronously before render, but they currently go through the same
|
|
109
|
+
// async loadAPIFilters pipeline as API filters, so filtered data isn't
|
|
110
|
+
// available on first render.
|
|
111
|
+
const heavyVizTypes = ['chart', 'map']
|
|
112
|
+
const shouldClearData = sharedFilterColumns.length && heavyVizTypes.includes(visualizationConfig.type)
|
|
103
113
|
visualizationConfig.data = shouldClearData ? [] : data[dataKey] || []
|
|
104
114
|
if (visualizationConfig.formattedData) {
|
|
105
115
|
visualizationConfig.formattedData =
|
package/src/helpers/iconHash.tsx
CHANGED
|
@@ -1,36 +1,45 @@
|
|
|
1
|
-
import Icon from '@cdc/core/components/ui/Icon'
|
|
2
|
-
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
3
|
-
|
|
4
|
-
export const iconHash = {
|
|
5
|
-
'data-bite': <Icon display='databite' base />,
|
|
6
|
-
Bar: <Icon display='chartBar' base />,
|
|
7
|
-
'Spark Line': <Icon display='chartLine' />,
|
|
8
|
-
'Bump Chart': <Icon display='chartLine' />,
|
|
9
|
-
'waffle-chart': <Icon display='grid' base />,
|
|
10
|
-
'markup-include': <Icon display='code' base />,
|
|
11
|
-
Line: <Icon display='chartLine' base />,
|
|
12
|
-
Pie: <Icon display='chartPie' base />,
|
|
13
|
-
us: <Icon display='mapUsa' base />,
|
|
14
|
-
'us-county': <Icon display='mapUsa' base />,
|
|
15
|
-
world: <Icon display='mapWorld' base />,
|
|
16
|
-
'single-state': <Icon display='mapAl' base />,
|
|
17
|
-
gear: <Icon display='gear' base />,
|
|
18
|
-
gearMulti: <Icon display='gearMulti' base />,
|
|
19
|
-
tools: <Icon display='tools' base />,
|
|
20
|
-
'filtered-text': <Icon display='filtered-text' base />,
|
|
21
|
-
dashboardFilters: <Icon display='dashboardFilters' base />,
|
|
22
|
-
table: <Icon display='table' base />,
|
|
23
|
-
Sankey: <Icon display='sankey' base
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
1
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
2
|
+
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
3
|
+
|
|
4
|
+
export const iconHash = {
|
|
5
|
+
'data-bite': <Icon display='databite' base />,
|
|
6
|
+
Bar: <Icon display='chartBar' base />,
|
|
7
|
+
'Spark Line': <Icon display='chartLine' />,
|
|
8
|
+
'Bump Chart': <Icon display='chartLine' />,
|
|
9
|
+
'waffle-chart': <Icon display='grid' base />,
|
|
10
|
+
'markup-include': <Icon display='code' base />,
|
|
11
|
+
Line: <Icon display='chartLine' base />,
|
|
12
|
+
Pie: <Icon display='chartPie' base />,
|
|
13
|
+
us: <Icon display='mapUsa' base />,
|
|
14
|
+
'us-county': <Icon display='mapUsa' base />,
|
|
15
|
+
world: <Icon display='mapWorld' base />,
|
|
16
|
+
'single-state': <Icon display='mapAl' base />,
|
|
17
|
+
gear: <Icon display='gear' base />,
|
|
18
|
+
gearMulti: <Icon display='gearMulti' base />,
|
|
19
|
+
tools: <Icon display='tools' base />,
|
|
20
|
+
'filtered-text': <Icon display='filtered-text' base />,
|
|
21
|
+
dashboardFilters: <Icon display='dashboardFilters' base />,
|
|
22
|
+
table: <Icon display='table' base />,
|
|
23
|
+
Sankey: <Icon display='sankey' base />,
|
|
24
|
+
Combo: <Icon display='chartBar' base />,
|
|
25
|
+
'Scatter Plot': <Icon display='chartBar' base />,
|
|
26
|
+
'Area Chart': <Icon display='chartLine' base />,
|
|
27
|
+
'Deviation Bar': <Icon display='chartBar' base />,
|
|
28
|
+
'Paired Bar': <Icon display='chartBar' base />,
|
|
29
|
+
'Box Plot': <Icon display='chartBar' base />,
|
|
30
|
+
'Forest Plot': <Icon display='chartBar' base />,
|
|
31
|
+
Forecasting: <Icon display='chartLine' base />,
|
|
32
|
+
'Warming Stripes': <Icon display='chartBar' base />
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const getIcon = (visualization: AnyVisualization) => {
|
|
36
|
+
const { type, visualizationType, general } = visualization
|
|
37
|
+
if (visualizationType) return iconHash[visualizationType]
|
|
38
|
+
if (general?.geoType) {
|
|
39
|
+
// for visualizations, mismatching state and state icon is not desired
|
|
40
|
+
// so instead of showing alabama as the default state icon we show the US icon.
|
|
41
|
+
if (general.geoType === 'single-state') return iconHash['us']
|
|
42
|
+
return iconHash[general.geoType]
|
|
43
|
+
}
|
|
44
|
+
return iconHash[type]
|
|
45
|
+
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
2
|
-
import { getFormattedData } from './getFormattedData'
|
|
3
|
-
|
|
4
|
-
export const processDataLegacy = async (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
2
|
+
import { getFormattedData } from './getFormattedData'
|
|
3
|
+
|
|
4
|
+
export const processDataLegacy = async (
|
|
5
|
+
response: any
|
|
6
|
+
): Promise<{ data: any[]; dataMetadata: Record<string, string> }> => {
|
|
7
|
+
let dataset = response.formattedData || response.data
|
|
8
|
+
let dataMetadata: Record<string, string> = {}
|
|
9
|
+
|
|
10
|
+
if (response.dataUrl) {
|
|
11
|
+
const result = await fetchRemoteData(`${response.dataUrl}`)
|
|
12
|
+
dataset = result.data
|
|
13
|
+
dataMetadata = result.dataMetadata
|
|
14
|
+
|
|
15
|
+
dataset = getFormattedData(dataset, response.dataDescription)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return { data: dataset, dataMetadata }
|
|
19
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { addVisualization } from '../addVisualization'
|
|
3
|
+
|
|
4
|
+
describe('addVisualization', () => {
|
|
5
|
+
it('creates chart visual settings with extra theme toggles disabled by default', () => {
|
|
6
|
+
vi.spyOn(Date, 'now').mockReturnValue(12345)
|
|
7
|
+
|
|
8
|
+
const visualization = addVisualization('chart', 'Bar')
|
|
9
|
+
|
|
10
|
+
expect(visualization).toMatchObject({
|
|
11
|
+
uid: 'chart12345',
|
|
12
|
+
type: 'chart',
|
|
13
|
+
visualizationType: 'Bar',
|
|
14
|
+
visual: {
|
|
15
|
+
border: false,
|
|
16
|
+
borderColorTheme: false,
|
|
17
|
+
accent: false,
|
|
18
|
+
background: false,
|
|
19
|
+
hideBackgroundColor: false
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('creates map visual settings with extra theme toggles disabled by default', () => {
|
|
25
|
+
vi.spyOn(Date, 'now').mockReturnValue(12345)
|
|
26
|
+
|
|
27
|
+
const visualization = addVisualization('map', 'single-state')
|
|
28
|
+
|
|
29
|
+
expect(visualization).toMatchObject({
|
|
30
|
+
uid: 'map12345',
|
|
31
|
+
type: 'map',
|
|
32
|
+
general: {
|
|
33
|
+
geoType: 'single-state'
|
|
34
|
+
},
|
|
35
|
+
visual: {
|
|
36
|
+
border: false,
|
|
37
|
+
borderColorTheme: false,
|
|
38
|
+
accent: false,
|
|
39
|
+
background: false,
|
|
40
|
+
hideBackgroundColor: false
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('preserves visualizationType for data-bite family visualizations', () => {
|
|
46
|
+
vi.spyOn(Date, 'now').mockReturnValue(12345)
|
|
47
|
+
|
|
48
|
+
expect(addVisualization('data-bite')).toMatchObject({ visualizationType: 'data-bite' })
|
|
49
|
+
expect(addVisualization('waffle-chart')).toMatchObject({ visualizationType: 'waffle-chart' })
|
|
50
|
+
expect(addVisualization('filtered-text')).toMatchObject({ visualizationType: 'filtered-text' })
|
|
51
|
+
})
|
|
52
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { cleanSharedFilters } from '../formatConfigBeforeSave'
|
|
2
|
+
import { cleanSharedFilters, stripConfig } from '../formatConfigBeforeSave'
|
|
3
3
|
import { DashboardConfig } from '../../types/DashboardConfig'
|
|
4
4
|
|
|
5
5
|
describe('cleanSharedFilters', () => {
|
|
@@ -67,3 +67,83 @@ describe('cleanSharedFilters', () => {
|
|
|
67
67
|
expect(config.dashboard.sharedFilters).toEqual([{ id: 1, type: 'urlfilter' }])
|
|
68
68
|
})
|
|
69
69
|
})
|
|
70
|
+
|
|
71
|
+
describe('stripConfig', () => {
|
|
72
|
+
it('removes inline data for non-dashboard URL-backed configs when isEditor is false', () => {
|
|
73
|
+
const config = {
|
|
74
|
+
type: 'bar',
|
|
75
|
+
dataUrl: '/api/data.csv',
|
|
76
|
+
data: [{ value: 10 }],
|
|
77
|
+
runtime: { loaded: true },
|
|
78
|
+
formattedData: [{ value: 10 }]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const stripped = stripConfig(config)
|
|
82
|
+
|
|
83
|
+
expect(stripped.data).toBeUndefined()
|
|
84
|
+
expect(stripped.dataUrl).toBe('/api/data.csv')
|
|
85
|
+
expect(stripped.runtime).toBeUndefined()
|
|
86
|
+
expect(stripped.formattedData).toBeUndefined()
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('preserves inline data for non-dashboard URL-backed configs when isEditor is true', () => {
|
|
90
|
+
const config = {
|
|
91
|
+
type: 'bar',
|
|
92
|
+
dataUrl: '/api/data.csv',
|
|
93
|
+
data: [{ value: 10 }],
|
|
94
|
+
runtime: { loaded: true },
|
|
95
|
+
formattedData: [{ value: 10 }]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const stripped = stripConfig(config, true)
|
|
99
|
+
|
|
100
|
+
expect(stripped.data).toEqual([{ value: 10 }])
|
|
101
|
+
expect(stripped.dataUrl).toBe('/api/data.csv')
|
|
102
|
+
expect(stripped.runtime).toBeUndefined()
|
|
103
|
+
expect(stripped.formattedData).toBeUndefined()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('removes dashboard dataset data when dataset has dataUrl and isEditor is false', () => {
|
|
107
|
+
const config = {
|
|
108
|
+
type: 'dashboard',
|
|
109
|
+
dashboard: { sharedFilters: [] },
|
|
110
|
+
datasets: {
|
|
111
|
+
data_1: {
|
|
112
|
+
dataUrl: '/api/dashboard.csv',
|
|
113
|
+
data: [{ a: 1 }],
|
|
114
|
+
formattedData: [{ a: 1 }]
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
visualizations: {},
|
|
118
|
+
rows: []
|
|
119
|
+
} as any
|
|
120
|
+
|
|
121
|
+
const stripped = stripConfig(config)
|
|
122
|
+
|
|
123
|
+
expect(stripped.datasets.data_1.data).toBeUndefined()
|
|
124
|
+
expect(stripped.datasets.data_1.formattedData).toBeUndefined()
|
|
125
|
+
expect(stripped.datasets.data_1.dataUrl).toBe('/api/dashboard.csv')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('preserves dashboard dataset data when dataset has dataUrl and isEditor is true', () => {
|
|
129
|
+
const config = {
|
|
130
|
+
type: 'dashboard',
|
|
131
|
+
dashboard: { sharedFilters: [] },
|
|
132
|
+
datasets: {
|
|
133
|
+
data_1: {
|
|
134
|
+
dataUrl: '/api/dashboard.csv',
|
|
135
|
+
data: [{ a: 1 }],
|
|
136
|
+
formattedData: [{ a: 1 }]
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
visualizations: {},
|
|
140
|
+
rows: []
|
|
141
|
+
} as any
|
|
142
|
+
|
|
143
|
+
const stripped = stripConfig(config, true)
|
|
144
|
+
|
|
145
|
+
expect(stripped.datasets.data_1.data).toEqual([{ a: 1 }])
|
|
146
|
+
expect(stripped.datasets.data_1.formattedData).toBeUndefined()
|
|
147
|
+
expect(stripped.datasets.data_1.dataUrl).toBe('/api/dashboard.csv')
|
|
148
|
+
})
|
|
149
|
+
})
|