@cdc/dashboard 4.25.3 → 4.25.5-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 +79 -0
- package/Override_Data.md +39 -0
- package/dist/cdcdashboard.js +67657 -70123
- package/examples/legend-issue-data.json +1874 -0
- package/examples/legend-issue.json +749 -0
- package/examples/map.json +628 -0
- package/index.html +1 -26
- package/package.json +10 -15
- package/src/CdcDashboardComponent.tsx +32 -5
- package/src/_stories/Dashboard.stories.tsx +2 -0
- package/src/_stories/_mock/api-filter-map.json +42 -1
- package/src/components/CollapsibleVisualizationRow.tsx +2 -2
- package/src/components/DashboardFilters/DashboardFilters.tsx +1 -1
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +129 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +680 -652
- package/src/components/DataDesignerModal.tsx +33 -14
- package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +3 -0
- package/src/components/Row.tsx +2 -1
- package/src/components/VisualizationRow.tsx +1 -1
- package/src/helpers/reloadURLHelpers.ts +10 -2
- package/src/helpers/shouldLoadAllFilters.ts +30 -30
- package/src/index.tsx +2 -1
- package/src/scss/main.scss +0 -5
- package/src/store/dashboard.actions.ts +2 -0
- package/src/store/dashboard.reducer.ts +15 -0
- package/src/types/DataSet.ts +1 -0
- package/src/types/SharedFilter.ts +2 -0
- package/LICENSE +0 -201
- package/examples/private/DEV-10120.json +0 -1294
- package/examples/private/DEV-10527.json +0 -845
- package/examples/private/DEV-10586.json +0 -54319
- package/examples/private/DEV-10856.json +0 -54319
- package/examples/private/DEV-9199.json +0 -606
- package/examples/private/DEV-9644.json +0 -20092
- package/examples/private/DEV-9684.json +0 -2135
- package/examples/private/DEV-9932.json +0 -95
- package/examples/private/DEV-9989.json +0 -229
- package/examples/private/art-dashboard.json +0 -18174
- package/examples/private/art-scratch.json +0 -2406
- package/examples/private/bird-flu-2.json +0 -440
- package/examples/private/bird-flu.json +0 -413
- package/examples/private/dashboard-config-ehdi.json +0 -29915
- package/examples/private/dashboard-map-filter.json +0 -815
- package/examples/private/dashboard-margins.js +0 -15
- package/examples/private/dataset.json +0 -1452
- package/examples/private/dev-10856-2.json +0 -1348
- package/examples/private/ehdi-data.json +0 -29502
- package/examples/private/exposure-source-h5-data.csv +0 -26
- package/examples/private/fatal-data.csv +0 -3159
- package/examples/private/feelings.json +0 -1
- package/examples/private/gaza-issue.json +0 -1214
- package/examples/private/markup.json +0 -115
- package/examples/private/nhis.json +0 -1792
- package/examples/private/workforce.json +0 -2041
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/dashboard",
|
|
3
|
-
"version": "4.25.
|
|
3
|
+
"version": "4.25.5-1",
|
|
4
4
|
"description": "React component for combining multiple visualizations into a single dashboard",
|
|
5
5
|
"moduleName": "CdcDashboard",
|
|
6
6
|
"main": "dist/cdcdashboard",
|
|
@@ -27,27 +27,22 @@
|
|
|
27
27
|
},
|
|
28
28
|
"license": "Apache-2.0",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@cdc/chart": "^4.25.
|
|
31
|
-
"@cdc/core": "^4.25.
|
|
32
|
-
"@cdc/data-bite": "^4.25.
|
|
33
|
-
"@cdc/filtered-text": "^4.25.
|
|
34
|
-
"@cdc/map": "^4.25.
|
|
35
|
-
"@cdc/markup-include": "^4.25.
|
|
36
|
-
"@cdc/waffle-chart": "^4.25.
|
|
37
|
-
"html-react-parser": "^3.0.8",
|
|
30
|
+
"@cdc/chart": "^4.25.5-1",
|
|
31
|
+
"@cdc/core": "^4.25.5-1",
|
|
32
|
+
"@cdc/data-bite": "^4.25.5-1",
|
|
33
|
+
"@cdc/filtered-text": "^4.25.5-1",
|
|
34
|
+
"@cdc/map": "^4.25.5-1",
|
|
35
|
+
"@cdc/markup-include": "^4.25.5-1",
|
|
36
|
+
"@cdc/waffle-chart": "^4.25.5-1",
|
|
38
37
|
"js-base64": "^2.5.2",
|
|
39
|
-
"papaparse": "^5.3.0",
|
|
40
38
|
"react-accessible-accordion": "^3.3.4",
|
|
41
39
|
"react-dnd": "^14.0.2",
|
|
42
40
|
"react-dnd-html5-backend": "^14.0.0",
|
|
43
|
-
"
|
|
44
|
-
"react-tooltip": "5.8.2-beta.3",
|
|
45
|
-
"use-debounce": "^6.0.1",
|
|
46
|
-
"whatwg-fetch": "^3.6.2"
|
|
41
|
+
"use-debounce": "^6.0.1"
|
|
47
42
|
},
|
|
48
43
|
"peerDependencies": {
|
|
49
44
|
"react": "^18.2.0",
|
|
50
45
|
"react-dom": "^18.2.0"
|
|
51
46
|
},
|
|
52
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "1d0a6e716bc113e6bea3636fcae90ebf2d19bb5c"
|
|
53
48
|
}
|
|
@@ -21,7 +21,7 @@ import getViewport from '@cdc/core/helpers/getViewport'
|
|
|
21
21
|
|
|
22
22
|
import CdcChart from '@cdc/chart/src/CdcChartComponent'
|
|
23
23
|
import CdcDataBite from '@cdc/data-bite/src/CdcDataBite'
|
|
24
|
-
import CdcMap from '@cdc/map/src/
|
|
24
|
+
import CdcMap from '@cdc/map/src/CdcMapComponent'
|
|
25
25
|
import CdcWaffleChart from '@cdc/waffle-chart/src/CdcWaffleChart'
|
|
26
26
|
import CdcMarkupInclude from '@cdc/markup-include/src/CdcMarkupInclude'
|
|
27
27
|
import CdcFilteredText from '@cdc/filtered-text/src/CdcFilteredText'
|
|
@@ -66,6 +66,7 @@ import { loadAPIFiltersFactory } from './helpers/loadAPIFilters'
|
|
|
66
66
|
import Loader from '@cdc/core/components/Loader'
|
|
67
67
|
import Alert from '@cdc/core/components/Alert'
|
|
68
68
|
import { shouldLoadAllFilters } from './helpers/shouldLoadAllFilters'
|
|
69
|
+
import { subscribe, unsubscribe } from '@cdc/core/helpers/events'
|
|
69
70
|
|
|
70
71
|
type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
71
72
|
initialState: InitialState
|
|
@@ -120,12 +121,16 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
120
121
|
for (let i = 0; i < datasetKeys.length; i++) {
|
|
121
122
|
const datasetKey = datasetKeys[i]
|
|
122
123
|
const dataset = config.datasets[datasetKey]
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
const windowQueryParams = Object.fromEntries(new URLSearchParams(window.location.search))
|
|
125
|
+
const loadQueryParam = windowQueryParams[dataset.loadQueryParam || '']
|
|
126
|
+
if (dataset.dataUrl && (filters || loadQueryParam)) {
|
|
125
127
|
const dataUrl = new URL(dataset.runtimeDataUrl || dataset.dataUrl, window.location.origin)
|
|
126
128
|
const currentQSParams = Object.fromEntries(new URLSearchParams(dataUrl.search))
|
|
127
129
|
const updatedQSParams = {}
|
|
128
|
-
|
|
130
|
+
if (loadQueryParam) {
|
|
131
|
+
updatedQSParams[dataset.loadQueryParam] = loadQueryParam
|
|
132
|
+
}
|
|
133
|
+
filters?.forEach(filter => {
|
|
129
134
|
if (
|
|
130
135
|
filter.type === 'urlfilter' &&
|
|
131
136
|
reloadURLHelpers.filterUsedByDataUrl(filter, datasetKey, config.visualizations, config.rows)
|
|
@@ -143,7 +148,6 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
if (!!filter.setByQueryParameter) {
|
|
146
|
-
const windowQueryParams = Object.fromEntries(new URLSearchParams(window.location.search))
|
|
147
151
|
const filterValue = windowQueryParams[filter.setByQueryParameter]
|
|
148
152
|
const queryParam = filter.apiFilter?.valueSelector || filter.setByQueryParameter
|
|
149
153
|
if (filterValue) {
|
|
@@ -271,6 +275,29 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
271
275
|
dispatch({ type: 'SET_SHARED_FILTERS', payload: newConfig.dashboard.sharedFilters })
|
|
272
276
|
}
|
|
273
277
|
|
|
278
|
+
const setEventData = ({ detail }) => {
|
|
279
|
+
try {
|
|
280
|
+
const newDatasets = Object.keys(detail).reduce((acc, key) => {
|
|
281
|
+
if (state.data[key] !== undefined) {
|
|
282
|
+
acc[key] = detail[key]
|
|
283
|
+
}
|
|
284
|
+
return acc
|
|
285
|
+
}, {})
|
|
286
|
+
const filteredData = { ...state.filteredData, ...newDatasets }
|
|
287
|
+
dispatch({ type: 'SET_FILTERED_DATA', payload: filteredData })
|
|
288
|
+
dispatch({ type: 'SET_DATA', payload: { ...state.data, ...newDatasets } })
|
|
289
|
+
} catch (e) {
|
|
290
|
+
console.error('Error setting event data: ', e)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
subscribe('cove_set_data', setEventData)
|
|
296
|
+
return () => {
|
|
297
|
+
unsubscribe('cove_set_data', setEventData)
|
|
298
|
+
}
|
|
299
|
+
}, [])
|
|
300
|
+
|
|
274
301
|
useEffect(() => {
|
|
275
302
|
const { config } = state
|
|
276
303
|
const loadAllFilters = shouldLoadAllFilters(config, isEditor && !isPreview)
|
|
@@ -341,6 +341,8 @@ export const RegressionAPIFiltersMap: Story = {
|
|
|
341
341
|
await sleep(1000)
|
|
342
342
|
const topicsFilter = canvas.getByLabelText('Category', { selector: 'select' })
|
|
343
343
|
await user.selectOptions(topicsFilter, ['topicId'])
|
|
344
|
+
await sleep(1000)
|
|
345
|
+
|
|
344
346
|
const indicatorsFilter = canvas.getByLabelText('Indicator', { selector: 'select' })
|
|
345
347
|
await user.selectOptions(indicatorsFilter, ['indicatorID'])
|
|
346
348
|
const yearsFilter = canvas.getByLabelText('Year', { selector: 'select' })
|
|
@@ -113,6 +113,45 @@
|
|
|
113
113
|
"hideGeoColumnInTooltip": false,
|
|
114
114
|
"hidePrimaryColumnInTooltip": false
|
|
115
115
|
},
|
|
116
|
+
"tooltips": {
|
|
117
|
+
"appearanceType": "hover",
|
|
118
|
+
"linkLabel": "Learn More",
|
|
119
|
+
"capitalizeLabels": true,
|
|
120
|
+
"opacity": 90
|
|
121
|
+
},
|
|
122
|
+
"map": {
|
|
123
|
+
"layers": [],
|
|
124
|
+
"patterns": []
|
|
125
|
+
},
|
|
126
|
+
"visual": {
|
|
127
|
+
"minBubbleSize": 1,
|
|
128
|
+
"maxBubbleSize": 20,
|
|
129
|
+
"extraBubbleBorder": false,
|
|
130
|
+
"cityStyle": "circle",
|
|
131
|
+
"cityStyleLabel": "",
|
|
132
|
+
"showBubbleZeros": false,
|
|
133
|
+
"additionalCityStyles": [],
|
|
134
|
+
"geoCodeCircleSize": 8
|
|
135
|
+
},
|
|
136
|
+
"hexMap": {
|
|
137
|
+
"type": "",
|
|
138
|
+
"shapeGroups": [
|
|
139
|
+
{
|
|
140
|
+
"legendTitle": "",
|
|
141
|
+
"legendDescription": "",
|
|
142
|
+
"items": [
|
|
143
|
+
{
|
|
144
|
+
"key": "",
|
|
145
|
+
"shape": "Arrow Up",
|
|
146
|
+
"column": "",
|
|
147
|
+
"operator": "=",
|
|
148
|
+
"value": ""
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
},
|
|
154
|
+
"annotations": [],
|
|
116
155
|
"color": "greenBlue",
|
|
117
156
|
"columns": {
|
|
118
157
|
"geo": {
|
|
@@ -170,7 +209,8 @@
|
|
|
170
209
|
"position": "side",
|
|
171
210
|
"title": "Crude Prevalence",
|
|
172
211
|
"separateZero": true
|
|
173
|
-
}
|
|
212
|
+
},
|
|
213
|
+
"table": {}
|
|
174
214
|
}
|
|
175
215
|
},
|
|
176
216
|
"type": "dashboard",
|
|
@@ -179,6 +219,7 @@
|
|
|
179
219
|
"dataUrl": "http://test.gov/api/POC/TableData?DataValueTypeId=CRDMEDN"
|
|
180
220
|
}
|
|
181
221
|
},
|
|
222
|
+
"table": {},
|
|
182
223
|
"filterBehavior": "Apply Button",
|
|
183
224
|
"runtime": {},
|
|
184
225
|
"uuid": 1684783370106
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react'
|
|
2
2
|
import Icon from '../../../core/components/ui/Icon'
|
|
3
|
-
import {
|
|
3
|
+
import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
|
|
4
4
|
|
|
5
5
|
type CollapsableVizRow = {
|
|
6
6
|
allExpanded: boolean
|
|
@@ -17,7 +17,7 @@ const CollapsibleVisualizationRow: React.FC<CollapsableVizRow> = ({
|
|
|
17
17
|
children
|
|
18
18
|
}) => {
|
|
19
19
|
const [isExpanded, setIsExpanded] = useState(allExpanded)
|
|
20
|
-
const titleFontSize = ['xs', 'xxs'].includes(currentViewport) ? '13px' : `${
|
|
20
|
+
const titleFontSize = ['xs', 'xxs'].includes(currentViewport) ? '13px' : `${APP_FONT_SIZE}px`
|
|
21
21
|
|
|
22
22
|
useEffect(() => {
|
|
23
23
|
setIsExpanded(allExpanded)
|
|
@@ -8,7 +8,7 @@ import NestedDropdown from '@cdc/core/components/NestedDropdown'
|
|
|
8
8
|
import { MouseEventHandler } from 'react'
|
|
9
9
|
import Loader from '@cdc/core/components/Loader'
|
|
10
10
|
import _ from 'lodash'
|
|
11
|
-
import { DROPDOWN_STYLES } from '@cdc/core/components/Filters/
|
|
11
|
+
import { DROPDOWN_STYLES } from '@cdc/core/components/Filters/components/Dropdown'
|
|
12
12
|
|
|
13
13
|
type DashboardFilterProps = {
|
|
14
14
|
show: number[]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { SharedFilter } from '../../../../types/SharedFilter'
|
|
3
|
+
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
4
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
5
|
+
|
|
6
|
+
type APIModalProps = {
|
|
7
|
+
filter: SharedFilter
|
|
8
|
+
isNestedDropdown: boolean
|
|
9
|
+
updateAPIFilter: Function
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const APIModal: React.FC<APIModalProps> = ({ filter, isNestedDropdown, updateAPIFilter }) => {
|
|
13
|
+
const [APIEndpoint, setAPIEndpoint] = useState(filter.apiFilter?.apiEndpoint || '')
|
|
14
|
+
const [APIValueSelector, setAPIValueSelector] = useState(filter.apiFilter?.valueSelector || '')
|
|
15
|
+
const [APITextSelector, setAPITextSelector] = useState(filter.apiFilter?.textSelector || '')
|
|
16
|
+
const [APISubGroupValueSelector, setAPISubGroupValueSelector] = useState(filter.apiFilter?.subgroupValueSelector)
|
|
17
|
+
const [APISubGroupTextSelector, setAPISubGroupTextSelector] = useState(filter.apiFilter?.subgroupTextSelector)
|
|
18
|
+
return (
|
|
19
|
+
<fieldset className='mb-1 px-3 cdc-open-viz-module'>
|
|
20
|
+
<label className='d-block'>
|
|
21
|
+
<span>API Endpoint: </span>
|
|
22
|
+
<textarea
|
|
23
|
+
value={APIEndpoint}
|
|
24
|
+
rows={4}
|
|
25
|
+
onChange={e => setAPIEndpoint(e.target.value)}
|
|
26
|
+
className='w-100'
|
|
27
|
+
style={{ minHeight: '1.5rem', maxWidth: '90%' }}
|
|
28
|
+
/>
|
|
29
|
+
{isNestedDropdown && (
|
|
30
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
31
|
+
<Tooltip.Target>
|
|
32
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
33
|
+
</Tooltip.Target>
|
|
34
|
+
<Tooltip.Content>
|
|
35
|
+
<p>Your API Endpoint should return both value selector values.</p>
|
|
36
|
+
</Tooltip.Content>
|
|
37
|
+
</Tooltip>
|
|
38
|
+
)}
|
|
39
|
+
</label>
|
|
40
|
+
<div className='d-flex'>
|
|
41
|
+
<div className={`w-50${isNestedDropdown ? ' border border-dark p-1 m-1' : ''}`}>
|
|
42
|
+
<label>
|
|
43
|
+
<span>Value Selector: </span>
|
|
44
|
+
<input type='text' value={APIValueSelector || ''} onChange={e => setAPIValueSelector(e.target.value)} />
|
|
45
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
46
|
+
<Tooltip.Target>
|
|
47
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
48
|
+
</Tooltip.Target>
|
|
49
|
+
<Tooltip.Content>
|
|
50
|
+
<p>Value to use in the html option element</p>
|
|
51
|
+
</Tooltip.Content>
|
|
52
|
+
</Tooltip>
|
|
53
|
+
<p>{` * Required`}</p>
|
|
54
|
+
</label>
|
|
55
|
+
<label>
|
|
56
|
+
<span>Display Text Selector: </span>
|
|
57
|
+
<input type='text' value={APITextSelector || ''} onChange={e => setAPITextSelector(e.target.value)} />
|
|
58
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
59
|
+
<Tooltip.Target>
|
|
60
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
61
|
+
</Tooltip.Target>
|
|
62
|
+
<Tooltip.Content>
|
|
63
|
+
<p>Text to use in the html option element. If none is applied value selector will be used.</p>
|
|
64
|
+
</Tooltip.Content>
|
|
65
|
+
</Tooltip>
|
|
66
|
+
<p>{` * Optional`}</p>
|
|
67
|
+
</label>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
{isNestedDropdown && (
|
|
71
|
+
<div className={`w-50${isNestedDropdown ? ' border border-dark p-1 m-1' : ''}`}>
|
|
72
|
+
<label>
|
|
73
|
+
<span>Subgroup Value Selector: </span>
|
|
74
|
+
<input
|
|
75
|
+
type='text'
|
|
76
|
+
value={APISubGroupValueSelector || ''}
|
|
77
|
+
onChange={e => setAPISubGroupValueSelector(e.target.value)}
|
|
78
|
+
/>
|
|
79
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
80
|
+
<Tooltip.Target>
|
|
81
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
82
|
+
</Tooltip.Target>
|
|
83
|
+
<Tooltip.Content>
|
|
84
|
+
<p>Value to use in the html option element</p>
|
|
85
|
+
</Tooltip.Content>
|
|
86
|
+
</Tooltip>
|
|
87
|
+
<p>{` * Required`}</p>
|
|
88
|
+
</label>
|
|
89
|
+
<label>
|
|
90
|
+
<span>Subgroup Display Text Selector: </span>
|
|
91
|
+
<input
|
|
92
|
+
type='text'
|
|
93
|
+
value={APISubGroupTextSelector || ''}
|
|
94
|
+
onChange={e => setAPISubGroupTextSelector(e.target.value)}
|
|
95
|
+
/>
|
|
96
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
97
|
+
<Tooltip.Target>
|
|
98
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
99
|
+
</Tooltip.Target>
|
|
100
|
+
<Tooltip.Content>
|
|
101
|
+
<p>Text to use in the html option element. If none is applied value selector will be used.</p>
|
|
102
|
+
</Tooltip.Content>
|
|
103
|
+
</Tooltip>
|
|
104
|
+
<p>{` * Optional`}</p>
|
|
105
|
+
</label>
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
<div className='d-flex justify-content-end mt-2'>
|
|
110
|
+
<button
|
|
111
|
+
className='btn btn-primary mt-2'
|
|
112
|
+
onClick={() =>
|
|
113
|
+
updateAPIFilter(
|
|
114
|
+
APIEndpoint,
|
|
115
|
+
APIValueSelector,
|
|
116
|
+
APITextSelector,
|
|
117
|
+
APISubGroupValueSelector,
|
|
118
|
+
APISubGroupTextSelector
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
>
|
|
122
|
+
Save
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
</fieldset>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default APIModal
|