@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.
- package/.claude/agents/qa-test-developer.md +126 -0
- package/CLAUDE.local.md +67 -0
- package/_stories/Gallery.Charts.stories.tsx +300 -0
- package/_stories/Gallery.DataBite.stories.tsx +79 -0
- package/_stories/Gallery.Maps.stories.tsx +239 -0
- package/_stories/Gallery.WaffleChart.stories.tsx +187 -0
- package/_stories/PageART.stories.tsx +193 -0
- package/_stories/PageBRFSS.stories.tsx +294 -0
- package/_stories/PageCancerRegistries.stories.tsx +199 -0
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +216 -0
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +201 -0
- package/_stories/PageMaternalMortality.stories.tsx +193 -0
- package/_stories/PageOralHealth.stories.tsx +201 -0
- package/_stories/PageRespiratory.stories.tsx +332 -0
- package/_stories/PageSmokingTobacco.stories.tsx +200 -0
- package/_stories/PageStateDiabetesProfiles.stories.tsx +201 -0
- package/_stories/PageWastewater.stories.tsx +477 -0
- package/_stories/VegaImport.stories.tsx +401 -0
- package/_stories/vega-fixtures/bars-with-line.json +444 -0
- package/_stories/vega-fixtures/bars.json +58 -0
- package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
- package/_stories/vega-fixtures/combo.json +68 -0
- package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
- package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
- package/_stories/vega-fixtures/horizontal-bar.json +427 -0
- package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
- package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
- package/_stories/vega-fixtures/lines.json +227 -0
- package/_stories/vega-fixtures/measles-bars.json +348 -0
- package/_stories/vega-fixtures/measles-map.json +11101 -0
- package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
- package/_stories/vega-fixtures/multi-dataset.json +255 -0
- package/_stories/vega-fixtures/no-data.json +14 -0
- package/_stories/vega-fixtures/pie-chart.json +94 -0
- package/_stories/vega-fixtures/repeat-spec.json +47 -0
- package/_stories/vega-fixtures/stacked-area.json +222 -0
- package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
- package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
- package/_stories/vega-fixtures/stacked-bars.json +212 -0
- package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
- package/_stories/vega-fixtures/warning-combo.json +59 -0
- package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
- package/assets/icon-chart-area.svg +1 -0
- package/assets/icon-chart-radar.svg +23 -0
- package/assets/icon-magnifying-glass.svg +5 -0
- package/assets/icon-warming-stripes.svg +13 -0
- package/assets/logo2.svg +31 -0
- package/components/AdvancedEditor/AdvancedEditor.tsx +4 -0
- package/components/AdvancedEditor/EmbedEditor.tsx +513 -0
- package/components/ComboBox/ComboBox.tsx +345 -0
- package/components/ComboBox/combobox.styles.css +185 -0
- package/components/ComboBox/index.ts +1 -0
- package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
- package/components/DataTable/DataTable.tsx +132 -58
- package/components/DataTable/data-table.css +216 -215
- package/components/DataTable/helpers/getSeriesName.ts +6 -0
- package/components/DataTable/helpers/mapCellMatrix.tsx +14 -6
- package/components/EditorPanel/ColumnsEditor.tsx +37 -19
- package/components/EditorPanel/DataTableEditor.tsx +51 -25
- package/components/EditorPanel/EditorPanel.styles.css +16 -0
- package/components/EditorPanel/EditorPanel.tsx +144 -0
- package/components/EditorPanel/EditorPanelDispatch.tsx +75 -0
- package/components/EditorPanel/FieldSetWrapper.tsx +66 -23
- package/components/EditorPanel/Inputs.tsx +33 -7
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +14 -6
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +240 -175
- package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
- package/components/EditorPanel/sections/VisualSection.tsx +169 -0
- package/components/Filters/Filters.tsx +31 -5
- package/components/Filters/helpers/getNestedOptions.ts +2 -1
- package/components/Filters/helpers/handleSorting.ts +1 -1
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +84 -2
- package/components/Layout/components/Visualization/index.tsx +27 -1
- package/components/Layout/components/Visualization/visualizations.scss +7 -0
- package/components/Legend/Legend.Gradient.tsx +1 -1
- package/components/MediaControls.tsx +53 -28
- package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
- package/components/_stories/DataTable.stories.tsx +1 -0
- package/components/ui/Icon.tsx +3 -1
- package/components/ui/Title/index.tsx +30 -2
- package/components/ui/Title/title.styles.css +42 -0
- package/data/colorPalettes.ts +18 -5
- package/data/mapColorPalettes.ts +10 -0
- package/devTemplate/dev.js +235 -0
- package/devTemplate/index.html +30 -0
- package/devTemplate/preview.html +1503 -0
- package/devTemplate/sidebar.css +151 -0
- package/dist/cove-main.css +2803 -4448
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +118 -2
- package/helpers/DataTransform.ts +1 -5
- package/helpers/addValuesToFilters.ts +6 -1
- package/helpers/cove/date.ts +33 -1
- package/helpers/cove/string.ts +29 -0
- package/helpers/coveUpdateWorker.ts +21 -12
- package/helpers/embed/embedCodeGenerator.ts +80 -0
- package/helpers/embed/embedHelper.js +158 -0
- package/helpers/embed/filterUtils.ts +121 -0
- package/helpers/embed/index.ts +21 -0
- package/helpers/embed/urlValidation.ts +119 -0
- package/helpers/filterVizData.ts +6 -1
- package/helpers/getFileExtension.ts +0 -6
- package/helpers/getUniqueValues.ts +19 -0
- package/helpers/hashObj.ts +25 -0
- package/helpers/isRightAlignedTableValue.js +5 -0
- package/helpers/metrics/helpers.ts +1 -0
- package/helpers/metrics/types.ts +3 -0
- package/helpers/palettes/colorDistributions.ts +1 -1
- package/helpers/palettes/utils.ts +12 -12
- package/helpers/parseCsvWithQuotes.ts +15 -14
- package/helpers/pivotData.ts +2 -2
- package/helpers/prepareScreenshot.ts +288 -0
- package/helpers/queryStringUtils.ts +29 -0
- package/helpers/testing.ts +44 -0
- package/helpers/tests/DataTransform.test.ts +125 -0
- package/helpers/tests/date.test.ts +64 -0
- package/helpers/tests/prepareScreenshot.test.ts +414 -0
- package/helpers/tests/queryStringUtils.test.ts +381 -0
- package/helpers/tests/testStandaloneBuild.ts +23 -5
- package/helpers/useDataVizClasses.ts +0 -1
- package/helpers/vegaConfig.ts +1 -1
- package/helpers/vegaConfigImport.ts +160 -0
- package/helpers/ver/4.26.1.ts +80 -0
- package/helpers/ver/4.26.2.ts +84 -0
- package/helpers/ver/tests/4.26.1.test.ts +105 -0
- package/helpers/ver/tests/4.26.2.test.ts +298 -0
- package/helpers/viewports.ts +2 -0
- package/hooks/useDataColumns.ts +63 -0
- package/hooks/useFilterManagement.ts +94 -0
- package/hooks/useLegendSeparators.ts +26 -0
- package/hooks/useListManagement.ts +192 -0
- package/package.json +29 -33
- package/styles/_button-section.scss +0 -3
- package/styles/v2/components/editor.scss +9 -9
- package/styles/v2/utils/_grid.scss +8 -3
- package/types/Annotation.ts +10 -11
- package/types/Axis.ts +1 -0
- package/types/ForecastingSeriesKey.ts +1 -0
- package/types/General.ts +2 -0
- package/types/MarkupInclude.ts +1 -0
- package/types/Palette.ts +21 -0
- package/types/Series.ts +3 -0
- package/types/Table.ts +1 -0
- package/types/Visualization.ts +7 -0
- package/types/VizFilter.ts +1 -0
- package/LICENSE +0 -201
- 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
|
+
}
|