@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.
Files changed (86) hide show
  1. package/Dynamic_Data.md +66 -0
  2. package/dist/{cdcdashboard-fce76882.es.js → cdcdashboard-BnB1QM5d.es.js} +6 -13
  3. package/dist/{cdcdashboard-c55ac1ea.es.js → cdcdashboard-D6CG2-Hb.es.js} +5 -12
  4. package/dist/{cdcdashboard-31a33da1.es.js → cdcdashboard-MXgURbdZ.es.js} +6 -13
  5. package/dist/{cdcdashboard-1a1724a1.es.js → cdcdashboard-dgT_1dIT.es.js} +136 -151
  6. package/dist/cdcdashboard.js +84214 -79641
  7. package/examples/api-dashboard-data.json +272 -0
  8. package/examples/api-dashboard-years.json +11 -0
  9. package/examples/api-geographies-data.json +11 -0
  10. package/examples/api-test/categories.json +18 -0
  11. package/examples/api-test/chart-data.json +602 -0
  12. package/examples/api-test/topics.json +47 -0
  13. package/examples/api-test/years.json +22 -0
  14. package/examples/markup-axis-label.json +4167 -0
  15. package/examples/private/big-dashboard.json +39095 -39077
  16. package/examples/private/cat-y.json +1235 -0
  17. package/examples/private/chronic-dash.json +1584 -0
  18. package/examples/private/clade-2.json +430 -0
  19. package/examples/private/diabetes.json +546 -196
  20. package/examples/private/map-issue.json +2260 -0
  21. package/examples/private/markup-footer/mortality-deaths-footnotes-age.csv +3 -0
  22. package/examples/private/mpinc-state-reports.json +2260 -0
  23. package/examples/private/mpox.json +38128 -0
  24. package/examples/private/nwss/rsv.json +1240 -0
  25. package/examples/private/reset.json +32920 -0
  26. package/examples/private/simple-dash.json +490 -0
  27. package/examples/private/test-dash.json +0 -0
  28. package/examples/private/test123.json +491 -0
  29. package/examples/test-api-filter-reset.json +132 -0
  30. package/examples/test-dashboard-simple.json +503 -0
  31. package/index.html +25 -26
  32. package/package.json +11 -11
  33. package/src/CdcDashboardComponent.tsx +35 -10
  34. package/src/DashboardContext.tsx +3 -1
  35. package/src/_stories/Dashboard.DataSetup.stories.tsx +203 -0
  36. package/src/_stories/Dashboard.stories.tsx +402 -1
  37. package/src/_stories/_mock/custom-order-new-values.json +116 -0
  38. package/src/_stories/_mock/filter-cascade.json +3350 -0
  39. package/src/_stories/_mock/gallery-data-bite-dashboard.json +3500 -0
  40. package/src/_stories/_mock/nested-parent-child-filters.json +392 -0
  41. package/src/_stories/_mock/parent-child-filters.json +233 -0
  42. package/src/components/DashboardFilters/DashboardFilters.tsx +54 -31
  43. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +118 -50
  44. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +96 -108
  45. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +196 -59
  46. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +129 -29
  47. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +62 -3
  48. package/src/components/DataDesignerModal.tsx +18 -6
  49. package/src/components/Header/Header.tsx +53 -21
  50. package/src/components/Toggle/Toggle.tsx +48 -48
  51. package/src/components/VisualizationRow.tsx +73 -6
  52. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +2 -3
  53. package/src/components/Widget/Widget.tsx +1 -1
  54. package/src/data/initial-state.js +1 -0
  55. package/src/helpers/addValuesToDashboardFilters.ts +24 -6
  56. package/src/helpers/apiFilterHelpers.ts +26 -2
  57. package/src/helpers/changeFilterActive.ts +67 -65
  58. package/src/helpers/filterData.ts +52 -7
  59. package/src/helpers/filterResetHelpers.ts +102 -0
  60. package/src/helpers/formatConfigBeforeSave.ts +6 -5
  61. package/src/helpers/getUpdateConfig.ts +91 -91
  62. package/src/helpers/getVizConfig.ts +2 -2
  63. package/src/helpers/loadAPIFilters.ts +109 -99
  64. package/src/helpers/tests/filterResetHelpers.test.ts +532 -0
  65. package/src/helpers/tests/updatesChildFilters.test.ts +53 -22
  66. package/src/helpers/updateChildFilters.ts +50 -27
  67. package/src/index.tsx +1 -0
  68. package/src/scss/editor-panel.scss +3 -431
  69. package/src/scss/main.scss +142 -25
  70. package/src/store/errorMessage/errorMessage.reducer.ts +1 -1
  71. package/src/test/CdcDashboard.test.jsx +9 -4
  72. package/src/types/Dashboard.ts +1 -0
  73. package/src/types/DashboardFilters.ts +9 -8
  74. package/src/types/FilterStyles.ts +8 -7
  75. package/src/types/SharedFilter.ts +13 -0
  76. package/LICENSE +0 -201
  77. package/examples/private/DEV-11072.json +0 -7591
  78. package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_data.csv +0 -14041
  79. package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_per_100000_data.csv +0 -14041
  80. package/examples/private/burden_toolkit_mortality_qaly_data.csv +0 -18721
  81. package/examples/private/burden_toolkit_mortality_yll_data.csv +0 -18721
  82. package/examples/private/pedro.json +0 -1
  83. package/src/helpers/getAutoLoadVisualization.ts +0 -11
  84. package/src/scss/mixins.scss +0 -47
  85. package/src/scss/variables.scss +0 -5
  86. /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/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
- updatedQSParams[filter.apiFilter.valueSelector] = filter.active
169
- if (filter.apiFilter.subgroupValueSelector && filter.subGrouping.active) {
170
- updatedQSParams[filter.apiFilter.subgroupValueSelector] = filter.subGrouping.active
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
- const sharedFiltersWithValues = addValuesToDashboardFilters(config.dashboard.sharedFilters, state.data)
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-3'>{parse(description)}</div>}
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>
@@ -15,7 +15,9 @@ type ConfigCTX = DashboardState & {
15
15
  loadAPIFilters: (
16
16
  sharedFilters: SharedFilter[],
17
17
  dropdowns: APIFilterDropdowns,
18
- recursiveLimit?: number
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
+ }