@cdc/dashboard 4.24.10 → 4.24.12-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcdashboard.js +51165 -49100
- package/examples/ed-visits-county-file.json +141 -357
- package/examples/private/DEV-10120.json +1294 -0
- package/examples/private/DEV-9199.json +606 -0
- package/examples/private/DEV-9644.json +20092 -0
- package/examples/private/DEV-9684.json +2135 -0
- package/examples/private/DEV-9989.json +229 -0
- package/examples/private/art-dashboard.json +18174 -0
- package/examples/private/art-scratch.json +2406 -0
- package/examples/private/dashboard-config-ehdi.json +29915 -0
- package/examples/private/dashboard-margins.js +15 -0
- package/examples/private/dataset.json +1452 -0
- package/examples/private/ehdi-data.json +29502 -0
- package/examples/private/gaza-issue.json +1214 -0
- package/examples/private/workforce.json +2041 -0
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +43 -29
- package/src/CdcDashboardComponent.tsx +91 -52
- package/src/DashboardContext.tsx +2 -0
- package/src/_stories/Dashboard.stories.tsx +8 -0
- package/src/_stories/_mock/api-filter-error.json +55 -0
- package/src/_stories/_mock/group-pivot-filter.json +10 -5
- package/src/components/CollapsibleVisualizationRow.tsx +8 -2
- package/src/components/DashboardFilters/DashboardFilters.tsx +121 -58
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +3 -1
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +54 -50
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +13 -7
- package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +21 -0
- package/src/components/DashboardFilters/dashboardfilter.styles.css +27 -0
- package/src/components/Grid.tsx +1 -1
- package/src/components/Header/Header.tsx +71 -10
- package/src/components/Header/index.scss +0 -5
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +28 -6
- package/src/components/MultiConfigTabs/MultiTabs.tsx +2 -0
- package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +4 -11
- package/src/components/Row.tsx +59 -13
- package/src/components/VisualizationRow.tsx +30 -22
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +0 -1
- package/src/components/Widget.tsx +23 -1
- package/src/data/initial-state.js +2 -1
- package/src/helpers/addValuesToDashboardFilters.ts +4 -2
- package/src/helpers/apiFilterHelpers.ts +55 -20
- package/src/helpers/changeFilterActive.ts +3 -0
- package/src/helpers/filterData.ts +1 -1
- package/src/helpers/getVizRowColumnLocator.ts +1 -0
- package/src/helpers/loadAPIFilters.ts +32 -10
- package/src/helpers/reloadURLHelpers.ts +9 -2
- package/src/helpers/shouldLoadAllFilters.ts +30 -0
- package/src/helpers/tests/apiFilterHelpers.test.ts +85 -4
- package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +10 -4
- package/src/helpers/tests/reloadURLHelpers.test.ts +11 -5
- package/src/helpers/tests/shouldLoadAllFilters.test.ts +117 -0
- package/src/scss/editor-panel.scss +0 -3
- package/src/scss/grid.scss +22 -23
- package/src/scss/main.scss +0 -27
- package/src/store/dashboard.reducer.ts +9 -2
- package/src/store/errorMessage/errorMessage.actions.ts +7 -0
- package/src/store/errorMessage/errorMessage.reducer.ts +24 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/dashboard",
|
|
3
|
-
"version": "4.24.
|
|
3
|
+
"version": "4.24.12-2",
|
|
4
4
|
"description": "React component for combining multiple visualizations into a single dashboard",
|
|
5
5
|
"moduleName": "CdcDashboard",
|
|
6
6
|
"main": "dist/cdcdashboard",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
},
|
|
28
28
|
"license": "Apache-2.0",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@cdc/chart": "^4.24.
|
|
31
|
-
"@cdc/core": "^4.24.
|
|
32
|
-
"@cdc/data-bite": "^4.24.
|
|
33
|
-
"@cdc/filtered-text": "^4.24.
|
|
34
|
-
"@cdc/map": "^4.24.
|
|
35
|
-
"@cdc/markup-include": "^4.24.
|
|
36
|
-
"@cdc/waffle-chart": "^4.24.
|
|
30
|
+
"@cdc/chart": "^4.24.12-2",
|
|
31
|
+
"@cdc/core": "^4.24.12-2",
|
|
32
|
+
"@cdc/data-bite": "^4.24.12-2",
|
|
33
|
+
"@cdc/filtered-text": "^4.24.12-2",
|
|
34
|
+
"@cdc/map": "^4.24.12-2",
|
|
35
|
+
"@cdc/markup-include": "^4.24.12-2",
|
|
36
|
+
"@cdc/waffle-chart": "^4.24.12-2",
|
|
37
37
|
"html-react-parser": "^3.0.8",
|
|
38
38
|
"js-base64": "^2.5.2",
|
|
39
39
|
"papaparse": "^5.3.0",
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"react": "^18.2.0",
|
|
50
50
|
"react-dom": "^18.2.0"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "a60edf1148396309eb473ac9f65426ee40797ddf"
|
|
53
53
|
}
|
package/src/CdcDashboard.tsx
CHANGED
|
@@ -3,7 +3,6 @@ import CdcDashboard from './CdcDashboardComponent'
|
|
|
3
3
|
import { MultiDashboardConfig } from './types/MultiDashboard'
|
|
4
4
|
import Loading from '@cdc/core/components/Loading'
|
|
5
5
|
import defaults from './data/initial-state'
|
|
6
|
-
import { processData } from './helpers/processData'
|
|
7
6
|
import { getVizKeys } from './helpers/getVizKeys'
|
|
8
7
|
import { processDataLegacy } from './helpers/processDataLegacy'
|
|
9
8
|
import { WCMSProps } from '@cdc/core/types/WCMSProps'
|
|
@@ -13,40 +12,47 @@ import { InitialState } from './types/InitialState'
|
|
|
13
12
|
import { DashboardConfig } from './types/DashboardConfig'
|
|
14
13
|
import { coveUpdateWorker } from '@cdc/core/helpers/coveUpdateWorker'
|
|
15
14
|
import _ from 'lodash'
|
|
16
|
-
import {
|
|
15
|
+
import { getQueryParams } from '@cdc/core/helpers/queryStringUtils'
|
|
17
16
|
|
|
18
17
|
type MultiDashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
19
18
|
configUrl?: string
|
|
20
19
|
config?: MultiDashboardConfig
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({
|
|
22
|
+
const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({
|
|
23
|
+
configUrl,
|
|
24
|
+
config: editorConfig,
|
|
25
|
+
isEditor,
|
|
26
|
+
isDebug
|
|
27
|
+
}) => {
|
|
24
28
|
const [initial, setInitial] = useState<InitialState>(undefined)
|
|
25
29
|
|
|
26
|
-
const getSelectedConfig = (config: MultiDashboardConfig
|
|
30
|
+
const getSelectedConfig = (config: MultiDashboardConfig): number | null => {
|
|
27
31
|
if (!config.multiDashboards) return null
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
})
|
|
33
|
-
if (foundConfig > -1) return foundConfig
|
|
32
|
+
// if query parameter, select based on query parameter
|
|
33
|
+
const selectedConfig = getQueryParams()['cove-tab']
|
|
34
|
+
if (selectedConfig !== undefined && Number(selectedConfig) < config.multiDashboards.length) {
|
|
35
|
+
return Number(selectedConfig)
|
|
34
36
|
}
|
|
35
37
|
// else select the first available
|
|
36
38
|
return 0
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
const formatInitialState = (
|
|
41
|
+
const formatInitialState = (
|
|
42
|
+
newConfig: MultiDashboardConfig | DashboardConfig,
|
|
43
|
+
datasets: Record<string, Object[]>
|
|
44
|
+
) => {
|
|
40
45
|
const [config, filteredData] = getUpdateConfig(initialState)(newConfig, datasets)
|
|
41
46
|
const versionedConfig = coveUpdateWorker(config)
|
|
42
47
|
return { ...initialState, config: versionedConfig, filteredData, data: datasets }
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
const loadConfig = async (
|
|
50
|
+
const loadConfig = async () => {
|
|
46
51
|
const _config: MultiDashboardConfig = editorConfig || (await (await fetch(configUrl)).json())
|
|
47
|
-
const selected = getSelectedConfig(_config
|
|
52
|
+
const selected = getSelectedConfig(_config)
|
|
48
53
|
|
|
49
|
-
const { newConfig, datasets } =
|
|
54
|
+
const { newConfig, datasets } =
|
|
55
|
+
selected !== null ? await loadMultiDashboard(_config, selected) : await loadSingleDashboard(_config)
|
|
50
56
|
setInitial(formatInitialState(newConfig, datasets))
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -54,18 +60,13 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, confi
|
|
|
54
60
|
loadConfig()
|
|
55
61
|
}, [])
|
|
56
62
|
|
|
57
|
-
const
|
|
63
|
+
const prepareDatasets = (initialConfig: DashboardConfig | MultiDashboardConfig) => {
|
|
58
64
|
let newConfig = { ...initialConfig }
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const data = await processData(initialConfig.datasets[key], !hasApplyBehavior)
|
|
65
|
-
datasets[key] = data || []
|
|
66
|
-
})
|
|
67
|
-
)
|
|
68
|
-
|
|
65
|
+
const datasets: Record<string, Object[]> = Object.keys(initialConfig.datasets).reduce((acc, key) => {
|
|
66
|
+
const dataset = initialConfig.datasets[key]
|
|
67
|
+
acc[key] = dataset.formattedData || dataset.data
|
|
68
|
+
return acc
|
|
69
|
+
}, {})
|
|
69
70
|
getVizKeys(newConfig).forEach(vizKey => {
|
|
70
71
|
const formattedData = datasets[newConfig.visualizations[vizKey].dataKey]
|
|
71
72
|
if (formattedData) {
|
|
@@ -84,7 +85,7 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, confi
|
|
|
84
85
|
let newConfig = { ...defaults, ...config } as DashboardConfig
|
|
85
86
|
|
|
86
87
|
if (config.datasets) {
|
|
87
|
-
return
|
|
88
|
+
return prepareDatasets(newConfig)
|
|
88
89
|
} else {
|
|
89
90
|
const dataKey = newConfig.dataFileName || 'backwards-compatibility'
|
|
90
91
|
const data = await processDataLegacy(config)
|
|
@@ -101,7 +102,14 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, confi
|
|
|
101
102
|
newConfig.visualizations[vizKey] = { ...newConfig.visualizations[vizKey], ...newData }
|
|
102
103
|
})
|
|
103
104
|
|
|
104
|
-
const blankFields = {
|
|
105
|
+
const blankFields = {
|
|
106
|
+
data: [],
|
|
107
|
+
dataUrl: '',
|
|
108
|
+
dataFileName: '',
|
|
109
|
+
dataFileSourceType: '',
|
|
110
|
+
dataDescription: {},
|
|
111
|
+
formattedData: []
|
|
112
|
+
}
|
|
105
113
|
newConfig = { ...newConfig, ...blankFields }
|
|
106
114
|
|
|
107
115
|
if (newConfig.dashboard.filters) {
|
|
@@ -122,8 +130,14 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, confi
|
|
|
122
130
|
|
|
123
131
|
const loadMultiDashboard = async (multiConfig: MultiDashboardConfig, selectedConfig: number) => {
|
|
124
132
|
const selectedDashboard = multiConfig.multiDashboards[selectedConfig]
|
|
125
|
-
|
|
126
|
-
|
|
133
|
+
const newConfig = {
|
|
134
|
+
...defaults,
|
|
135
|
+
...multiConfig,
|
|
136
|
+
...selectedDashboard,
|
|
137
|
+
multiDashboards: multiConfig.multiDashboards,
|
|
138
|
+
activeDashboard: selectedConfig
|
|
139
|
+
} as MultiDashboardConfig
|
|
140
|
+
return prepareDatasets(newConfig)
|
|
127
141
|
}
|
|
128
142
|
|
|
129
143
|
if (!initial) return <Loading />
|
|
@@ -35,6 +35,7 @@ import './scss/main.scss'
|
|
|
35
35
|
|
|
36
36
|
import VisualizationsPanel from './components/VisualizationsPanel'
|
|
37
37
|
import dashboardReducer from './store/dashboard.reducer'
|
|
38
|
+
import errorMessagesReducer from './store/errorMessage/errorMessage.reducer'
|
|
38
39
|
import { filterData } from './helpers/filterData'
|
|
39
40
|
import { getVizKeys } from './helpers/getVizKeys'
|
|
40
41
|
import Title from '@cdc/core/components/ui/Title'
|
|
@@ -42,13 +43,12 @@ import { type TableConfig } from '@cdc/core/components/DataTable/types/TableConf
|
|
|
42
43
|
|
|
43
44
|
// types
|
|
44
45
|
import { type SharedFilter } from './types/SharedFilter'
|
|
45
|
-
import { type APIFilter } from './types/APIFilter'
|
|
46
46
|
import { type WCMSProps } from '@cdc/core/types/WCMSProps'
|
|
47
47
|
import { type InitialState } from './types/InitialState'
|
|
48
48
|
import MultiTabs from './components/MultiConfigTabs'
|
|
49
49
|
import _ from 'lodash'
|
|
50
50
|
import EditorContext from '../../editor/src/ConfigContext'
|
|
51
|
-
import { APIFilterDropdowns
|
|
51
|
+
import { APIFilterDropdowns } from './components/DashboardFilters'
|
|
52
52
|
import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
|
|
53
53
|
import { ViewPort } from '@cdc/core/types/ViewPort'
|
|
54
54
|
import VisualizationRow from './components/VisualizationRow'
|
|
@@ -62,9 +62,10 @@ import { addValuesToDashboardFilters } from './helpers/addValuesToDashboardFilte
|
|
|
62
62
|
import { DashboardFilters } from './types/DashboardFilters'
|
|
63
63
|
import DashboardSharedFilters from './components/DashboardFilters'
|
|
64
64
|
import ExpandCollapseButtons from './components/ExpandCollapseButtons'
|
|
65
|
-
import { hasDashboardApplyBehavior } from './helpers/hasDashboardApplyBehavior'
|
|
66
65
|
import { loadAPIFiltersFactory } from './helpers/loadAPIFilters'
|
|
67
66
|
import Loader from '@cdc/core/components/Loader'
|
|
67
|
+
import Alert from '@cdc/core/components/Alert'
|
|
68
|
+
import { shouldLoadAllFilters } from './helpers/shouldLoadAllFilters'
|
|
68
69
|
|
|
69
70
|
type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
70
71
|
initialState: InitialState
|
|
@@ -72,6 +73,7 @@ type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
|
72
73
|
|
|
73
74
|
export default function CdcDashboard({ initialState, isEditor = false, isDebug = false }: DashboardProps) {
|
|
74
75
|
const [state, dispatch] = useReducer(dashboardReducer, initialState)
|
|
76
|
+
const [errorMessages, dispatchErrorMessages] = useReducer(errorMessagesReducer, [])
|
|
75
77
|
const editorContext = useContext(EditorContext)
|
|
76
78
|
const [apiFilterDropdowns, setAPIFilterDropdowns] = useState<APIFilterDropdowns>({})
|
|
77
79
|
const [currentViewport, setCurrentViewport] = useState<ViewPort>('lg')
|
|
@@ -82,7 +84,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
82
84
|
const isPreview = state.tabSelected === 'Dashboard Preview'
|
|
83
85
|
|
|
84
86
|
const inNoDataState = useMemo(() => {
|
|
85
|
-
const vals =
|
|
87
|
+
const vals = reloadURLHelpers.getDatasetKeys(state.config).map(key => state.data[key])
|
|
86
88
|
if (!vals.length) return true
|
|
87
89
|
return vals.some(val => val === undefined)
|
|
88
90
|
}, [state.data])
|
|
@@ -97,7 +99,12 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
97
99
|
.reduce((acc, viz: DashboardFilters) => (viz.autoLoad ? [...acc, ...viz.sharedFilterIndexes] : acc), [])
|
|
98
100
|
}, [state.config.visualizations])
|
|
99
101
|
|
|
100
|
-
const loadAPIFilters = loadAPIFiltersFactory(
|
|
102
|
+
const loadAPIFilters = loadAPIFiltersFactory(
|
|
103
|
+
dispatch,
|
|
104
|
+
dispatchErrorMessages,
|
|
105
|
+
setAPIFilterDropdowns,
|
|
106
|
+
autoLoadFilterIndexes
|
|
107
|
+
)
|
|
101
108
|
|
|
102
109
|
const reloadURLData = async (newFilters?: SharedFilter[]) => {
|
|
103
110
|
const config = _.cloneDeep(state.config)
|
|
@@ -121,7 +128,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
121
128
|
filters.forEach(filter => {
|
|
122
129
|
if (
|
|
123
130
|
filter.type === 'urlfilter' &&
|
|
124
|
-
reloadURLHelpers.filterUsedByDataUrl(filter, datasetKey, config.visualizations)
|
|
131
|
+
reloadURLHelpers.filterUsedByDataUrl(filter, datasetKey, config.visualizations, config.rows)
|
|
125
132
|
) {
|
|
126
133
|
if (filter.filterBy === 'File Name') {
|
|
127
134
|
newFileName = reloadURLHelpers.getNewFileName(newFileName, filter, datasetKey)
|
|
@@ -138,10 +145,9 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
138
145
|
if (!!filter.setByQueryParameter) {
|
|
139
146
|
const windowQueryParams = Object.fromEntries(new URLSearchParams(window.location.search))
|
|
140
147
|
const filterValue = windowQueryParams[filter.setByQueryParameter]
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
updatedQSParams[filter.setByQueryParameter] = filterValue
|
|
148
|
+
const queryParam = filter.apiFilter?.valueSelector || filter.setByQueryParameter
|
|
149
|
+
if (filterValue) {
|
|
150
|
+
updatedQSParams[queryParam] = filterValue
|
|
145
151
|
}
|
|
146
152
|
}
|
|
147
153
|
|
|
@@ -154,7 +160,9 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
154
160
|
}
|
|
155
161
|
})
|
|
156
162
|
|
|
157
|
-
|
|
163
|
+
const dataNeedsUpdate = reloadURLHelpers.isUpdateNeeded(filters, currentQSParams, updatedQSParams)
|
|
164
|
+
|
|
165
|
+
if (!!newFilters || dataNeedsUpdate) {
|
|
158
166
|
dataWasFetched = true
|
|
159
167
|
const dataUrlFinal = reloadURLHelpers.getDataURL(
|
|
160
168
|
{ ...currentQSParams, ...updatedQSParams },
|
|
@@ -163,34 +171,60 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
163
171
|
)
|
|
164
172
|
|
|
165
173
|
setAPILoading(true)
|
|
166
|
-
await fetchRemoteData(dataUrlFinal)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
await fetchRemoteData(dataUrlFinal)
|
|
175
|
+
.then(responseData => {
|
|
176
|
+
let data: any[] = responseData
|
|
177
|
+
if (responseData && dataset.dataDescription) {
|
|
178
|
+
try {
|
|
179
|
+
data = transform.autoStandardize(data)
|
|
180
|
+
data = transform.developerStandardize(data, dataset.dataDescription)
|
|
181
|
+
} catch (e) {
|
|
182
|
+
//Data not able to be standardized, leave as is
|
|
183
|
+
console.error('Error standardizing data:', e)
|
|
184
|
+
}
|
|
174
185
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
186
|
+
newDatasets[datasetKey].data = data
|
|
187
|
+
newDatasets[datasetKey].runtimeDataUrl = dataUrlFinal
|
|
188
|
+
newData[datasetKey] = data
|
|
189
|
+
})
|
|
190
|
+
.catch(e => {
|
|
191
|
+
console.error(e)
|
|
192
|
+
dispatchErrorMessages({
|
|
193
|
+
type: 'ADD_ERROR_MESSAGE',
|
|
194
|
+
payload: 'There was a problem returning data. Please try again.'
|
|
195
|
+
})
|
|
196
|
+
newDatasets[datasetKey].data = []
|
|
197
|
+
newDatasets[datasetKey].runtimeDataUrl = dataUrlFinal
|
|
198
|
+
newData[datasetKey] = []
|
|
199
|
+
})
|
|
180
200
|
}
|
|
181
201
|
}
|
|
182
202
|
}
|
|
183
203
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
204
|
+
const datasetsWithFiles = _.pickBy(newDatasets, dataset => !dataset.dataUrl)
|
|
205
|
+
|
|
206
|
+
if (dataWasFetched || Object.keys(datasetsWithFiles).length) {
|
|
207
|
+
const dataFiles = Object.keys(datasetsWithFiles).reduce((acc, key) => {
|
|
208
|
+
acc[key] = datasetsWithFiles[key].data
|
|
209
|
+
return acc
|
|
210
|
+
}, {})
|
|
211
|
+
const _newData = { ...newData, ...dataFiles }
|
|
212
|
+
dispatch({ type: 'SET_DATA', payload: _newData })
|
|
213
|
+
const dataFilterIndexes = config.dashboard.sharedFilters.reduce((acc, filter, index) => {
|
|
214
|
+
if (filter.type === 'datafilter') acc.push(index)
|
|
215
|
+
return acc
|
|
216
|
+
}, [])
|
|
217
|
+
const appliedFilterIndexes = Object.values(config.visualizations)
|
|
218
|
+
.filter(viz => viz.type === 'dashboardFilters')
|
|
219
|
+
.flatMap(viz => viz.sharedFilterIndexes)
|
|
220
|
+
.filter(index => !dataFilterIndexes.includes(index))
|
|
221
|
+
const filtersWithNewValues = addValuesToDashboardFilters(filters, _newData, appliedFilterIndexes)
|
|
222
|
+
|
|
223
|
+
const dashboardConfig = { ...config.dashboard, sharedFilters: filtersWithNewValues }
|
|
190
224
|
const filteredData = getFilteredData(
|
|
191
225
|
{ ...state, config: { ...state.config, dashboard: dashboardConfig } },
|
|
192
226
|
{},
|
|
193
|
-
|
|
227
|
+
_newData
|
|
194
228
|
)
|
|
195
229
|
dispatch({ type: 'SET_FILTERED_DATA', payload: filteredData })
|
|
196
230
|
const visualizations = reloadURLHelpers.getVisualizationsWithFormattedData(config.visualizations, newData)
|
|
@@ -230,15 +264,16 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
230
264
|
}
|
|
231
265
|
|
|
232
266
|
useEffect(() => {
|
|
233
|
-
if (isEditor && !isPreview) return
|
|
234
267
|
const { config } = state
|
|
235
|
-
|
|
236
|
-
reloadURLData()
|
|
237
|
-
}
|
|
238
|
-
|
|
268
|
+
const loadAllFilters = shouldLoadAllFilters(config, isEditor && !isPreview)
|
|
239
269
|
const sharedFiltersWithValues = addValuesToDashboardFilters(config.dashboard.sharedFilters, state.data)
|
|
240
|
-
|
|
241
|
-
|
|
270
|
+
|
|
271
|
+
loadAPIFilters(sharedFiltersWithValues, apiFilterDropdowns, loadAllFilters).then(newFilters => {
|
|
272
|
+
const allValuesSelected = newFilters.every(filter => {
|
|
273
|
+
return filter.type === 'datafilter' || filter.active
|
|
274
|
+
})
|
|
275
|
+
if (allValuesSelected) reloadURLData(newFilters)
|
|
276
|
+
})
|
|
242
277
|
}, [isEditor, isPreview, state.config?.activeDashboard])
|
|
243
278
|
|
|
244
279
|
const updateChildConfig = (visualizationKey, newConfig) => {
|
|
@@ -262,13 +297,6 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
262
297
|
}
|
|
263
298
|
}
|
|
264
299
|
|
|
265
|
-
const updateFilteredData = (sharedFilters = undefined) => {
|
|
266
|
-
const clonedState = _.cloneDeep(state)
|
|
267
|
-
if (sharedFilters) clonedState.config.dashboard.sharedFilters = sharedFilters
|
|
268
|
-
const newFilteredData = getFilteredData(clonedState)
|
|
269
|
-
dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
|
|
270
|
-
}
|
|
271
|
-
|
|
272
300
|
const resizeObserver = new ResizeObserver(entries => {
|
|
273
301
|
for (let entry of entries) {
|
|
274
302
|
let newViewport = getViewport(entry.contentRect.width)
|
|
@@ -421,8 +449,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
421
449
|
)
|
|
422
450
|
break
|
|
423
451
|
case 'dashboardFilters':
|
|
424
|
-
|
|
425
|
-
body = !hideFilter ? (
|
|
452
|
+
body = (
|
|
426
453
|
<>
|
|
427
454
|
<Header visualizationKey={visualizationKey} subEditor='Filter Dropdowns' />
|
|
428
455
|
<DashboardSharedFilters
|
|
@@ -432,8 +459,6 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
432
459
|
setConfig={_updateConfig}
|
|
433
460
|
/>
|
|
434
461
|
</>
|
|
435
|
-
) : (
|
|
436
|
-
<></>
|
|
437
462
|
)
|
|
438
463
|
break
|
|
439
464
|
case 'table':
|
|
@@ -453,11 +478,12 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
453
478
|
body = (
|
|
454
479
|
<>
|
|
455
480
|
<Header visualizationKey={visualizationKey} subEditor='Footnotes' />
|
|
481
|
+
{/* Datasets are passed in just for reference and need to be removed */}
|
|
456
482
|
<FootnotesStandAlone
|
|
457
483
|
visualizationKey={visualizationKey}
|
|
458
484
|
config={{ ...visualizationConfig, datasets: state.config.datasets }}
|
|
459
485
|
isEditor={true}
|
|
460
|
-
updateConfig={_updateConfig}
|
|
486
|
+
updateConfig={conf => _updateConfig(_.omit(conf, 'datasets'))}
|
|
461
487
|
/>
|
|
462
488
|
</>
|
|
463
489
|
)
|
|
@@ -492,6 +518,14 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
492
518
|
{isEditor && <Header />}
|
|
493
519
|
{apiLoading && <Loader fullScreen={true} />}
|
|
494
520
|
<MultiTabs isEditor={isEditor && !isPreview} />
|
|
521
|
+
{errorMessages.map((message, index) => (
|
|
522
|
+
<Alert
|
|
523
|
+
type='danger'
|
|
524
|
+
onDismiss={() => dispatchErrorMessages({ type: 'DISMISS_ERROR_MESSAGE', payload: index })}
|
|
525
|
+
message={message}
|
|
526
|
+
autoDismiss={true}
|
|
527
|
+
/>
|
|
528
|
+
))}
|
|
495
529
|
<Layout.Responsive isEditor={isEditor}>
|
|
496
530
|
<div className={`cdc-dashboard-inner-container${isEditor ? ' is-editor' : ''}`}>
|
|
497
531
|
<Title
|
|
@@ -518,7 +552,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
518
552
|
return (
|
|
519
553
|
<>
|
|
520
554
|
{/* Expand/Collapse All */}
|
|
521
|
-
{row.expandCollapseAllButtons === true && (
|
|
555
|
+
{!inNoDataState && row.expandCollapseAllButtons === true && (
|
|
522
556
|
<ExpandCollapseButtons setAllExpanded={setAllExpanded} />
|
|
523
557
|
)}
|
|
524
558
|
{Object.keys(dataGroups).map(groupName => {
|
|
@@ -535,6 +569,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
535
569
|
updateChildConfig={updateChildConfig}
|
|
536
570
|
apiFilterDropdowns={apiFilterDropdowns}
|
|
537
571
|
currentViewport={currentViewport}
|
|
572
|
+
inNoDataState={inNoDataState}
|
|
538
573
|
/>
|
|
539
574
|
)
|
|
540
575
|
})}
|
|
@@ -552,11 +587,14 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
552
587
|
updateChildConfig={updateChildConfig}
|
|
553
588
|
apiFilterDropdowns={apiFilterDropdowns}
|
|
554
589
|
currentViewport={currentViewport}
|
|
590
|
+
inNoDataState={inNoDataState}
|
|
555
591
|
/>
|
|
556
592
|
)
|
|
557
593
|
}
|
|
558
594
|
})}
|
|
559
595
|
|
|
596
|
+
{inNoDataState ? <div className='mt-5'>Please complete your selection to continue.</div> : <></>}
|
|
597
|
+
|
|
560
598
|
{/* Image or PDF Inserts */}
|
|
561
599
|
<section className='download-buttons'>
|
|
562
600
|
{config.table?.downloadImageButton && (
|
|
@@ -667,7 +705,8 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
667
705
|
isDebug,
|
|
668
706
|
loadAPIFilters,
|
|
669
707
|
setAPIFilterDropdowns,
|
|
670
|
-
reloadURLData
|
|
708
|
+
reloadURLData,
|
|
709
|
+
setAPILoading
|
|
671
710
|
}}
|
|
672
711
|
>
|
|
673
712
|
<DashboardDispatchContext.Provider value={dispatch}>
|
package/src/DashboardContext.tsx
CHANGED
|
@@ -17,6 +17,7 @@ type ConfigCTX = DashboardState & {
|
|
|
17
17
|
recursiveLimit?: number
|
|
18
18
|
) => Promise<SharedFilter[]>
|
|
19
19
|
setAPIFilterDropdowns: (dropdowns: APIFilterDropdowns) => void
|
|
20
|
+
setAPILoading: (loading: boolean) => void
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const firstTab: Tab = 'Dashboard Description'
|
|
@@ -33,6 +34,7 @@ const initialContext: ConfigCTX = {
|
|
|
33
34
|
outerContainerRef: () => {},
|
|
34
35
|
setParentConfig: () => {},
|
|
35
36
|
setAPIFilterDropdowns: () => {},
|
|
37
|
+
setAPILoading: () => {},
|
|
36
38
|
reloadURLData: () => {},
|
|
37
39
|
loadAPIFilters: () => Promise.resolve([]),
|
|
38
40
|
isDebug: false,
|
|
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'
|
|
|
2
2
|
import { faker } from '@faker-js/faker'
|
|
3
3
|
import APIFiltersMapData from './_mock/api-filter-map.json'
|
|
4
4
|
import APIFiltersChartData from './_mock/api-filter-chart.json'
|
|
5
|
+
import APIFilterErrorConfig from './_mock/api-filter-error.json'
|
|
5
6
|
import ExampleConfig_1 from './_mock/dashboard-gallery.json'
|
|
6
7
|
import ExampleConfig_2 from './_mock/dashboard-2.json'
|
|
7
8
|
import ExampleConfig_3 from './_mock/dashboard_no_filter.json'
|
|
@@ -66,6 +67,13 @@ export const Dashboard_Filters: Story = {
|
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
export const API_Filter_Error: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
config: APIFilterErrorConfig,
|
|
73
|
+
isEditor: false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
export const StandAloneTable: Story = {
|
|
70
78
|
args: {
|
|
71
79
|
config: StandaloneTable,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dashboard": {
|
|
3
|
+
"theme": "theme-blue",
|
|
4
|
+
"sharedFilters": [
|
|
5
|
+
{
|
|
6
|
+
"key": "New Dashboard Filter 1",
|
|
7
|
+
"showDropdown": true,
|
|
8
|
+
"type": "urlfilter",
|
|
9
|
+
"apiFilter": {
|
|
10
|
+
"apiEndpoint": "https://nccd-cove-public-api.apps.ecpaas-dev.cdc.gov/od-public?$datakey=brfss_prevalence_cove_explore_by_t",
|
|
11
|
+
"valueSelector": "",
|
|
12
|
+
"textSelector": ""
|
|
13
|
+
},
|
|
14
|
+
"tier": 1
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"rows": [{ "columns": [{ "width": 12, "widget": "dashboardFilters1730831317688" }, {}, {}] }],
|
|
19
|
+
"visualizations": {
|
|
20
|
+
"dashboardFilters1730831317688": {
|
|
21
|
+
"filters": [],
|
|
22
|
+
"filterBehavior": "Filter Change",
|
|
23
|
+
"newViz": true,
|
|
24
|
+
"openModal": true,
|
|
25
|
+
"uid": "dashboardFilters1730831317688",
|
|
26
|
+
"type": "dashboardFilters",
|
|
27
|
+
"sharedFilterIndexes": [0],
|
|
28
|
+
"visualizationType": "dashboardFilters"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"table": {
|
|
32
|
+
"label": "Data Table",
|
|
33
|
+
"show": true,
|
|
34
|
+
"showDownloadUrl": false,
|
|
35
|
+
"showDownloadLinkBelow": true,
|
|
36
|
+
"showVertical": true
|
|
37
|
+
},
|
|
38
|
+
"newViz": true,
|
|
39
|
+
"datasets": {
|
|
40
|
+
"https://nccd-cove-public-api.apps.ecpaas-dev.cdc.gov/od-public?$datakey=brfss_prevalence_cove_explore_by_topic": {
|
|
41
|
+
"dataFileSize": 35061,
|
|
42
|
+
"dataFileName": "https://nccd-cove-public-api.apps.ecpaas-dev.cdc.gov/od-public?$datakey=brfss_prevalence_cove_explore_by_topic",
|
|
43
|
+
"dataFileSourceType": "url",
|
|
44
|
+
"dataFileFormat": "JSON",
|
|
45
|
+
"preview": true,
|
|
46
|
+
"dataUrl": "https://nccd-cove-public-api.apps.ecpaas-dev.cdc.gov/od-public?$datakey=brfss_prevalence_cove_explore_by_topic"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"isResponsiveTicks": false,
|
|
50
|
+
"type": "dashboard",
|
|
51
|
+
"barThickness": "0.37",
|
|
52
|
+
"xAxis": { "type": "categorical", "size": 75, "maxTickRotation": 45, "labelOffset": 0 },
|
|
53
|
+
"runtime": {},
|
|
54
|
+
"version": "4.24.10"
|
|
55
|
+
}
|
|
@@ -30,7 +30,12 @@
|
|
|
30
30
|
"valueColumns": ["age", "color"]
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
|
-
"columns": {
|
|
33
|
+
"columns": {
|
|
34
|
+
"other": {
|
|
35
|
+
"name": "other",
|
|
36
|
+
"dataTable": false
|
|
37
|
+
}
|
|
38
|
+
},
|
|
34
39
|
"dataFormat": {},
|
|
35
40
|
"visualizationType": "table",
|
|
36
41
|
"dataDescription": {
|
|
@@ -59,10 +64,10 @@
|
|
|
59
64
|
"datasets": {
|
|
60
65
|
"valid-data-chart.csv": {
|
|
61
66
|
"data": [
|
|
62
|
-
{ "name": "John", "age": 25, "color": "blue", "city": "New York" },
|
|
63
|
-
{ "name": "Jane", "age": 27, "color": "red", "city": "New York" },
|
|
64
|
-
{ "name": "Jane", "age": 30, "color": "yellow", "city": "San Francisco" },
|
|
65
|
-
{ "name": "John", "age": 31, "color": "green", "city": "San Francisco" }
|
|
67
|
+
{ "name": "John", "age": 25, "color": "blue", "other": "no", "city": "New York" },
|
|
68
|
+
{ "name": "Jane", "age": 27, "color": "red", "other": "yes", "city": "New York" },
|
|
69
|
+
{ "name": "Jane", "age": 30, "color": "yellow", "other": "no", "city": "San Francisco" },
|
|
70
|
+
{ "name": "John", "age": 31, "color": "green", "other": "yes", "city": "San Francisco" }
|
|
66
71
|
],
|
|
67
72
|
"dataFileSize": 178,
|
|
68
73
|
"dataFileName": "valid-data-chart.csv",
|
|
@@ -8,10 +8,16 @@ type CollapsableVizRow = {
|
|
|
8
8
|
groupName: string
|
|
9
9
|
currentViewport: string
|
|
10
10
|
}
|
|
11
|
-
const CollapsibleVisualizationRow: React.FC<CollapsableVizRow> = ({
|
|
11
|
+
const CollapsibleVisualizationRow: React.FC<CollapsableVizRow> = ({
|
|
12
|
+
allExpanded,
|
|
13
|
+
fontSize,
|
|
14
|
+
groupName,
|
|
15
|
+
currentViewport,
|
|
16
|
+
children
|
|
17
|
+
}) => {
|
|
12
18
|
const [isExpanded, setIsExpanded] = useState(allExpanded)
|
|
13
19
|
const fontSizes = { small: 16, medium: 18, large: 20 }
|
|
14
|
-
const titleFontSize = ['
|
|
20
|
+
const titleFontSize = ['xs', 'xxs'].includes(currentViewport) ? '13px' : `${fontSizes[fontSize]}px`
|
|
15
21
|
|
|
16
22
|
useEffect(() => {
|
|
17
23
|
setIsExpanded(allExpanded)
|