@cdc/dashboard 4.25.10 → 4.26.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 +66 -0
- package/dist/{cdcdashboard-fce76882.es.js → cdcdashboard-BnB1QM5d.es.js} +6 -13
- package/dist/{cdcdashboard-c55ac1ea.es.js → cdcdashboard-D6CG2-Hb.es.js} +5 -12
- package/dist/{cdcdashboard-31a33da1.es.js → cdcdashboard-MXgURbdZ.es.js} +6 -13
- package/dist/{cdcdashboard-1a1724a1.es.js → cdcdashboard-dgT_1dIT.es.js} +136 -151
- package/dist/cdcdashboard.js +84214 -79641
- package/examples/api-dashboard-data.json +272 -0
- package/examples/api-dashboard-years.json +11 -0
- package/examples/api-geographies-data.json +11 -0
- package/examples/api-test/categories.json +18 -0
- package/examples/api-test/chart-data.json +602 -0
- package/examples/api-test/topics.json +47 -0
- package/examples/api-test/years.json +22 -0
- package/examples/markup-axis-label.json +4167 -0
- package/examples/private/big-dashboard.json +39095 -39077
- package/examples/private/cat-y.json +1235 -0
- package/examples/private/chronic-dash.json +1584 -0
- package/examples/private/clade-2.json +430 -0
- package/examples/private/diabetes.json +546 -196
- package/examples/private/map-issue.json +2260 -0
- package/examples/private/markup-footer/mortality-deaths-footnotes-age.csv +3 -0
- package/examples/private/mpinc-state-reports.json +2260 -0
- package/examples/private/mpox.json +38128 -0
- package/examples/private/nwss/rsv.json +1240 -0
- package/examples/private/reset.json +32920 -0
- package/examples/private/simple-dash.json +490 -0
- package/examples/private/test-dash.json +0 -0
- package/examples/private/test123.json +491 -0
- package/examples/test-api-filter-reset.json +132 -0
- package/examples/test-dashboard-simple.json +503 -0
- package/index.html +25 -26
- package/package.json +11 -11
- package/src/CdcDashboardComponent.tsx +35 -10
- package/src/DashboardContext.tsx +3 -1
- package/src/_stories/Dashboard.DataSetup.stories.tsx +203 -0
- package/src/_stories/Dashboard.stories.tsx +402 -1
- package/src/_stories/_mock/custom-order-new-values.json +116 -0
- package/src/_stories/_mock/filter-cascade.json +3350 -0
- package/src/_stories/_mock/gallery-data-bite-dashboard.json +3500 -0
- package/src/_stories/_mock/nested-parent-child-filters.json +392 -0
- package/src/_stories/_mock/parent-child-filters.json +233 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +54 -31
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +118 -50
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +96 -108
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +196 -59
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +129 -29
- package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +62 -3
- package/src/components/DataDesignerModal.tsx +18 -6
- package/src/components/Header/Header.tsx +53 -21
- package/src/components/Toggle/Toggle.tsx +48 -48
- package/src/components/VisualizationRow.tsx +73 -6
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +2 -3
- package/src/components/Widget/Widget.tsx +1 -1
- package/src/data/initial-state.js +1 -0
- package/src/helpers/addValuesToDashboardFilters.ts +24 -6
- package/src/helpers/apiFilterHelpers.ts +26 -2
- package/src/helpers/changeFilterActive.ts +67 -65
- package/src/helpers/filterData.ts +52 -7
- package/src/helpers/filterResetHelpers.ts +102 -0
- package/src/helpers/formatConfigBeforeSave.ts +6 -5
- package/src/helpers/getUpdateConfig.ts +91 -91
- package/src/helpers/getVizConfig.ts +2 -2
- package/src/helpers/loadAPIFilters.ts +109 -99
- package/src/helpers/tests/filterResetHelpers.test.ts +532 -0
- package/src/helpers/tests/updatesChildFilters.test.ts +53 -22
- package/src/helpers/updateChildFilters.ts +50 -27
- package/src/index.tsx +1 -0
- package/src/scss/editor-panel.scss +3 -431
- package/src/scss/main.scss +142 -25
- package/src/store/errorMessage/errorMessage.reducer.ts +1 -1
- package/src/test/CdcDashboard.test.jsx +9 -4
- package/src/types/Dashboard.ts +1 -0
- package/src/types/DashboardFilters.ts +9 -8
- package/src/types/FilterStyles.ts +8 -7
- package/src/types/SharedFilter.ts +13 -0
- package/LICENSE +0 -201
- package/examples/private/DEV-11072.json +0 -7591
- package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_data.csv +0 -14041
- package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_per_100000_data.csv +0 -14041
- package/examples/private/burden_toolkit_mortality_qaly_data.csv +0 -18721
- package/examples/private/burden_toolkit_mortality_yll_data.csv +0 -18721
- package/examples/private/pedro.json +0 -1
- package/src/helpers/getAutoLoadVisualization.ts +0 -11
- package/src/scss/mixins.scss +0 -47
- package/src/scss/variables.scss +0 -5
- /package/dist/{cdcdashboard-548642e6.es.js → cdcdashboard-Ct2SB0vL.es.js} +0 -0
|
@@ -23,7 +23,7 @@ import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
|
23
23
|
import getViewport from '@cdc/core/helpers/getViewport'
|
|
24
24
|
|
|
25
25
|
import Grid from './components/Grid'
|
|
26
|
-
import Header from './components/Header
|
|
26
|
+
import Header from './components/Header'
|
|
27
27
|
import DataTable from '@cdc/core/components/DataTable'
|
|
28
28
|
import MediaControls from '@cdc/core/components/MediaControls'
|
|
29
29
|
|
|
@@ -32,7 +32,7 @@ import './scss/main.scss'
|
|
|
32
32
|
import VisualizationsPanel from './components/VisualizationsPanel'
|
|
33
33
|
import dashboardReducer from './store/dashboard.reducer'
|
|
34
34
|
import errorMessagesReducer from './store/errorMessage/errorMessage.reducer'
|
|
35
|
-
import { filterData } from './helpers/filterData'
|
|
35
|
+
import { filterData, isFilterAtResetState } from './helpers/filterData'
|
|
36
36
|
import { getVizKeys } from './helpers/getVizKeys'
|
|
37
37
|
import Title from '@cdc/core/components/ui/Title'
|
|
38
38
|
import { type TableConfig } from '@cdc/core/components/DataTable/types/TableConfig'
|
|
@@ -60,6 +60,7 @@ import Alert from '@cdc/core/components/Alert'
|
|
|
60
60
|
import { shouldLoadAllFilters } from './helpers/shouldLoadAllFilters'
|
|
61
61
|
import { subscribe, unsubscribe } from '@cdc/core/helpers/events'
|
|
62
62
|
import DashboardEditors from './components/DashboardEditors'
|
|
63
|
+
import { updateChildFilters } from './helpers/updateChildFilters'
|
|
63
64
|
|
|
64
65
|
type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
65
66
|
initialState: InitialState
|
|
@@ -89,10 +90,27 @@ export default function CdcDashboard({
|
|
|
89
90
|
return true
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
// Check if any filters are at their reset state (incomplete)
|
|
94
|
+
const sharedFilters = state.config.dashboard?.sharedFilters || []
|
|
95
|
+
const hasResetFilters = sharedFilters.some(isFilterAtResetState)
|
|
96
|
+
if (hasResetFilters) {
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
|
|
92
100
|
const vals = reloadURLHelpers.getDatasetKeys(state.config).map(key => state.data[key])
|
|
101
|
+
|
|
102
|
+
// Check if there are any visualizations that actually need data
|
|
103
|
+
// Markup-includes without dataKey don't require dashboard data
|
|
104
|
+
const visualizationsNeedingData = Object.values(state.config.visualizations).filter(viz => {
|
|
105
|
+
return viz.type !== 'markup-include' || viz.dataKey
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// If no visualizations need data, don't show no-data state
|
|
109
|
+
if (!vals.length && visualizationsNeedingData.length === 0) return false
|
|
110
|
+
|
|
93
111
|
if (!vals.length) return true
|
|
94
112
|
return vals.some(val => val === undefined)
|
|
95
|
-
}, [state.data, state.config.visualizations, state.filtersApplied])
|
|
113
|
+
}, [state.data, state.config.visualizations, state.config.dashboard?.sharedFilters, state.filtersApplied])
|
|
96
114
|
|
|
97
115
|
const vizRowColumnLocator = getVizRowColumnLocator(state.config.rows)
|
|
98
116
|
|
|
@@ -165,9 +183,13 @@ export default function CdcDashboard({
|
|
|
165
183
|
}
|
|
166
184
|
|
|
167
185
|
if (filter.apiFilter && filter.active) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
186
|
+
// Don't add filter to query params if it's set to its resetLabel
|
|
187
|
+
const isResetLabel = filter.resetLabel && filter.active === filter.resetLabel
|
|
188
|
+
if (!isResetLabel) {
|
|
189
|
+
updatedQSParams[filter.apiFilter.valueSelector] = filter.active
|
|
190
|
+
if (filter.apiFilter.subgroupValueSelector && filter.subGrouping.active) {
|
|
191
|
+
updatedQSParams[filter.apiFilter.subgroupValueSelector] = filter.subGrouping.active
|
|
192
|
+
}
|
|
171
193
|
}
|
|
172
194
|
}
|
|
173
195
|
}
|
|
@@ -292,8 +314,6 @@ export default function CdcDashboard({
|
|
|
292
314
|
}
|
|
293
315
|
|
|
294
316
|
const setEventData = ({ detail }, data, filteredData) => {
|
|
295
|
-
// eslint-disable-next-line no-console
|
|
296
|
-
console.log('Event: cove_set_data', detail)
|
|
297
317
|
try {
|
|
298
318
|
const newDatasets = Object.keys(detail).reduce((acc, key) => {
|
|
299
319
|
if (data[key] !== undefined) {
|
|
@@ -321,7 +341,8 @@ export default function CdcDashboard({
|
|
|
321
341
|
useEffect(() => {
|
|
322
342
|
const { config } = state
|
|
323
343
|
const loadAllFilters = shouldLoadAllFilters(config, isEditor && !isPreview)
|
|
324
|
-
|
|
344
|
+
let sharedFiltersWithValues = addValuesToDashboardFilters(config.dashboard.sharedFilters, state.data)
|
|
345
|
+
sharedFiltersWithValues = updateChildFilters(sharedFiltersWithValues, state.data)
|
|
325
346
|
setAPILoading(true)
|
|
326
347
|
loadAPIFilters(sharedFiltersWithValues, apiFilterDropdowns, loadAllFilters)?.then(newFilters => {
|
|
327
348
|
const allValuesSelected = newFilters.every(filter => {
|
|
@@ -484,10 +505,11 @@ export default function CdcDashboard({
|
|
|
484
505
|
<Title
|
|
485
506
|
title={title}
|
|
486
507
|
isDashboard={true}
|
|
508
|
+
titleStyle={config.dashboard.titleStyle}
|
|
487
509
|
classes={[`dashboard-title`, `${config.dashboard.theme ?? 'theme-blue'}`]}
|
|
488
510
|
/>
|
|
489
511
|
{/* Description */}
|
|
490
|
-
{description && <div className='subtext mb-
|
|
512
|
+
{description && <div className='subtext mb-4'>{parse(description)}</div>}
|
|
491
513
|
{/* Visualizations */}
|
|
492
514
|
{filteredRows?.map((row, index) => (
|
|
493
515
|
<VisualizationRow
|
|
@@ -627,6 +649,9 @@ export default function CdcDashboard({
|
|
|
627
649
|
}
|
|
628
650
|
|
|
629
651
|
const dashboardContainerClasses = ['cdc-open-viz-module', 'type-dashboard', `${currentViewport}`]
|
|
652
|
+
if (isEditor) {
|
|
653
|
+
dashboardContainerClasses.push('isDashboardEditor')
|
|
654
|
+
}
|
|
630
655
|
|
|
631
656
|
return (
|
|
632
657
|
<GlobalContextProvider>
|
package/src/DashboardContext.tsx
CHANGED
|
@@ -15,7 +15,9 @@ type ConfigCTX = DashboardState & {
|
|
|
15
15
|
loadAPIFilters: (
|
|
16
16
|
sharedFilters: SharedFilter[],
|
|
17
17
|
dropdowns: APIFilterDropdowns,
|
|
18
|
-
|
|
18
|
+
loadAll?: boolean,
|
|
19
|
+
recursiveLimit?: number,
|
|
20
|
+
isStale?: () => boolean
|
|
19
21
|
) => Promise<SharedFilter[]>
|
|
20
22
|
setAPIFilterDropdowns: (dropdowns: APIFilterDropdowns) => void
|
|
21
23
|
setAPILoading: (loading: boolean) => void
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { within, expect, waitFor, userEvent } from 'storybook/test'
|
|
3
|
+
import CdcEditor from '@cdc/editor/src/CdcEditor'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof CdcEditor> = {
|
|
6
|
+
title: 'Components/Pages/Dashboard/Data Setup Integration Tests',
|
|
7
|
+
component: CdcEditor
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default meta
|
|
11
|
+
|
|
12
|
+
type Story = StoryObj<typeof CdcEditor>
|
|
13
|
+
|
|
14
|
+
// Helper function to wait
|
|
15
|
+
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Multi-Visualization Configuration Workflow
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// This test matches the Playwright codegen test exactly
|
|
21
|
+
|
|
22
|
+
export const MultiVizConfigurationWorkflow: Story = {
|
|
23
|
+
args: {
|
|
24
|
+
config: {}
|
|
25
|
+
},
|
|
26
|
+
parameters: {
|
|
27
|
+
docs: {
|
|
28
|
+
description: {
|
|
29
|
+
story:
|
|
30
|
+
'Tests multi-visualization configuration workflow (matches Playwright codegen):\n' +
|
|
31
|
+
'1. Click "Choose Visualization Type"\n' +
|
|
32
|
+
'2. Click "Dashboard" button\n' +
|
|
33
|
+
'3. Click "Load from URL"\n' +
|
|
34
|
+
'4. Enter "Demo" in dataset name textbox\n' +
|
|
35
|
+
'5. Fill "/custom-order-demo-data.csv" in URL textbox\n' +
|
|
36
|
+
'6. Click "Always load from URL"\n' +
|
|
37
|
+
'7. Click "Save & Load" button\n' +
|
|
38
|
+
'8. Click "Configure your visualization" button\n' +
|
|
39
|
+
'9. Click "gearMulti" button\n' +
|
|
40
|
+
'10. Select "Demo" from combobox\n' +
|
|
41
|
+
'11. Click "Vertical Values for map" button\n' +
|
|
42
|
+
'12. Click "No" button\n' +
|
|
43
|
+
'13. Click "Configure Multiple"\n' +
|
|
44
|
+
'14. Select "Location" from Multi-Visualization Column dropdown\n' +
|
|
45
|
+
'15. Click "Continue" button'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
play: async ({ canvasElement }) => {
|
|
50
|
+
const canvas = within(canvasElement)
|
|
51
|
+
const user = userEvent.setup()
|
|
52
|
+
|
|
53
|
+
// ========================================================================
|
|
54
|
+
// STEP 1: Click "Choose Visualization Type" (verify it's visible)
|
|
55
|
+
// ========================================================================
|
|
56
|
+
await waitFor(
|
|
57
|
+
async () => {
|
|
58
|
+
const chooseVizText = canvas.queryByText(/choose visualization type/i)
|
|
59
|
+
expect(chooseVizText).toBeTruthy()
|
|
60
|
+
},
|
|
61
|
+
{ timeout: 5000 }
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const chooseVizText = canvas.getByText(/choose visualization type/i)
|
|
65
|
+
await user.click(chooseVizText)
|
|
66
|
+
await sleep(500)
|
|
67
|
+
|
|
68
|
+
// ========================================================================
|
|
69
|
+
// STEP 2: Click "Dashboard" button
|
|
70
|
+
// ========================================================================
|
|
71
|
+
const dashboardButton = canvas.getByRole('button', { name: /dashboard/i })
|
|
72
|
+
await user.click(dashboardButton)
|
|
73
|
+
await sleep(1500)
|
|
74
|
+
|
|
75
|
+
// ========================================================================
|
|
76
|
+
// STEP 3: Click "Load from URL"
|
|
77
|
+
// ========================================================================
|
|
78
|
+
await sleep(500)
|
|
79
|
+
|
|
80
|
+
const loadFromUrlButton =
|
|
81
|
+
canvas.queryAllByText(/load from url/i)[0] ||
|
|
82
|
+
canvas.queryByRole('button', { name: /url/i }) ||
|
|
83
|
+
canvas.queryByRole('tab', { name: /url/i })
|
|
84
|
+
|
|
85
|
+
if (loadFromUrlButton) {
|
|
86
|
+
await user.click(loadFromUrlButton)
|
|
87
|
+
await sleep(500)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ========================================================================
|
|
91
|
+
// STEP 4: Enter "Demo" in dataset name textbox
|
|
92
|
+
// ========================================================================
|
|
93
|
+
await sleep(500)
|
|
94
|
+
|
|
95
|
+
const datasetNameInput = canvas.getByRole('textbox', { name: /enter dataset name/i })
|
|
96
|
+
await user.click(datasetNameInput)
|
|
97
|
+
await user.clear(datasetNameInput)
|
|
98
|
+
await user.type(datasetNameInput, 'Demo')
|
|
99
|
+
await sleep(300)
|
|
100
|
+
|
|
101
|
+
// ========================================================================
|
|
102
|
+
// STEP 5: Fill "/custom-order-demo-data.csv" in URL textbox
|
|
103
|
+
// ========================================================================
|
|
104
|
+
|
|
105
|
+
// Press Tab to move to URL field
|
|
106
|
+
await user.tab()
|
|
107
|
+
|
|
108
|
+
const urlInput = canvas.getByRole('textbox', { name: /load data from external url/i })
|
|
109
|
+
await user.clear(urlInput)
|
|
110
|
+
await user.type(urlInput, '/custom-order-demo-data.csv')
|
|
111
|
+
await sleep(300)
|
|
112
|
+
|
|
113
|
+
// ========================================================================
|
|
114
|
+
// STEP 6: Click "Always load from URL"
|
|
115
|
+
// ========================================================================
|
|
116
|
+
await sleep(500)
|
|
117
|
+
|
|
118
|
+
const alwaysLoadText = canvas.getByText(/always load from url/i)
|
|
119
|
+
await user.click(alwaysLoadText)
|
|
120
|
+
await sleep(300)
|
|
121
|
+
|
|
122
|
+
// ========================================================================
|
|
123
|
+
// STEP 7: Click "Save & Load" button
|
|
124
|
+
// ========================================================================
|
|
125
|
+
await sleep(500)
|
|
126
|
+
|
|
127
|
+
const saveLoadButton = canvas.getByRole('button', { name: /save & load/i })
|
|
128
|
+
await user.click(saveLoadButton)
|
|
129
|
+
await sleep(2000) // Wait for data to load
|
|
130
|
+
|
|
131
|
+
// ========================================================================
|
|
132
|
+
// STEP 8: Click "Configure your visualization" button
|
|
133
|
+
// ========================================================================
|
|
134
|
+
await sleep(1000)
|
|
135
|
+
|
|
136
|
+
const configureButton = canvas.getByRole('button', { name: /configure your visualization/i })
|
|
137
|
+
await user.click(configureButton)
|
|
138
|
+
await sleep(500)
|
|
139
|
+
|
|
140
|
+
// ========================================================================
|
|
141
|
+
// STEP 9: Click "gearMulti" button
|
|
142
|
+
// ========================================================================
|
|
143
|
+
await sleep(500)
|
|
144
|
+
|
|
145
|
+
const gearMultiButton = canvas.getByRole('button', { name: /gearMulti/i })
|
|
146
|
+
await user.click(gearMultiButton)
|
|
147
|
+
await sleep(500)
|
|
148
|
+
|
|
149
|
+
// ========================================================================
|
|
150
|
+
// STEP 10: Select "Demo" from combobox
|
|
151
|
+
// ========================================================================
|
|
152
|
+
await sleep(500)
|
|
153
|
+
|
|
154
|
+
const dataSourceSelect = canvas.getByRole('combobox')
|
|
155
|
+
await user.selectOptions(dataSourceSelect, 'Demo')
|
|
156
|
+
await sleep(500)
|
|
157
|
+
|
|
158
|
+
// ========================================================================
|
|
159
|
+
// STEP 11: Click "Vertical Values for map" button
|
|
160
|
+
// ========================================================================
|
|
161
|
+
await sleep(500)
|
|
162
|
+
|
|
163
|
+
const verticalValuesButton = canvas.getByRole('button', { name: /vertical values for map/i })
|
|
164
|
+
await user.click(verticalValuesButton)
|
|
165
|
+
await sleep(500)
|
|
166
|
+
|
|
167
|
+
// ========================================================================
|
|
168
|
+
// STEP 12: Click "No" button
|
|
169
|
+
// ========================================================================
|
|
170
|
+
await sleep(500)
|
|
171
|
+
|
|
172
|
+
const noButton = canvas.getByRole('button', { name: /^no$/i })
|
|
173
|
+
await user.click(noButton)
|
|
174
|
+
await sleep(500)
|
|
175
|
+
|
|
176
|
+
// ========================================================================
|
|
177
|
+
// STEP 13: Click "Configure Multiple"
|
|
178
|
+
// ========================================================================
|
|
179
|
+
await sleep(500)
|
|
180
|
+
|
|
181
|
+
const configureMultipleText = canvas.getByText(/configure multiple/i)
|
|
182
|
+
await user.click(configureMultipleText)
|
|
183
|
+
await sleep(500)
|
|
184
|
+
|
|
185
|
+
// ========================================================================
|
|
186
|
+
// STEP 14: Select "Location" from Multi-Visualization Column dropdown
|
|
187
|
+
// ========================================================================
|
|
188
|
+
await sleep(500)
|
|
189
|
+
|
|
190
|
+
const multiVizColumnSelect = canvas.getByLabelText(/multi-visualization column/i)
|
|
191
|
+
await user.selectOptions(multiVizColumnSelect, 'Location')
|
|
192
|
+
await sleep(500)
|
|
193
|
+
|
|
194
|
+
// ========================================================================
|
|
195
|
+
// STEP 15: Click "Continue" button
|
|
196
|
+
// ========================================================================
|
|
197
|
+
await sleep(500)
|
|
198
|
+
|
|
199
|
+
const continueButton = canvas.getByRole('button', { name: /continue/i })
|
|
200
|
+
await user.click(continueButton)
|
|
201
|
+
await sleep(500)
|
|
202
|
+
}
|
|
203
|
+
}
|