@cdc/core 4.25.11 → 4.26.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.
Files changed (147) hide show
  1. package/.claude/agents/qa-test-developer.md +126 -0
  2. package/CLAUDE.local.md +67 -0
  3. package/_stories/Gallery.Charts.stories.tsx +300 -0
  4. package/_stories/Gallery.DataBite.stories.tsx +79 -0
  5. package/_stories/Gallery.Maps.stories.tsx +239 -0
  6. package/_stories/Gallery.WaffleChart.stories.tsx +187 -0
  7. package/_stories/PageART.stories.tsx +193 -0
  8. package/_stories/PageBRFSS.stories.tsx +294 -0
  9. package/_stories/PageCancerRegistries.stories.tsx +199 -0
  10. package/_stories/PageEasternEquineEncephalitis.stories.tsx +216 -0
  11. package/_stories/PageExcessiveAlcoholUse.stories.tsx +201 -0
  12. package/_stories/PageMaternalMortality.stories.tsx +193 -0
  13. package/_stories/PageOralHealth.stories.tsx +201 -0
  14. package/_stories/PageRespiratory.stories.tsx +332 -0
  15. package/_stories/PageSmokingTobacco.stories.tsx +200 -0
  16. package/_stories/PageStateDiabetesProfiles.stories.tsx +201 -0
  17. package/_stories/PageWastewater.stories.tsx +477 -0
  18. package/_stories/VegaImport.stories.tsx +401 -0
  19. package/_stories/vega-fixtures/bars-with-line.json +444 -0
  20. package/_stories/vega-fixtures/bars.json +58 -0
  21. package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
  22. package/_stories/vega-fixtures/combo.json +68 -0
  23. package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
  24. package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
  25. package/_stories/vega-fixtures/horizontal-bar.json +427 -0
  26. package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
  27. package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
  28. package/_stories/vega-fixtures/lines.json +227 -0
  29. package/_stories/vega-fixtures/measles-bars.json +348 -0
  30. package/_stories/vega-fixtures/measles-map.json +11101 -0
  31. package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
  32. package/_stories/vega-fixtures/multi-dataset.json +255 -0
  33. package/_stories/vega-fixtures/no-data.json +14 -0
  34. package/_stories/vega-fixtures/pie-chart.json +94 -0
  35. package/_stories/vega-fixtures/repeat-spec.json +47 -0
  36. package/_stories/vega-fixtures/stacked-area.json +222 -0
  37. package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
  38. package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
  39. package/_stories/vega-fixtures/stacked-bars.json +212 -0
  40. package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
  41. package/_stories/vega-fixtures/warning-combo.json +59 -0
  42. package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
  43. package/assets/icon-chart-area.svg +1 -0
  44. package/assets/icon-chart-radar.svg +23 -0
  45. package/assets/icon-magnifying-glass.svg +5 -0
  46. package/assets/icon-warming-stripes.svg +13 -0
  47. package/assets/logo2.svg +31 -0
  48. package/components/AdvancedEditor/AdvancedEditor.tsx +4 -0
  49. package/components/AdvancedEditor/EmbedEditor.tsx +513 -0
  50. package/components/ComboBox/ComboBox.tsx +345 -0
  51. package/components/ComboBox/combobox.styles.css +185 -0
  52. package/components/ComboBox/index.ts +1 -0
  53. package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
  54. package/components/DataTable/DataTable.tsx +132 -58
  55. package/components/DataTable/data-table.css +216 -215
  56. package/components/DataTable/helpers/getSeriesName.ts +6 -0
  57. package/components/DataTable/helpers/mapCellMatrix.tsx +14 -6
  58. package/components/EditorPanel/ColumnsEditor.tsx +37 -19
  59. package/components/EditorPanel/DataTableEditor.tsx +51 -25
  60. package/components/EditorPanel/EditorPanel.styles.css +16 -0
  61. package/components/EditorPanel/EditorPanel.tsx +144 -0
  62. package/components/EditorPanel/EditorPanelDispatch.tsx +75 -0
  63. package/components/EditorPanel/FieldSetWrapper.tsx +66 -23
  64. package/components/EditorPanel/Inputs.tsx +33 -7
  65. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +14 -6
  66. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +240 -175
  67. package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
  68. package/components/EditorPanel/sections/VisualSection.tsx +169 -0
  69. package/components/Filters/Filters.tsx +31 -5
  70. package/components/Filters/helpers/getNestedOptions.ts +2 -1
  71. package/components/Filters/helpers/handleSorting.ts +1 -1
  72. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +84 -2
  73. package/components/Layout/components/Visualization/index.tsx +27 -1
  74. package/components/Layout/components/Visualization/visualizations.scss +7 -0
  75. package/components/Legend/Legend.Gradient.tsx +1 -1
  76. package/components/MediaControls.tsx +53 -28
  77. package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
  78. package/components/_stories/DataTable.stories.tsx +1 -0
  79. package/components/ui/Icon.tsx +3 -1
  80. package/components/ui/Title/index.tsx +30 -2
  81. package/components/ui/Title/title.styles.css +42 -0
  82. package/data/colorPalettes.ts +18 -5
  83. package/data/mapColorPalettes.ts +10 -0
  84. package/devTemplate/dev.js +235 -0
  85. package/devTemplate/index.html +30 -0
  86. package/devTemplate/preview.html +1503 -0
  87. package/devTemplate/sidebar.css +151 -0
  88. package/dist/cove-main.css +2803 -4448
  89. package/dist/cove-main.css.map +1 -1
  90. package/generateViteConfig.js +118 -2
  91. package/helpers/DataTransform.ts +1 -5
  92. package/helpers/addValuesToFilters.ts +6 -1
  93. package/helpers/cove/date.ts +33 -1
  94. package/helpers/cove/string.ts +29 -0
  95. package/helpers/coveUpdateWorker.ts +21 -12
  96. package/helpers/embed/embedCodeGenerator.ts +80 -0
  97. package/helpers/embed/embedHelper.js +158 -0
  98. package/helpers/embed/filterUtils.ts +121 -0
  99. package/helpers/embed/index.ts +21 -0
  100. package/helpers/embed/urlValidation.ts +119 -0
  101. package/helpers/filterVizData.ts +6 -1
  102. package/helpers/getFileExtension.ts +0 -6
  103. package/helpers/getUniqueValues.ts +19 -0
  104. package/helpers/hashObj.ts +25 -0
  105. package/helpers/isRightAlignedTableValue.js +5 -0
  106. package/helpers/metrics/helpers.ts +1 -0
  107. package/helpers/metrics/types.ts +3 -0
  108. package/helpers/palettes/colorDistributions.ts +1 -1
  109. package/helpers/palettes/utils.ts +12 -12
  110. package/helpers/parseCsvWithQuotes.ts +15 -14
  111. package/helpers/pivotData.ts +2 -2
  112. package/helpers/prepareScreenshot.ts +288 -0
  113. package/helpers/queryStringUtils.ts +29 -0
  114. package/helpers/testing.ts +44 -0
  115. package/helpers/tests/DataTransform.test.ts +125 -0
  116. package/helpers/tests/date.test.ts +64 -0
  117. package/helpers/tests/prepareScreenshot.test.ts +414 -0
  118. package/helpers/tests/queryStringUtils.test.ts +381 -0
  119. package/helpers/tests/testStandaloneBuild.ts +23 -5
  120. package/helpers/useDataVizClasses.ts +0 -1
  121. package/helpers/vegaConfig.ts +1 -1
  122. package/helpers/vegaConfigImport.ts +160 -0
  123. package/helpers/ver/4.26.1.ts +80 -0
  124. package/helpers/ver/4.26.2.ts +84 -0
  125. package/helpers/ver/tests/4.26.1.test.ts +105 -0
  126. package/helpers/ver/tests/4.26.2.test.ts +298 -0
  127. package/helpers/viewports.ts +2 -0
  128. package/hooks/useDataColumns.ts +63 -0
  129. package/hooks/useFilterManagement.ts +94 -0
  130. package/hooks/useLegendSeparators.ts +26 -0
  131. package/hooks/useListManagement.ts +192 -0
  132. package/package.json +29 -33
  133. package/styles/_button-section.scss +0 -3
  134. package/styles/v2/components/editor.scss +9 -9
  135. package/styles/v2/utils/_grid.scss +8 -3
  136. package/types/Annotation.ts +10 -11
  137. package/types/Axis.ts +1 -0
  138. package/types/ForecastingSeriesKey.ts +1 -0
  139. package/types/General.ts +2 -0
  140. package/types/MarkupInclude.ts +1 -0
  141. package/types/Palette.ts +21 -0
  142. package/types/Series.ts +3 -0
  143. package/types/Table.ts +1 -0
  144. package/types/Visualization.ts +7 -0
  145. package/types/VizFilter.ts +1 -0
  146. package/LICENSE +0 -201
  147. package/_stories/StoryRenderingTests.stories.tsx +0 -164
@@ -0,0 +1,200 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { within, expect } from 'storybook/test'
3
+ import Dashboard from '@cdc/dashboard'
4
+ import { useEffect, useState } from 'react'
5
+
6
+ // Fallback step function for test descriptions
7
+ const step = async (description: string, fn: () => Promise<void> | void) => {
8
+ console.log(`▶ ${description}`)
9
+ await fn()
10
+ console.log(`✓ ${description}`)
11
+ }
12
+
13
+ const meta: Meta = {
14
+ title: 'Regression Tests/Pages/Smoking and Tobacco',
15
+ parameters: {
16
+ layout: 'fullscreen',
17
+ docs: {
18
+ description: {
19
+ component:
20
+ 'Stories for visualizations from the CDC Global Tobacco Surveillance System (GTSS) Data Explorer page (https://www.cdc.gov/tobacco/global/gtss/data-explorer/index.html)'
21
+ }
22
+ }
23
+ },
24
+ tags: ['autodocs']
25
+ }
26
+
27
+ export default meta
28
+
29
+ // Config URL from the tobacco data explorer page
30
+ const CONFIG_URLS = {
31
+ dataExplorer: 'https://www.cdc.gov/tobacco/global/gtss/data-explorer/data-explorer.json'
32
+ }
33
+
34
+ // Helper to fetch config and update data URLs to use absolute cdc.gov paths
35
+ const useConfigWithAbsoluteDataUrl = (configUrl: string) => {
36
+ const [config, setConfig] = useState(null)
37
+
38
+ useEffect(() => {
39
+ fetch(configUrl)
40
+ .then(res => res.json())
41
+ .then(data => {
42
+ // Convert relative data URLs to absolute cdc.gov URLs
43
+ if (data.dataUrl) {
44
+ // Handle different relative path formats (../../path or /path)
45
+ const dataUrl = data.dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
46
+ data.dataUrl = `https://www.cdc.gov/${dataUrl}`
47
+ }
48
+ if (data.dataFileName) {
49
+ const dataFileName = data.dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
50
+ data.dataFileName = `https://www.cdc.gov/${dataFileName}`
51
+ }
52
+
53
+ // For dashboard configs with multiDashboards, convert dataKey references in visualizations
54
+ if (data.multiDashboards) {
55
+ data.multiDashboards.forEach((dashboard: any) => {
56
+ if (dashboard.visualizations) {
57
+ Object.values(dashboard.visualizations).forEach((viz: any) => {
58
+ // Only convert dataKey if it's a URL path (starts with / or ../)
59
+ if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
60
+ const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
61
+ viz.dataKey = `https://www.cdc.gov/${dataKey}`
62
+ }
63
+ })
64
+ }
65
+ })
66
+ }
67
+
68
+ // For dashboard configs, convert dataKey references in visualizations
69
+ if (data.visualizations) {
70
+ Object.values(data.visualizations).forEach((viz: any) => {
71
+ // Only convert dataKey if it's a URL path (starts with / or ../)
72
+ if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
73
+ const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
74
+ viz.dataKey = `https://www.cdc.gov/${dataKey}`
75
+ }
76
+ })
77
+ }
78
+
79
+ // For dashboard configs, convert datasets only if they reference external files
80
+ if (data.datasets) {
81
+ const newDatasets = {}
82
+ Object.entries(data.datasets).forEach(([key, dataset]: [string, any]) => {
83
+ // Check if dataset has embedded data
84
+ const hasEmbeddedData = (dataset as any).data && Array.isArray((dataset as any).data)
85
+
86
+ // If data is embedded, keep the original key
87
+ if (hasEmbeddedData) {
88
+ newDatasets[key] = dataset
89
+ } else {
90
+ // Otherwise, convert paths to absolute URLs (but keep absolute URLs as-is)
91
+ const newKey = key.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
92
+ const absoluteKey = key.startsWith('http') ? key : `https://www.cdc.gov/${newKey}`
93
+
94
+ newDatasets[absoluteKey] = {
95
+ ...dataset,
96
+ dataFileName:
97
+ (dataset as any).dataFileName && !(dataset as any).dataFileName.startsWith('http')
98
+ ? `https://www.cdc.gov/${(dataset as any).dataFileName
99
+ .replace(/^(\.\.\/)+/, '')
100
+ .replace(/^\//, '')}`
101
+ : (dataset as any).dataFileName,
102
+ dataUrl:
103
+ (dataset as any).dataUrl && !(dataset as any).dataUrl.startsWith('http')
104
+ ? `https://www.cdc.gov/${(dataset as any).dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
105
+ : (dataset as any).dataUrl
106
+ }
107
+ }
108
+ })
109
+ data.datasets = newDatasets
110
+ }
111
+
112
+ // Set activeDashboard to 0 if it's null and multiDashboards exist
113
+ if (data.multiDashboards && data.multiDashboards.length > 0 && data.activeDashboard === null) {
114
+ data.activeDashboard = 0
115
+ }
116
+
117
+ // Log config info for debugging
118
+ console.log('✓ Config loaded:', {
119
+ type: data.type,
120
+ hasMultiDashboards: !!data.multiDashboards,
121
+ dashboardCount: data.multiDashboards?.length || 0,
122
+ activeDashboard: data.activeDashboard,
123
+ datasetCount: Object.keys(data.datasets || {}).length,
124
+ dashboardLabels: data.multiDashboards?.map((d: any) => d.label)
125
+ })
126
+
127
+ setConfig(data)
128
+ })
129
+ .catch(err => {
130
+ console.error('Failed to fetch config:', configUrl, err)
131
+ })
132
+ }, [configUrl])
133
+
134
+ return config
135
+ }
136
+
137
+ type DashboardStory = StoryObj<typeof Dashboard>
138
+
139
+ // Helper function to test dashboard rendering
140
+ const testDashboardRendering = async (canvasElement: HTMLElement, storyName: string) => {
141
+ await step('Wait for dashboard to render', async () => {
142
+ await new Promise<void>((resolve, reject) => {
143
+ const startTime = Date.now()
144
+ const timeout = 30000 // Longer timeout for external data loading
145
+
146
+ const checkDashboard = () => {
147
+ const dashboardElement = canvasElement.querySelector('.type-dashboard')
148
+ const loadingDiv = canvasElement.querySelector('div')
149
+
150
+ // Log current state for debugging
151
+ if (!dashboardElement && loadingDiv?.textContent?.includes('Loading')) {
152
+ console.log('Still loading config...')
153
+ }
154
+
155
+ if (dashboardElement) {
156
+ resolve()
157
+ } else if (Date.now() - startTime > timeout) {
158
+ reject(new Error(`Timeout: Dashboard element not found after ${timeout}ms`))
159
+ } else {
160
+ setTimeout(checkDashboard, 100)
161
+ }
162
+ }
163
+ checkDashboard()
164
+ })
165
+ })
166
+
167
+ await step('Verify dashboard wrapper is present', async () => {
168
+ const dashboard = canvasElement.querySelector('.type-dashboard')
169
+ expect(dashboard).toBeInTheDocument()
170
+ })
171
+
172
+ await step('Verify at least one visualization rendered', async () => {
173
+ const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
174
+ expect(coveModules.length).toBeGreaterThan(0)
175
+ })
176
+
177
+ console.log(` ${storyName} dashboard rendered successfully`)
178
+ }
179
+
180
+ /**
181
+ * GTSS Data Explorer Dashboard
182
+ *
183
+ * Interactive dashboard for exploring Global Tobacco Surveillance System (GTSS) data.
184
+ * The dashboard includes 2 views:
185
+ * - Explore by Location: Browse tobacco use data by geographic location
186
+ * - Explore by Indicator: Browse tobacco use data by specific health indicators
187
+ *
188
+ * GTSS monitors tobacco use among youth and adults globally, tracking prevalence,
189
+ * attitudes, and exposure to tobacco products and smoke.
190
+ */
191
+ export const GTSS_Data_Explorer: DashboardStory = {
192
+ render: () => {
193
+ const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.dataExplorer)
194
+ if (!config) return <div>Loading...</div>
195
+ return <Dashboard config={config} />
196
+ },
197
+ play: async ({ canvasElement }) => {
198
+ await testDashboardRendering(canvasElement, 'GTSS Data Explorer Dashboard')
199
+ }
200
+ }
@@ -0,0 +1,201 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { within, expect } from 'storybook/test'
3
+ import Dashboard from '@cdc/dashboard'
4
+ import { useEffect, useState } from 'react'
5
+
6
+ // Fallback step function for test descriptions
7
+ const step = async (description: string, fn: () => Promise<void> | void) => {
8
+ console.log(`▶ ${description}`)
9
+ await fn()
10
+ console.log(`✓ ${description}`)
11
+ }
12
+
13
+ const meta: Meta = {
14
+ title: 'Regression Tests/Pages/State Diabetes Profiles',
15
+ parameters: {
16
+ layout: 'fullscreen',
17
+ docs: {
18
+ description: {
19
+ component:
20
+ 'Stories for visualizations from the CDC State Diabetes Profiles page (https://www.cdc.gov/diabetes-state-local/php/state-profiles/index.html)'
21
+ }
22
+ }
23
+ },
24
+ tags: ['autodocs']
25
+ }
26
+
27
+ export default meta
28
+
29
+ // Config URL from the state diabetes profiles page
30
+ const CONFIG_URLS = {
31
+ stateDiabetesProfiles: 'https://www.cdc.gov/diabetes-state-local/php/state-profiles/Diabetes-State-Profiles.json'
32
+ }
33
+
34
+ // Helper to fetch config and update data URLs to use absolute cdc.gov paths
35
+ const useConfigWithAbsoluteDataUrl = (configUrl: string) => {
36
+ const [config, setConfig] = useState(null)
37
+
38
+ useEffect(() => {
39
+ fetch(configUrl)
40
+ .then(res => res.json())
41
+ .then(data => {
42
+ // Convert relative data URLs to absolute cdc.gov URLs
43
+ if (data.dataUrl) {
44
+ // Handle different relative path formats (../../path or /path)
45
+ const dataUrl = data.dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
46
+ data.dataUrl = `https://www.cdc.gov/${dataUrl}`
47
+ }
48
+ if (data.dataFileName) {
49
+ const dataFileName = data.dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
50
+ data.dataFileName = `https://www.cdc.gov/${dataFileName}`
51
+ }
52
+
53
+ // For dashboard configs with multiDashboards, convert dataKey references in visualizations
54
+ if (data.multiDashboards) {
55
+ data.multiDashboards.forEach((dashboard: any) => {
56
+ if (dashboard.visualizations) {
57
+ Object.values(dashboard.visualizations).forEach((viz: any) => {
58
+ // Only convert dataKey if it's a URL path (starts with / or ../)
59
+ if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
60
+ const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
61
+ viz.dataKey = `https://www.cdc.gov/${dataKey}`
62
+ }
63
+ })
64
+ }
65
+ })
66
+ }
67
+
68
+ // For dashboard configs, convert dataKey references in visualizations
69
+ if (data.visualizations) {
70
+ Object.values(data.visualizations).forEach((viz: any) => {
71
+ // Only convert dataKey if it's a URL path (starts with / or ../)
72
+ if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
73
+ const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
74
+ viz.dataKey = `https://www.cdc.gov/${dataKey}`
75
+ }
76
+ })
77
+ }
78
+
79
+ // For dashboard configs, convert datasets only if they reference external files
80
+ if (data.datasets) {
81
+ const newDatasets = {}
82
+ Object.entries(data.datasets).forEach(([key, dataset]: [string, any]) => {
83
+ // Check if dataset has embedded data
84
+ const hasEmbeddedData = (dataset as any).data && Array.isArray((dataset as any).data)
85
+
86
+ // If data is embedded, keep the original key
87
+ if (hasEmbeddedData) {
88
+ newDatasets[key] = dataset
89
+ } else {
90
+ // Otherwise, convert paths to absolute URLs (but keep absolute URLs as-is)
91
+ const newKey = key.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
92
+ const absoluteKey = key.startsWith('http') ? key : `https://www.cdc.gov/${newKey}`
93
+
94
+ newDatasets[absoluteKey] = {
95
+ ...dataset,
96
+ dataFileName:
97
+ (dataset as any).dataFileName && !(dataset as any).dataFileName.startsWith('http')
98
+ ? `https://www.cdc.gov/${(dataset as any).dataFileName
99
+ .replace(/^(\.\.\/)+/, '')
100
+ .replace(/^\//, '')}`
101
+ : (dataset as any).dataFileName,
102
+ dataUrl:
103
+ (dataset as any).dataUrl && !(dataset as any).dataUrl.startsWith('http')
104
+ ? `https://www.cdc.gov/${(dataset as any).dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
105
+ : (dataset as any).dataUrl
106
+ }
107
+ }
108
+ })
109
+ data.datasets = newDatasets
110
+ }
111
+
112
+ // Set activeDashboard to 0 if it's null and multiDashboards exist
113
+ if (data.multiDashboards && data.multiDashboards.length > 0 && data.activeDashboard === null) {
114
+ data.activeDashboard = 0
115
+ }
116
+
117
+ // Log config info for debugging
118
+ console.log('✓ Config loaded:', {
119
+ type: data.type,
120
+ hasMultiDashboards: !!data.multiDashboards,
121
+ dashboardCount: data.multiDashboards?.length || 0,
122
+ activeDashboard: data.activeDashboard,
123
+ datasetCount: Object.keys(data.datasets || {}).length,
124
+ dashboardLabels: data.multiDashboards?.map((d: any) => d.label)
125
+ })
126
+
127
+ setConfig(data)
128
+ })
129
+ .catch(err => {
130
+ console.error('Failed to fetch config:', configUrl, err)
131
+ })
132
+ }, [configUrl])
133
+
134
+ return config
135
+ }
136
+
137
+ type DashboardStory = StoryObj<typeof Dashboard>
138
+
139
+ // Helper function to test dashboard rendering
140
+ const testDashboardRendering = async (canvasElement: HTMLElement, storyName: string) => {
141
+ await step('Wait for dashboard to render', async () => {
142
+ await new Promise<void>((resolve, reject) => {
143
+ const startTime = Date.now()
144
+ const timeout = 30000 // Longer timeout for external data loading
145
+
146
+ const checkDashboard = () => {
147
+ const dashboardElement = canvasElement.querySelector('.type-dashboard')
148
+ const loadingDiv = canvasElement.querySelector('div')
149
+
150
+ // Log current state for debugging
151
+ if (!dashboardElement && loadingDiv?.textContent?.includes('Loading')) {
152
+ console.log('Still loading config...')
153
+ }
154
+
155
+ if (dashboardElement) {
156
+ resolve()
157
+ } else if (Date.now() - startTime > timeout) {
158
+ reject(new Error(`Timeout: Dashboard element not found after ${timeout}ms`))
159
+ } else {
160
+ setTimeout(checkDashboard, 100)
161
+ }
162
+ }
163
+ checkDashboard()
164
+ })
165
+ })
166
+
167
+ await step('Verify dashboard wrapper is present', async () => {
168
+ const dashboard = canvasElement.querySelector('.type-dashboard')
169
+ expect(dashboard).toBeInTheDocument()
170
+ })
171
+
172
+ await step('Verify at least one visualization rendered', async () => {
173
+ const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
174
+ expect(coveModules.length).toBeGreaterThan(0)
175
+ })
176
+
177
+ console.log(`✓ ${storyName} dashboard rendered successfully`)
178
+ }
179
+
180
+ /**
181
+ * State Diabetes Profiles Dashboard
182
+ *
183
+ * Interactive dashboard showing the economic impact and burden of diabetes, key program outcomes,
184
+ * and the Division of Diabetes Translation's (DDT) direct funding in all 50 states and Washington, DC.
185
+ *
186
+ * The dashboard displays state-by-state diabetes data including:
187
+ * - DDT Direct Investments by State
188
+ * - Diabetes Burden and Economic Impact
189
+ * - Program Outcomes and Prevention Initiatives
190
+ * - State-specific diabetes statistics and trends
191
+ */
192
+ export const State_Diabetes_Profiles_Dashboard: DashboardStory = {
193
+ render: () => {
194
+ const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.stateDiabetesProfiles)
195
+ if (!config) return <div>Loading...</div>
196
+ return <Dashboard config={config} />
197
+ },
198
+ play: async ({ canvasElement }) => {
199
+ await testDashboardRendering(canvasElement, 'State Diabetes Profiles Dashboard')
200
+ }
201
+ }