@cdc/dashboard 4.24.2 → 4.24.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/dist/cdcdashboard.js +98192 -85200
- package/examples/sankey.json +5218 -0
- package/index.html +3 -2
- package/package.json +11 -10
- package/src/CdcDashboard.tsx +124 -124
- package/src/CdcDashboardComponent.tsx +173 -186
- package/src/DashboardContext.tsx +4 -1
- package/src/_stories/Dashboard.stories.tsx +27 -5
- package/src/_stories/_mock/pivot-filter.json +163 -0
- package/src/_stories/_mock/standalone-table.json +122 -0
- package/src/_stories/_mock/toggle-example.json +4035 -0
- package/src/components/EditorWrapper/EditorWrapper.tsx +52 -0
- package/src/components/EditorWrapper/editor-wrapper.style.css +13 -0
- package/src/components/Filters.tsx +88 -0
- package/src/components/Header/FilterModal.tsx +480 -0
- package/src/components/Header/Header.tsx +25 -465
- package/src/components/Row.tsx +28 -17
- package/src/components/Toggle/Toggle.tsx +37 -0
- package/src/components/Toggle/index.tsx +1 -0
- package/src/components/Toggle/toggle-style.css +34 -0
- package/src/components/VisualizationsPanel.tsx +13 -3
- package/src/components/Widget.tsx +14 -30
- package/src/helpers/filterData.ts +72 -49
- package/src/helpers/generateValuesForFilter.ts +2 -12
- package/src/helpers/getApiFilterKey.ts +5 -0
- package/src/helpers/getUpdateConfig.ts +24 -22
- package/src/helpers/iconHash.tsx +34 -0
- package/src/helpers/tests/filterData.test.ts +149 -0
- package/src/images/icon-toggle.svg +1 -0
- package/src/scss/grid.scss +1 -1
- package/src/scss/main.scss +6 -0
- package/src/store/dashboard.actions.ts +19 -2
- package/src/store/dashboard.reducer.ts +9 -1
- package/src/types/ConfigRow.ts +2 -0
- package/src/types/DataSet.ts +7 -7
- package/src/types/InitialState.ts +2 -1
- package/src/types/SharedFilter.ts +5 -2
- package/src/types/Tab.ts +1 -0
|
@@ -18,6 +18,7 @@ import OverlayFrame from '@cdc/core/components/ui/OverlayFrame'
|
|
|
18
18
|
import Loading from '@cdc/core/components/Loading'
|
|
19
19
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
20
20
|
import getViewport from '@cdc/core/helpers/getViewport'
|
|
21
|
+
import { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
|
|
21
22
|
|
|
22
23
|
import CdcMap from '@cdc/map'
|
|
23
24
|
import CdcChart from '@cdc/chart'
|
|
@@ -53,13 +54,14 @@ import { type InitialState } from './types/InitialState'
|
|
|
53
54
|
import MultiTabs from './components/MultiConfigTabs'
|
|
54
55
|
import _ from 'lodash'
|
|
55
56
|
import EditorContext from '../../editor/src/ConfigContext'
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
import { getApiFilterKey } from './helpers/getApiFilterKey'
|
|
58
|
+
import Filters, { APIFilterDropdowns, DropdownOptions } from './components/Filters'
|
|
59
|
+
import EditorWrapper from './components/EditorWrapper/EditorWrapper'
|
|
60
|
+
import DataTableEditorPanel from '@cdc/core/components/DataTable/components/DataTableEditorPanel'
|
|
61
|
+
import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
|
|
62
|
+
import { ViewPort } from '@cdc/core/types/ViewPort'
|
|
63
|
+
import Toggle from './components/Toggle'
|
|
64
|
+
import { Dashboard } from './types/Dashboard'
|
|
63
65
|
|
|
64
66
|
type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
65
67
|
initialState: InitialState
|
|
@@ -67,12 +69,12 @@ type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
|
67
69
|
|
|
68
70
|
export default function CdcDashboard({ initialState, isEditor = false, isDebug = false }: DashboardProps) {
|
|
69
71
|
const [state, dispatch] = useReducer(dashboardReducer, initialState)
|
|
70
|
-
console.log('state', state)
|
|
71
72
|
const editorContext = useContext(EditorContext)
|
|
72
73
|
const [apiFilterDropdowns, setAPIFilterDropdowns] = useState<APIFilterDropdowns>({})
|
|
73
|
-
const [currentViewport, setCurrentViewport] = useState('lg')
|
|
74
|
+
const [currentViewport, setCurrentViewport] = useState<ViewPort>('lg')
|
|
74
75
|
const [imageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`)
|
|
75
76
|
|
|
77
|
+
const isPreview = state.tabSelected === 'Dashboard Preview'
|
|
76
78
|
const replacements = {
|
|
77
79
|
'Remove Spaces': '',
|
|
78
80
|
'Keep Spaces': ' ',
|
|
@@ -96,10 +98,6 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
96
98
|
|
|
97
99
|
const transform = new DataTransform()
|
|
98
100
|
|
|
99
|
-
const getApiFilterKey = ({ apiEndpoint, heirarchyLookup }: APIFilter) => {
|
|
100
|
-
return apiEndpoint + (heirarchyLookup || '')
|
|
101
|
-
}
|
|
102
|
-
|
|
103
101
|
const setAutoLoadDefaultValue = (sharedFilterIndex: number, filterDropdowns: DropdownOptions) => {
|
|
104
102
|
const autoLoadViz = getAutoLoadVisualization()
|
|
105
103
|
if (!autoLoadViz) return // no autoLoading happening
|
|
@@ -135,15 +133,15 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
135
133
|
if (!_parents.length) return null
|
|
136
134
|
return _parents.map(({ queryParameter, queuedActive }) => ({ key: queryParameter || '', value: queuedActive || '' }))
|
|
137
135
|
}
|
|
138
|
-
const getFilterValues = (
|
|
136
|
+
const getFilterValues = (data: Object | Array<Object>, apiFilter: APIFilter): DropdownOptions => {
|
|
139
137
|
const { textSelector, valueSelector, heirarchyLookup } = apiFilter
|
|
140
138
|
if (heirarchyLookup) {
|
|
141
139
|
const heirarchy = heirarchyLookup!.split('.')
|
|
142
140
|
const selector = heirarchy.shift() // pop first element
|
|
143
|
-
return getFilterValues(selector ?
|
|
141
|
+
return getFilterValues(selector ? data[selector] : data, { ...apiFilter, heirarchyLookup: heirarchy.join('.') })
|
|
144
142
|
}
|
|
145
|
-
if (!Array.isArray(
|
|
146
|
-
return
|
|
143
|
+
if (!Array.isArray(data)) throw new Error('the filter data has requires a heirarchy path to access the filter values, This should be in the format key.subkey.subsubkey')
|
|
144
|
+
return data.map(v => ({ text: v[textSelector], value: v[valueSelector] }))
|
|
147
145
|
}
|
|
148
146
|
state.config.dashboard.sharedFilters.forEach(async (filter, index) => {
|
|
149
147
|
if (!filter.apiFilter) return
|
|
@@ -167,55 +165,68 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
167
165
|
}
|
|
168
166
|
}
|
|
169
167
|
|
|
168
|
+
const getApplicableFilters = (dashboard: Dashboard, key: string): false | SharedFilter[] => {
|
|
169
|
+
const c = dashboard.sharedFilters?.filter(sharedFilter => sharedFilter.usedBy && sharedFilter.usedBy.indexOf(key) !== -1)
|
|
170
|
+
return c?.length > 0 ? c : false
|
|
171
|
+
}
|
|
172
|
+
|
|
170
173
|
const reloadURLData = async () => {
|
|
171
174
|
const { config } = state
|
|
172
|
-
if (config.datasets)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
175
|
+
if (!config.datasets) return
|
|
176
|
+
let newData = { ...state.data }
|
|
177
|
+
let newDatasets = { ...config.datasets }
|
|
178
|
+
let datasetsNeedsUpdate = false
|
|
179
|
+
let datasetKeys = Object.keys(config.datasets)
|
|
180
|
+
let newFileName = ''
|
|
181
|
+
|
|
182
|
+
for (let i = 0; i < datasetKeys.length; i++) {
|
|
183
|
+
const datasetKey = datasetKeys[i]
|
|
184
|
+
const dataset = config.datasets[datasetKey]
|
|
185
|
+
const filters = config.dashboard?.sharedFilters
|
|
186
|
+
if (dataset.dataUrl && filters) {
|
|
187
|
+
const dataUrl = new URL(dataset.runtimeDataUrl || dataset.dataUrl, window.location.origin)
|
|
188
|
+
let currentQSParams = Object.fromEntries(new URLSearchParams(dataUrl.search))
|
|
189
|
+
let updatedQSParams = {}
|
|
190
|
+
let isUpdateNeeded = false
|
|
191
|
+
|
|
192
|
+
filters.forEach(filter => {
|
|
193
|
+
// filter.active is always a string when filter.type is 'urlfilter'
|
|
194
|
+
if (filter.type === 'urlfilter' && !Array.isArray(filter.active)) {
|
|
189
195
|
if (filter.filterBy === 'File Name') {
|
|
190
|
-
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
isUpdateNeeded = true
|
|
197
|
+
if (filter.datasetKey === datasetKey) {
|
|
198
|
+
if (filter.fileName) {
|
|
199
|
+
// if a file name is found, ie, state_${query}, use that, ie. state_activeFilter.json
|
|
200
|
+
newFileName = capitalizeSplitAndJoin.call(String(filter.fileName), ' ', replacements[filter.whitespaceReplacement ?? 'Keep Spaces'])
|
|
201
|
+
} else {
|
|
202
|
+
// if no file name is entered use the default active filter. ie. /activeFilter.json
|
|
203
|
+
newFileName = filter.active
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (newFileName?.includes('${query}')) {
|
|
195
208
|
newFileName = newFileName.replace('${query}', capitalizeSplitAndJoin.call(String(filter.active), ' ', replacements[filter.whitespaceReplacement ?? 'Keep Spaces']))
|
|
196
209
|
}
|
|
197
210
|
}
|
|
198
211
|
|
|
199
|
-
if (
|
|
212
|
+
if (!!filter.queryParameter) {
|
|
200
213
|
if (updatedQSParams[filter.queryParameter]) {
|
|
201
214
|
updatedQSParams[filter.queryParameter] = updatedQSParams[filter.queryParameter] + filter.active
|
|
202
215
|
} else {
|
|
203
216
|
updatedQSParams[filter.queryParameter] = filter.active
|
|
204
217
|
}
|
|
205
218
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
Object.keys(updatedQSParams).forEach(updatedParam => {
|
|
212
|
-
if (decodeURIComponent(updatedQSParams[updatedParam]) !== currentQSParams[updatedParam]) {
|
|
213
|
-
isUpdateNeeded = true
|
|
214
|
-
}
|
|
215
|
-
})
|
|
219
|
+
}
|
|
220
|
+
})
|
|
216
221
|
|
|
217
|
-
|
|
222
|
+
Object.keys(updatedQSParams).forEach(updatedParam => {
|
|
223
|
+
if (decodeURIComponent(updatedQSParams[updatedParam]) !== currentQSParams[updatedParam]) {
|
|
224
|
+
isUpdateNeeded = true
|
|
225
|
+
}
|
|
226
|
+
})
|
|
218
227
|
|
|
228
|
+
if (isUpdateNeeded) {
|
|
229
|
+
datasetsNeedsUpdate = true
|
|
219
230
|
Object.keys(currentQSParams).forEach(currentParam => {
|
|
220
231
|
if (!updatedQSParams[currentParam]) {
|
|
221
232
|
updatedQSParams[currentParam] = currentQSParams[currentParam]
|
|
@@ -243,32 +254,30 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
243
254
|
newDatasets[datasetKey].data = newDataset
|
|
244
255
|
newDatasets[datasetKey].runtimeDataUrl = dataUrlFinal
|
|
245
256
|
newData[datasetKey] = newDataset
|
|
246
|
-
datasetsNeedsUpdate = true
|
|
247
257
|
}
|
|
248
258
|
}
|
|
259
|
+
}
|
|
249
260
|
|
|
250
|
-
|
|
251
|
-
|
|
261
|
+
if (datasetsNeedsUpdate) {
|
|
262
|
+
dispatch({ type: 'SET_DATA', payload: newData })
|
|
252
263
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
264
|
+
let newFilteredData = {}
|
|
265
|
+
let visualizations = { ...config.visualizations }
|
|
266
|
+
getVizKeys(config).forEach(key => {
|
|
267
|
+
let dataKey = config.visualizations[key].dataKey
|
|
257
268
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
269
|
+
const applicableFilters = getApplicableFilters(config.dashboard, key)
|
|
270
|
+
if (applicableFilters) {
|
|
271
|
+
newFilteredData[key] = filterData(applicableFilters, newData[dataKey])
|
|
272
|
+
}
|
|
262
273
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
274
|
+
if (newData[dataKey]) {
|
|
275
|
+
visualizations[key].formattedData = newData[dataKey]
|
|
276
|
+
}
|
|
277
|
+
})
|
|
267
278
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
dispatch({ type: 'SET_CONFIG', payload: newConfig })
|
|
271
|
-
}
|
|
279
|
+
dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
|
|
280
|
+
dispatch({ type: 'SET_CONFIG', payload: { ...config, datasets: newDatasets, visualizations } })
|
|
272
281
|
}
|
|
273
282
|
}
|
|
274
283
|
|
|
@@ -297,14 +306,13 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
297
306
|
}
|
|
298
307
|
|
|
299
308
|
getVizKeys(newConfig).forEach(visualizationKey => {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (applicableFilters.length > 0) {
|
|
309
|
+
const applicableFilters = getApplicableFilters(newConfig.dashboard, visualizationKey)
|
|
310
|
+
if (applicableFilters) {
|
|
303
311
|
const visualization = newConfig.visualizations[visualizationKey]
|
|
304
312
|
|
|
305
313
|
const formattedData = visualization.dataDescription ? getFormattedData(state.data[visualization.dataKey] || visualization.data, visualization.dataDescription) : undefined
|
|
306
314
|
|
|
307
|
-
newFilteredData[visualizationKey] = filterData(applicableFilters, formattedData || state.data[visualization.dataKey]
|
|
315
|
+
newFilteredData[visualizationKey] = filterData(applicableFilters, formattedData || state.data[visualization.dataKey])
|
|
308
316
|
}
|
|
309
317
|
})
|
|
310
318
|
|
|
@@ -346,12 +354,23 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
346
354
|
const allFiltersSelected = !state.config.dashboard.sharedFilters.some(filter => !filter.active && !filter.queuedActive)
|
|
347
355
|
if (allFiltersSelected) {
|
|
348
356
|
if (state.config.filterBehavior === FilterBehavior.Apply) {
|
|
357
|
+
const queryParams = getQueryParams()
|
|
358
|
+
let needsQueryUpdate = false
|
|
349
359
|
state.config.dashboard.sharedFilters.forEach((sharedFilter, index) => {
|
|
350
360
|
if (sharedFilter.queuedActive) {
|
|
351
361
|
dashboardConfig.sharedFilters[index].active = sharedFilter.queuedActive
|
|
352
362
|
delete dashboardConfig.sharedFilters[index].queuedActive
|
|
363
|
+
|
|
364
|
+
if (sharedFilter.setByQueryParameter && queryParams[sharedFilter.setByQueryParameter] !== sharedFilter.active) {
|
|
365
|
+
queryParams[sharedFilter.setByQueryParameter] = sharedFilter.active
|
|
366
|
+
needsQueryUpdate = true
|
|
367
|
+
}
|
|
353
368
|
}
|
|
354
369
|
})
|
|
370
|
+
|
|
371
|
+
if (needsQueryUpdate) {
|
|
372
|
+
updateQueryString(queryParams)
|
|
373
|
+
}
|
|
355
374
|
}
|
|
356
375
|
|
|
357
376
|
dispatch({ type: 'SET_CONFIG', payload: { ...state.config, dashboard: dashboardConfig } })
|
|
@@ -362,13 +381,21 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
362
381
|
}
|
|
363
382
|
}
|
|
364
383
|
|
|
365
|
-
const changeFilterActive = (index: number, value: string) => {
|
|
384
|
+
const changeFilterActive = (index: number, value: string | string[]) => {
|
|
366
385
|
const { config } = state
|
|
367
386
|
let dashboardConfig = { ...config.dashboard }
|
|
387
|
+
let filterActive = dashboardConfig.sharedFilters[index]
|
|
368
388
|
|
|
369
389
|
if (config.filterBehavior !== FilterBehavior.Apply) {
|
|
370
390
|
dashboardConfig.sharedFilters[index].active = value
|
|
391
|
+
|
|
392
|
+
const queryParams = getQueryParams()
|
|
393
|
+
if (filterActive.setByQueryParameter && queryParams[filterActive.setByQueryParameter] !== filterActive.active) {
|
|
394
|
+
queryParams[filterActive.setByQueryParameter] = filterActive.active
|
|
395
|
+
updateQueryString(queryParams)
|
|
396
|
+
}
|
|
371
397
|
} else {
|
|
398
|
+
if (Array.isArray(value)) throw Error(`Cannot set active values on urlfilters. expected: ${JSON.stringify(value)} to be a single value.`)
|
|
372
399
|
dashboardConfig.sharedFilters[index].queuedActive = value
|
|
373
400
|
}
|
|
374
401
|
|
|
@@ -381,24 +408,24 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
381
408
|
|
|
382
409
|
const updateDataFilters = () => {
|
|
383
410
|
const { config } = state
|
|
384
|
-
|
|
411
|
+
const dashboardConfig = { ...config.dashboard }
|
|
385
412
|
|
|
386
|
-
|
|
413
|
+
const newFilteredData = {}
|
|
387
414
|
getVizKeys(config).forEach(key => {
|
|
388
|
-
|
|
389
|
-
if (applicableFilters
|
|
415
|
+
const applicableFilters = getApplicableFilters(dashboardConfig, key)
|
|
416
|
+
if (applicableFilters) {
|
|
390
417
|
const visualization = config.visualizations[key]
|
|
391
418
|
const _data = state.data[visualization.dataKey] || visualization.data
|
|
392
419
|
const formattedData = visualization.dataDescription ? getFormattedData(_data, visualization.dataDescription) : _data
|
|
393
420
|
|
|
394
|
-
newFilteredData[key] = filterData(applicableFilters, formattedData
|
|
421
|
+
newFilteredData[key] = filterData(applicableFilters, formattedData)
|
|
395
422
|
}
|
|
396
423
|
})
|
|
397
424
|
|
|
398
425
|
dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
|
|
399
426
|
}
|
|
400
427
|
|
|
401
|
-
const handleOnChange = (index: number, value: string) => {
|
|
428
|
+
const handleOnChange = (index: number, value: string | string[]) => {
|
|
402
429
|
const { config } = state
|
|
403
430
|
changeFilterActive(index, value)
|
|
404
431
|
if (config.filterBehavior === FilterBehavior.Apply) {
|
|
@@ -423,6 +450,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
423
450
|
if (config.filterBehavior !== FilterBehavior.Apply) {
|
|
424
451
|
dashboardConfig.sharedFilters[index].active = value
|
|
425
452
|
} else {
|
|
453
|
+
if (Array.isArray(value)) throw Error(`Cannot set active values on urlfilters. expected: ${JSON.stringify(value)} to be a single value.`)
|
|
426
454
|
dashboardConfig.sharedFilters[index].queuedActive = value
|
|
427
455
|
}
|
|
428
456
|
const newSharedFilters = config.dashboard.sharedFilters.map((filter, _index) => {
|
|
@@ -439,68 +467,6 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
439
467
|
}
|
|
440
468
|
}
|
|
441
469
|
|
|
442
|
-
const Filters = ({ hide, autoLoad }: { hide?: number[]; autoLoad?: boolean }) => {
|
|
443
|
-
const { config } = state
|
|
444
|
-
const isLegacyFilter = !config.filterBehavior
|
|
445
|
-
return (
|
|
446
|
-
<>
|
|
447
|
-
{config.dashboard.sharedFilters.map((singleFilter, filterIndex) => {
|
|
448
|
-
if ((singleFilter.type !== 'urlfilter' && !singleFilter.showDropdown) || (hide && hide.indexOf(filterIndex) !== -1)) return <></>
|
|
449
|
-
const values: JSX.Element[] = []
|
|
450
|
-
if (singleFilter.resetLabel) {
|
|
451
|
-
values.push(
|
|
452
|
-
<option key={`${singleFilter.resetLabel}-option`} value={singleFilter.resetLabel}>
|
|
453
|
-
{singleFilter.resetLabel}
|
|
454
|
-
</option>
|
|
455
|
-
)
|
|
456
|
-
}
|
|
457
|
-
const _key = singleFilter.apiFilter ? getApiFilterKey(singleFilter.apiFilter) : undefined
|
|
458
|
-
if (_key && apiFilterDropdowns[_key]) {
|
|
459
|
-
// URL Filter
|
|
460
|
-
apiFilterDropdowns[_key]!.forEach(({ text, value }, index) => {
|
|
461
|
-
values.push(
|
|
462
|
-
<option key={`${value}-option-${index}`} value={value}>
|
|
463
|
-
{text}
|
|
464
|
-
</option>
|
|
465
|
-
)
|
|
466
|
-
})
|
|
467
|
-
} else {
|
|
468
|
-
// Data Filter
|
|
469
|
-
singleFilter.values?.forEach((filterOption, index) => {
|
|
470
|
-
const labeledOpt = singleFilter.labels && singleFilter.labels[filterOption]
|
|
471
|
-
values.push(
|
|
472
|
-
<option key={`${singleFilter.key}-option-${index}`} value={filterOption}>
|
|
473
|
-
{labeledOpt || filterOption}
|
|
474
|
-
</option>
|
|
475
|
-
)
|
|
476
|
-
})
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
return (
|
|
480
|
-
<div className='cove-dashboard-filters' key={`${singleFilter.key}-filtersection-${filterIndex}`}>
|
|
481
|
-
<section className='dashboard-filters-section'>
|
|
482
|
-
<label htmlFor={`filter-${filterIndex}`}>{singleFilter.key}</label>
|
|
483
|
-
<select
|
|
484
|
-
id={`filter-${filterIndex}`}
|
|
485
|
-
className='filter-select'
|
|
486
|
-
data-index='0'
|
|
487
|
-
value={singleFilter.queuedActive || singleFilter.active}
|
|
488
|
-
onChange={val => {
|
|
489
|
-
handleOnChange(filterIndex, val.target.value)
|
|
490
|
-
}}
|
|
491
|
-
>
|
|
492
|
-
{values}
|
|
493
|
-
</select>
|
|
494
|
-
</section>
|
|
495
|
-
</div>
|
|
496
|
-
)
|
|
497
|
-
})}
|
|
498
|
-
|
|
499
|
-
{!isLegacyFilter && config.filterBehavior === FilterBehavior.Apply && <button onClick={applyFilters}>GO!</button>}
|
|
500
|
-
</>
|
|
501
|
-
)
|
|
502
|
-
}
|
|
503
|
-
|
|
504
470
|
const resizeObserver = new ResizeObserver(entries => {
|
|
505
471
|
for (let entry of entries) {
|
|
506
472
|
let newViewport = getViewport(entry.contentRect.width)
|
|
@@ -520,13 +486,20 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
520
486
|
// Prevent render if loading
|
|
521
487
|
if (state.loading) return <Loading />
|
|
522
488
|
|
|
489
|
+
const GoButton = ({ autoLoad }: { autoLoad?: boolean }) => {
|
|
490
|
+
if (state.config.filterBehavior === FilterBehavior.Apply && !autoLoad) {
|
|
491
|
+
return <button onClick={applyFilters}>GO!</button>
|
|
492
|
+
}
|
|
493
|
+
return null
|
|
494
|
+
}
|
|
495
|
+
|
|
523
496
|
let body: JSX.Element | null = null
|
|
524
497
|
// Editor mode
|
|
525
|
-
if (isEditor && !
|
|
498
|
+
if (isEditor && !isPreview) {
|
|
526
499
|
let subVisualizationEditing = false
|
|
527
500
|
|
|
528
501
|
getVizKeys(state.config).forEach(visualizationKey => {
|
|
529
|
-
|
|
502
|
+
const visualizationConfig = _.cloneDeep(state.config.visualizations[visualizationKey])
|
|
530
503
|
|
|
531
504
|
const dataKey = visualizationConfig.dataKey || 'backwards-compatibility'
|
|
532
505
|
|
|
@@ -598,11 +571,10 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
598
571
|
)
|
|
599
572
|
break
|
|
600
573
|
case 'data-bite':
|
|
601
|
-
visualizationConfig = { ...visualizationConfig, newViz: true }
|
|
602
574
|
body = (
|
|
603
575
|
<>
|
|
604
576
|
<Header visualizationKey={visualizationKey} subEditor='Data Bite' />
|
|
605
|
-
<CdcDataBite key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={_updateConfig} isDashboard={true} />
|
|
577
|
+
<CdcDataBite key={visualizationKey} config={{ ...visualizationConfig, newViz: true }} isEditor={true} setConfig={_updateConfig} isDashboard={true} />
|
|
606
578
|
</>
|
|
607
579
|
)
|
|
608
580
|
break
|
|
@@ -635,12 +607,20 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
635
607
|
body = !hideFilter ? (
|
|
636
608
|
<>
|
|
637
609
|
<Header visualizationKey={visualizationKey} subEditor='Filter Dropdowns' />
|
|
638
|
-
<Filters hide={visualizationConfig.hide}
|
|
610
|
+
<Filters hide={visualizationConfig.hide} filters={state.config.dashboard.sharedFilters} apiFilterDropdowns={apiFilterDropdowns} handleOnChange={handleOnChange} />
|
|
611
|
+
<GoButton autoLoad={visualizationConfig.autoLoad} />
|
|
639
612
|
</>
|
|
640
613
|
) : (
|
|
641
614
|
<></>
|
|
642
615
|
)
|
|
643
616
|
break
|
|
617
|
+
case 'table':
|
|
618
|
+
body = (
|
|
619
|
+
<EditorWrapper component={DataTableStandAlone} visualizationKey={visualizationKey} visualizationConfig={visualizationConfig} updateConfig={_updateConfig} type={'Table'} viewport={currentViewport}>
|
|
620
|
+
<DataTableEditorPanel key={visualizationKey} config={visualizationConfig} updateConfig={_updateConfig} />
|
|
621
|
+
</EditorWrapper>
|
|
622
|
+
)
|
|
623
|
+
break
|
|
644
624
|
default:
|
|
645
625
|
body = <></>
|
|
646
626
|
break
|
|
@@ -652,7 +632,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
652
632
|
body = (
|
|
653
633
|
<DndProvider backend={HTML5Backend}>
|
|
654
634
|
<div className='header-container'>
|
|
655
|
-
<Header
|
|
635
|
+
<Header />
|
|
656
636
|
<VisualizationsPanel loadConfig={newConfig => dispatch({ type: 'UPDATE_CONFIG', payload: [newConfig] })} config={state.config} />
|
|
657
637
|
</div>
|
|
658
638
|
|
|
@@ -667,28 +647,35 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
667
647
|
const { title, description } = config.dashboard || {}
|
|
668
648
|
body = (
|
|
669
649
|
<>
|
|
670
|
-
{isEditor && <Header
|
|
671
|
-
<MultiTabs isEditor={isEditor && !
|
|
650
|
+
{isEditor && <Header />}
|
|
651
|
+
<MultiTabs isEditor={isEditor && !isPreview} />
|
|
672
652
|
<div className={`cdc-dashboard-inner-container${isEditor ? ' is-editor' : ''}`}>
|
|
673
653
|
<Title title={title} isDashboard={true} classes={[`dashboard-title`, `${config.dashboard.theme ?? 'theme-blue'}`]} />
|
|
674
654
|
{/* Description */}
|
|
675
655
|
{description && <div className='subtext'>{parse(description)}</div>}
|
|
676
656
|
|
|
677
657
|
{/* Filters */}
|
|
678
|
-
{config.dashboard.sharedFilters && Object.values(config.visualizations || {}).filter(viz => viz.visualizationType === 'filter-dropdowns').length === 0 &&
|
|
658
|
+
{config.dashboard.sharedFilters && Object.values(config.visualizations || {}).filter(viz => viz.visualizationType === 'filter-dropdowns').length === 0 && (
|
|
659
|
+
<>
|
|
660
|
+
<Filters filters={state.config.dashboard.sharedFilters} apiFilterDropdowns={apiFilterDropdowns} handleOnChange={handleOnChange} />
|
|
661
|
+
<GoButton />
|
|
662
|
+
</>
|
|
663
|
+
)}
|
|
679
664
|
|
|
680
665
|
{/* Visualizations */}
|
|
681
666
|
{config.rows &&
|
|
682
667
|
config.rows
|
|
683
668
|
.filter(row => row.filter(col => col.widget).length !== 0)
|
|
684
669
|
.map((row, index) => {
|
|
670
|
+
const isToggleRow = row[0].toggle
|
|
685
671
|
return (
|
|
686
|
-
<div className={`dashboard-row ${row.equalHeight ? 'equal-height' : ''}`} key={`row__${index}`}>
|
|
672
|
+
<div className={`dashboard-row ${row.equalHeight ? 'equal-height' : ''} ${isToggleRow ? 'toggle' : ''}`} key={`row__${index}`}>
|
|
673
|
+
{isToggleRow && <Toggle row={row} rowIndex={index} visualizations={config.visualizations} />}
|
|
687
674
|
{row.map((col, colIndex) => {
|
|
688
675
|
if (col.width) {
|
|
689
676
|
if (!col.widget) return <div key={`row__${index}__col__${colIndex}`} className={`dashboard-col dashboard-col-${col.width}`}></div>
|
|
690
677
|
|
|
691
|
-
|
|
678
|
+
const visualizationConfig = _.cloneDeep(config.visualizations[col.widget])
|
|
692
679
|
|
|
693
680
|
const dataKey = visualizationConfig.dataKey || 'backwards-compatibility'
|
|
694
681
|
|
|
@@ -714,9 +701,12 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
714
701
|
</a>
|
|
715
702
|
)
|
|
716
703
|
const hideFilter = visualizationConfig.autoLoad && inNoDataState
|
|
704
|
+
|
|
705
|
+
const hiddenToggle = col.hide !== undefined ? col.hide : colIndex !== 0
|
|
706
|
+
const hidden = col.toggle ? hiddenToggle : false
|
|
717
707
|
return (
|
|
718
708
|
<React.Fragment key={`vis__${index}__${colIndex}`}>
|
|
719
|
-
<div className={`dashboard-col dashboard-col-${col.width}`}>
|
|
709
|
+
<div className={`dashboard-col dashboard-col-${col.width} ${hidden ? 'hidden-toggle' : ''}`}>
|
|
720
710
|
{visualizationConfig.type === 'chart' && (
|
|
721
711
|
<CdcChart
|
|
722
712
|
key={col.widget}
|
|
@@ -797,7 +787,13 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
797
787
|
configUrl={undefined}
|
|
798
788
|
/>
|
|
799
789
|
)}
|
|
800
|
-
{visualizationConfig.type === 'filter-dropdowns' && !hideFilter &&
|
|
790
|
+
{visualizationConfig.type === 'filter-dropdowns' && !hideFilter && (
|
|
791
|
+
<>
|
|
792
|
+
<Filters hide={visualizationConfig.hide} filters={state.config.dashboard.sharedFilters} apiFilterDropdowns={apiFilterDropdowns} handleOnChange={handleOnChange} />
|
|
793
|
+
<GoButton autoLoad={visualizationConfig.autoLoad} />
|
|
794
|
+
</>
|
|
795
|
+
)}
|
|
796
|
+
{visualizationConfig.type === 'table' && <DataTableStandAlone key={col.widget} visualizationKey={col.widget} config={visualizationConfig} viewport={currentViewport} />}
|
|
801
797
|
</div>
|
|
802
798
|
</React.Fragment>
|
|
803
799
|
)
|
|
@@ -818,8 +814,8 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
818
814
|
{config.table?.show && config.data && (
|
|
819
815
|
<DataTable
|
|
820
816
|
config={config}
|
|
821
|
-
rawData={config.data}
|
|
822
|
-
runtimeData={config.data || []}
|
|
817
|
+
rawData={config.data?.[0]?.tableData ? config.data?.[0]?.tableData : config.data}
|
|
818
|
+
runtimeData={config.data?.[0]?.tableData ? config.data?.[0]?.tableData : config.data || []}
|
|
823
819
|
expandDataTable={config.table.expanded}
|
|
824
820
|
showDownloadButton={config.table.download}
|
|
825
821
|
tableTitle={config.dashboard.title || ''}
|
|
@@ -835,44 +831,35 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
835
831
|
config.datasets &&
|
|
836
832
|
Object.keys(config.datasets).map(datasetKey => {
|
|
837
833
|
//For each dataset, find any shared filters that apply to all visualizations using the dataset
|
|
838
|
-
//Apply these filters to the table
|
|
839
|
-
let filteredTableData
|
|
840
|
-
if (config.dashboard.sharedFilters && config.dashboard.sharedFilters.length > 0) {
|
|
841
|
-
//Gets list of visuailzations using the dataset
|
|
842
|
-
let vizKeysUsingDataset: string[] = []
|
|
843
|
-
getVizKeys(config).forEach(visualizationKey => {
|
|
844
|
-
if (config.visualizations[visualizationKey].dataKey === datasetKey) {
|
|
845
|
-
vizKeysUsingDataset.push(visualizationKey)
|
|
846
|
-
}
|
|
847
|
-
})
|
|
848
834
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
config.
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
835
|
+
//Gets list of visuailzations using the dataset
|
|
836
|
+
const vizKeysUsingDataset: string[] = getVizKeys(config).filter(visualizationKey => {
|
|
837
|
+
return config.visualizations[visualizationKey].dataKey === datasetKey
|
|
838
|
+
})
|
|
839
|
+
|
|
840
|
+
//Checks shared filters against list to see if all visualizations are represented
|
|
841
|
+
let applicableFilters: SharedFilter[] = []
|
|
842
|
+
config.dashboard.sharedFilters.forEach(sharedFilter => {
|
|
843
|
+
let allMatch = true
|
|
844
|
+
vizKeysUsingDataset.forEach(visualizationKey => {
|
|
845
|
+
if (sharedFilter.usedBy && sharedFilter.usedBy.indexOf(visualizationKey) === -1) {
|
|
846
|
+
allMatch = false
|
|
860
847
|
}
|
|
861
848
|
})
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (applicableFilters.length > 0) {
|
|
865
|
-
filteredTableData = filterData(applicableFilters, config.datasets[datasetKey].data, state.config.filterBehavior)
|
|
849
|
+
if (allMatch) {
|
|
850
|
+
applicableFilters.push(sharedFilter)
|
|
866
851
|
}
|
|
867
|
-
}
|
|
852
|
+
})
|
|
868
853
|
|
|
854
|
+
//Applys any applicable filters to the Table
|
|
855
|
+
const filteredTableData = applicableFilters.length > 0 ? filterData(applicableFilters, config.datasets[datasetKey].data) : undefined
|
|
869
856
|
return (
|
|
870
857
|
<div className='multi-table-container' id={`data-table-${datasetKey}`} key={`data-table-${datasetKey}`}>
|
|
871
858
|
<DataTable
|
|
872
859
|
config={config as TableConfig}
|
|
873
860
|
dataConfig={config.datasets[datasetKey]}
|
|
874
|
-
rawData={config.datasets[datasetKey].data}
|
|
875
|
-
runtimeData={filteredTableData || config.datasets[datasetKey].data || []}
|
|
861
|
+
rawData={config.datasets[datasetKey].data?.[0]?.tableData || config.datasets[datasetKey].data}
|
|
862
|
+
runtimeData={config.datasets[datasetKey].data?.[0]?.tableData || filteredTableData || config.datasets[datasetKey].data || []}
|
|
876
863
|
expandDataTable={config.table.expanded}
|
|
877
864
|
tableTitle={datasetKey}
|
|
878
865
|
viewport={currentViewport}
|
package/src/DashboardContext.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Dispatch, createContext } from 'react'
|
|
2
2
|
import { DashboardState } from './store/dashboard.reducer'
|
|
3
3
|
import DashboardActions from './store/dashboard.actions'
|
|
4
|
+
import { Tab } from './types/Tab'
|
|
4
5
|
|
|
5
6
|
type ConfigCTX = DashboardState & {
|
|
6
7
|
outerContainerRef: (node: any) => void
|
|
@@ -8,12 +9,14 @@ type ConfigCTX = DashboardState & {
|
|
|
8
9
|
isDebug: boolean
|
|
9
10
|
}
|
|
10
11
|
|
|
12
|
+
const firstTab: Tab = 'Dashboard Description'
|
|
13
|
+
|
|
11
14
|
export const initialState = {
|
|
12
15
|
data: {},
|
|
13
16
|
loading: false,
|
|
14
17
|
filteredData: {},
|
|
15
18
|
preview: false,
|
|
16
|
-
tabSelected:
|
|
19
|
+
tabSelected: firstTab
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
const initialContext: ConfigCTX = {
|