@cdc/core 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/_stories/Gallery.Charts.stories.tsx +307 -0
- package/_stories/Gallery.DataBite.stories.tsx +72 -0
- package/_stories/Gallery.Maps.stories.tsx +230 -0
- package/_stories/Gallery.WaffleChart.stories.tsx +187 -0
- package/_stories/PageART.stories.tsx +192 -0
- package/_stories/PageBRFSS.stories.tsx +289 -0
- package/_stories/PageCancerRegistries.stories.tsx +199 -0
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +202 -0
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +196 -0
- package/_stories/PageMaternalMortality.stories.tsx +192 -0
- package/_stories/PageOralHealth.stories.tsx +196 -0
- package/_stories/PageRespiratory.stories.tsx +332 -0
- package/_stories/PageSmokingTobacco.stories.tsx +195 -0
- package/_stories/PageStateDiabetesProfiles.stories.tsx +196 -0
- package/_stories/PageWastewater.stories.tsx +463 -0
- package/_stories/StoryRenderingTests.stories.tsx +164 -0
- package/assets/icon-magnifying-glass.svg +5 -0
- package/assets/icon-warming-stripes.svg +13 -0
- package/components/AdvancedEditor/AdvancedEditor.tsx +7 -1
- package/components/AdvancedEditor/EmbedEditor.tsx +281 -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.css +299 -0
- package/components/CustomColorsEditor/CustomColorsEditor.tsx +209 -0
- package/components/CustomColorsEditor/index.ts +1 -0
- package/components/DataTable/DataTable.tsx +132 -58
- package/components/DataTable/DataTableStandAlone.tsx +8 -3
- package/components/DataTable/components/DataTableEditorPanel.tsx +12 -2
- package/components/DataTable/data-table.css +217 -210
- package/components/DataTable/helpers/mapCellMatrix.tsx +28 -9
- package/components/DataTable/helpers/standardizeState.js +2 -2
- package/components/DataTable/helpers/tests/standardizeState.test.js +54 -0
- package/components/EditorPanel/ColumnsEditor.tsx +37 -19
- package/components/EditorPanel/DataTableEditor.tsx +54 -28
- package/components/EditorPanel/EditorPanel.styles.css +439 -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/FootnotesEditor.tsx +44 -37
- package/components/EditorPanel/Inputs.tsx +44 -8
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +35 -62
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +246 -175
- package/components/EditorPanel/components/MarkupVariablesEditor.tsx +61 -22
- package/components/EditorPanel/sections/VisualSection.tsx +169 -0
- package/components/Filters/Filters.tsx +57 -10
- package/components/Filters/components/Dropdown.tsx +6 -1
- package/components/Filters/helpers/getNestedOptions.ts +2 -1
- package/components/Filters/helpers/handleSorting.ts +1 -1
- package/components/Footnotes/Footnotes.tsx +35 -25
- package/components/Footnotes/FootnotesStandAlone.tsx +42 -6
- package/components/HeaderThemeSelector/HeaderThemeSelector.css +43 -0
- package/components/HeaderThemeSelector/HeaderThemeSelector.stories.tsx +74 -0
- package/components/HeaderThemeSelector/HeaderThemeSelector.tsx +61 -0
- package/components/HeaderThemeSelector/index.ts +2 -0
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +82 -0
- package/components/Layout/components/Visualization/index.tsx +16 -1
- package/components/Layout/components/Visualization/visualizations.scss +7 -0
- package/components/Layout/styles/editor.scss +2 -1
- package/components/Legend/Legend.Gradient.tsx +1 -1
- package/components/Loader/Loader.tsx +1 -1
- package/components/MediaControls.tsx +63 -34
- package/components/PaletteConversionModal.tsx +7 -4
- package/components/PaletteSelector/PaletteSelector.css +49 -6
- package/components/Table/components/Cell.tsx +23 -2
- package/components/Table/components/Row.tsx +5 -3
- package/components/_stories/Filters.stories.tsx +20 -1
- package/components/_stories/Footnotes.CSV.stories.tsx +247 -0
- package/components/_stories/Footnotes.stories.tsx +768 -3
- package/components/_stories/Inputs.stories.tsx +2 -2
- package/components/_stories/styles.scss +0 -1
- package/components/ui/Accordion.jsx +1 -1
- 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/components/ui/accordion.styles.css +57 -0
- package/data/chartColorPalettes.ts +1 -1
- package/dist/cove-main.css +75 -6
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +8 -1
- package/helpers/addValuesToFilters.ts +11 -1
- package/helpers/constants.ts +37 -0
- package/helpers/cove/number.ts +33 -12
- package/helpers/coveUpdateWorker.ts +20 -11
- package/helpers/embedCodeGenerator.ts +109 -0
- package/helpers/fetchRemoteData.ts +3 -15
- package/helpers/getUniqueValues.ts +19 -0
- package/helpers/hashObj.ts +25 -0
- package/helpers/isRightAlignedTableValue.js +5 -0
- package/helpers/markupProcessor.ts +27 -12
- package/helpers/mergeCustomOrderValues.ts +37 -0
- package/helpers/metrics/helpers.ts +1 -0
- package/helpers/parseCsvWithQuotes.ts +65 -0
- package/helpers/pivotData.ts +2 -2
- package/helpers/prepareScreenshot.ts +268 -0
- package/helpers/queryStringUtils.ts +29 -0
- package/helpers/testing.ts +17 -4
- 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/ver/4.25.11.ts +13 -0
- package/helpers/ver/4.26.1.ts +80 -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 +6 -4
- package/styles/_button-section.scss +0 -3
- package/styles/_common-components.css +73 -0
- package/styles/_global.scss +25 -5
- package/styles/base.scss +0 -50
- package/styles/cove-main.scss +3 -1
- package/styles/filters.scss +10 -3
- package/styles/v2/base/index.scss +0 -1
- package/styles/v2/components/editor.scss +14 -6
- package/styles/v2/utils/_breakpoints.scss +1 -1
- package/styles/v2/utils/index.scss +0 -1
- package/styles/waiting.scss +1 -1
- package/types/Axis.ts +1 -0
- package/types/ForecastingSeriesKey.ts +1 -0
- package/types/MarkupInclude.ts +5 -3
- package/types/MarkupVariable.ts +1 -1
- package/types/Series.ts +3 -0
- package/types/Table.ts +1 -0
- package/types/Visualization.ts +1 -0
- package/types/VizFilter.ts +2 -0
- package/LICENSE +0 -201
- package/styles/_mixins.scss +0 -13
- package/styles/_typography.scss +0 -0
- package/styles/v2/base/_typography.scss +0 -0
- package/styles/v2/components/guidance-block.scss +0 -74
- package/styles/v2/utils/_functions.scss +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
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/Maternal Mortality',
|
|
15
|
+
parameters: {
|
|
16
|
+
layout: 'fullscreen',
|
|
17
|
+
docs: {
|
|
18
|
+
description: {
|
|
19
|
+
component: 'Stories for visualizations from the CDC Pregnancy Mortality Surveillance System (PMSS) page (https://www.cdc.gov/maternal-mortality/php/pregnancy-mortality-surveillance-data/index.html)'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
tags: ['autodocs']
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default meta
|
|
27
|
+
|
|
28
|
+
// Config URL from the maternal mortality surveillance data page
|
|
29
|
+
const CONFIG_URLS = {
|
|
30
|
+
pmssDashboard: 'https://www.cdc.gov/maternal-mortality/dfe-module/data-mmrc/pmss-data-dashboard.json'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Helper to fetch config and update data URLs to use absolute cdc.gov paths
|
|
34
|
+
const useConfigWithAbsoluteDataUrl = (configUrl: string) => {
|
|
35
|
+
const [config, setConfig] = useState(null)
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
fetch(configUrl)
|
|
39
|
+
.then(res => res.json())
|
|
40
|
+
.then(data => {
|
|
41
|
+
// Convert relative data URLs to absolute cdc.gov URLs
|
|
42
|
+
if (data.dataUrl) {
|
|
43
|
+
// Handle different relative path formats (../../path or /path)
|
|
44
|
+
const dataUrl = data.dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
45
|
+
data.dataUrl = `https://www.cdc.gov/${dataUrl}`
|
|
46
|
+
}
|
|
47
|
+
if (data.dataFileName) {
|
|
48
|
+
const dataFileName = data.dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
49
|
+
data.dataFileName = `https://www.cdc.gov/${dataFileName}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// For dashboard configs with multiDashboards, convert dataKey references in visualizations
|
|
53
|
+
if (data.multiDashboards) {
|
|
54
|
+
data.multiDashboards.forEach((dashboard: any) => {
|
|
55
|
+
if (dashboard.visualizations) {
|
|
56
|
+
Object.values(dashboard.visualizations).forEach((viz: any) => {
|
|
57
|
+
// Only convert dataKey if it's a URL path (starts with / or ../)
|
|
58
|
+
if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
|
|
59
|
+
const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
60
|
+
viz.dataKey = `https://www.cdc.gov/${dataKey}`
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// For dashboard configs, convert dataKey references in visualizations
|
|
68
|
+
if (data.visualizations) {
|
|
69
|
+
Object.values(data.visualizations).forEach((viz: any) => {
|
|
70
|
+
// Only convert dataKey if it's a URL path (starts with / or ../)
|
|
71
|
+
if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
|
|
72
|
+
const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
73
|
+
viz.dataKey = `https://www.cdc.gov/${dataKey}`
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// For dashboard configs, convert datasets only if they reference external files
|
|
79
|
+
if (data.datasets) {
|
|
80
|
+
const newDatasets = {}
|
|
81
|
+
Object.entries(data.datasets).forEach(([key, dataset]: [string, any]) => {
|
|
82
|
+
// Check if dataset has embedded data
|
|
83
|
+
const hasEmbeddedData = (dataset as any).data && Array.isArray((dataset as any).data)
|
|
84
|
+
|
|
85
|
+
// If data is embedded, keep the original key
|
|
86
|
+
if (hasEmbeddedData) {
|
|
87
|
+
newDatasets[key] = dataset
|
|
88
|
+
} else {
|
|
89
|
+
// Otherwise, convert paths to absolute URLs
|
|
90
|
+
const newKey = key.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
91
|
+
const absoluteKey = `https://www.cdc.gov/${newKey}`
|
|
92
|
+
|
|
93
|
+
newDatasets[absoluteKey] = {
|
|
94
|
+
...dataset,
|
|
95
|
+
dataFileName: (dataset as any).dataFileName
|
|
96
|
+
? `https://www.cdc.gov/${(dataset as any).dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
|
|
97
|
+
: (dataset as any).dataFileName,
|
|
98
|
+
dataUrl: (dataset as any).dataUrl
|
|
99
|
+
? `https://www.cdc.gov/${(dataset as any).dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
|
|
100
|
+
: (dataset as any).dataUrl
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
data.datasets = newDatasets
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Set activeDashboard to 0 if it's null and multiDashboards exist
|
|
108
|
+
if (data.multiDashboards && data.multiDashboards.length > 0 && data.activeDashboard === null) {
|
|
109
|
+
data.activeDashboard = 0
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Log config info for debugging
|
|
113
|
+
console.log('✓ Config loaded:', {
|
|
114
|
+
hasMultiDashboards: !!data.multiDashboards,
|
|
115
|
+
dashboardCount: data.multiDashboards?.length || 0,
|
|
116
|
+
activeDashboard: data.activeDashboard,
|
|
117
|
+
datasetCount: Object.keys(data.datasets || {}).length,
|
|
118
|
+
firstDashboardVizCount: data.multiDashboards?.[0]?.visualizations
|
|
119
|
+
? Object.keys(data.multiDashboards[0].visualizations).length
|
|
120
|
+
: 0
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
setConfig(data)
|
|
124
|
+
})
|
|
125
|
+
.catch(err => {
|
|
126
|
+
console.error('Failed to fetch config:', configUrl, err)
|
|
127
|
+
})
|
|
128
|
+
}, [configUrl])
|
|
129
|
+
|
|
130
|
+
return config
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
type DashboardStory = StoryObj<typeof Dashboard>
|
|
134
|
+
|
|
135
|
+
// Helper function to test dashboard rendering
|
|
136
|
+
const testDashboardRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
137
|
+
await step('Wait for dashboard to render', async () => {
|
|
138
|
+
await new Promise<void>((resolve, reject) => {
|
|
139
|
+
const startTime = Date.now()
|
|
140
|
+
const timeout = 20000
|
|
141
|
+
|
|
142
|
+
const checkDashboard = () => {
|
|
143
|
+
const dashboardElement = canvasElement.querySelector('.cove-dashboard')
|
|
144
|
+
const loadingDiv = canvasElement.querySelector('div')
|
|
145
|
+
|
|
146
|
+
// Log current state for debugging
|
|
147
|
+
if (!dashboardElement && loadingDiv?.textContent?.includes('Loading')) {
|
|
148
|
+
console.log('Still loading config...')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (dashboardElement) {
|
|
152
|
+
resolve()
|
|
153
|
+
} else if (Date.now() - startTime > timeout) {
|
|
154
|
+
reject(new Error(`Timeout: Dashboard element not found after ${timeout}ms`))
|
|
155
|
+
} else {
|
|
156
|
+
setTimeout(checkDashboard, 100)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
checkDashboard()
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
await step('Verify dashboard wrapper is present', async () => {
|
|
164
|
+
const dashboard = canvasElement.querySelector('.cove-dashboard')
|
|
165
|
+
expect(dashboard).toBeInTheDocument()
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
await step('Verify at least one visualization rendered', async () => {
|
|
169
|
+
const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
|
|
170
|
+
expect(coveModules.length).toBeGreaterThan(0)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
console.log(` ${storyName} dashboard rendered successfully`)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* PMSS Data Dashboard
|
|
178
|
+
*
|
|
179
|
+
* Interactive dashboard showing Pregnancy Mortality Surveillance System (PMSS) data,
|
|
180
|
+
* including pregnancy-related mortality ratios, trends over time, and demographic breakdowns.
|
|
181
|
+
* This surveillance system monitors pregnancy-related deaths in the United States.
|
|
182
|
+
*/
|
|
183
|
+
export const PMSS_Dashboard: DashboardStory = {
|
|
184
|
+
render: () => {
|
|
185
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.pmssDashboard)
|
|
186
|
+
if (!config) return <div>Loading...</div>
|
|
187
|
+
return <Dashboard config={config} />
|
|
188
|
+
},
|
|
189
|
+
play: async ({ canvasElement }) => {
|
|
190
|
+
await testDashboardRendering(canvasElement, 'PMSS Dashboard')
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
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/Oral Health',
|
|
15
|
+
parameters: {
|
|
16
|
+
layout: 'fullscreen',
|
|
17
|
+
docs: {
|
|
18
|
+
description: {
|
|
19
|
+
component: 'Stories for visualizations from the CDC Oral Health Data page (https://www.cdc.gov/oral-health-data-systems/oral_health_data/oral-health-dashboard.html)'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
tags: ['autodocs']
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default meta
|
|
27
|
+
|
|
28
|
+
// Config URL from the oral health data page
|
|
29
|
+
const CONFIG_URLS = {
|
|
30
|
+
oralHealthData: 'https://www.cdc.gov/oral-health-data-systems/oral_health_data/modules/oral-health-data.json'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Helper to fetch config and update data URLs to use absolute cdc.gov paths
|
|
34
|
+
const useConfigWithAbsoluteDataUrl = (configUrl: string) => {
|
|
35
|
+
const [config, setConfig] = useState(null)
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
fetch(configUrl)
|
|
39
|
+
.then(res => res.json())
|
|
40
|
+
.then(data => {
|
|
41
|
+
// Convert relative data URLs to absolute cdc.gov URLs
|
|
42
|
+
if (data.dataUrl) {
|
|
43
|
+
// Handle different relative path formats (../../path or /path)
|
|
44
|
+
const dataUrl = data.dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
45
|
+
data.dataUrl = `https://www.cdc.gov/${dataUrl}`
|
|
46
|
+
}
|
|
47
|
+
if (data.dataFileName) {
|
|
48
|
+
const dataFileName = data.dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
49
|
+
data.dataFileName = `https://www.cdc.gov/${dataFileName}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// For dashboard configs with multiDashboards, convert dataKey references in visualizations
|
|
53
|
+
if (data.multiDashboards) {
|
|
54
|
+
data.multiDashboards.forEach((dashboard: any) => {
|
|
55
|
+
if (dashboard.visualizations) {
|
|
56
|
+
Object.values(dashboard.visualizations).forEach((viz: any) => {
|
|
57
|
+
// Only convert dataKey if it's a URL path (starts with / or ../)
|
|
58
|
+
if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
|
|
59
|
+
const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
60
|
+
viz.dataKey = `https://www.cdc.gov/${dataKey}`
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// For dashboard configs, convert dataKey references in visualizations
|
|
68
|
+
if (data.visualizations) {
|
|
69
|
+
Object.values(data.visualizations).forEach((viz: any) => {
|
|
70
|
+
// Only convert dataKey if it's a URL path (starts with / or ../)
|
|
71
|
+
if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
|
|
72
|
+
const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
73
|
+
viz.dataKey = `https://www.cdc.gov/${dataKey}`
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// For dashboard configs, convert datasets only if they reference external files
|
|
79
|
+
if (data.datasets) {
|
|
80
|
+
const newDatasets = {}
|
|
81
|
+
Object.entries(data.datasets).forEach(([key, dataset]: [string, any]) => {
|
|
82
|
+
// Check if dataset has embedded data
|
|
83
|
+
const hasEmbeddedData = (dataset as any).data && Array.isArray((dataset as any).data)
|
|
84
|
+
|
|
85
|
+
// If data is embedded, keep the original key
|
|
86
|
+
if (hasEmbeddedData) {
|
|
87
|
+
newDatasets[key] = dataset
|
|
88
|
+
} else {
|
|
89
|
+
// Otherwise, convert paths to absolute URLs (but keep absolute URLs as-is)
|
|
90
|
+
const newKey = key.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
91
|
+
const absoluteKey = key.startsWith('http') ? key : `https://www.cdc.gov/${newKey}`
|
|
92
|
+
|
|
93
|
+
newDatasets[absoluteKey] = {
|
|
94
|
+
...dataset,
|
|
95
|
+
dataFileName: (dataset as any).dataFileName && !(dataset as any).dataFileName.startsWith('http')
|
|
96
|
+
? `https://www.cdc.gov/${(dataset as any).dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
|
|
97
|
+
: (dataset as any).dataFileName,
|
|
98
|
+
dataUrl: (dataset as any).dataUrl && !(dataset as any).dataUrl.startsWith('http')
|
|
99
|
+
? `https://www.cdc.gov/${(dataset as any).dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
|
|
100
|
+
: (dataset as any).dataUrl
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
data.datasets = newDatasets
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Set activeDashboard to 0 if it's null and multiDashboards exist
|
|
108
|
+
if (data.multiDashboards && data.multiDashboards.length > 0 && data.activeDashboard === null) {
|
|
109
|
+
data.activeDashboard = 0
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Log config info for debugging
|
|
113
|
+
console.log('✓ Config loaded:', {
|
|
114
|
+
type: data.type,
|
|
115
|
+
hasMultiDashboards: !!data.multiDashboards,
|
|
116
|
+
dashboardCount: data.multiDashboards?.length || 0,
|
|
117
|
+
activeDashboard: data.activeDashboard,
|
|
118
|
+
datasetCount: Object.keys(data.datasets || {}).length,
|
|
119
|
+
dashboardLabels: data.multiDashboards?.map((d: any) => d.label)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
setConfig(data)
|
|
123
|
+
})
|
|
124
|
+
.catch(err => {
|
|
125
|
+
console.error('Failed to fetch config:', configUrl, err)
|
|
126
|
+
})
|
|
127
|
+
}, [configUrl])
|
|
128
|
+
|
|
129
|
+
return config
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
type DashboardStory = StoryObj<typeof Dashboard>
|
|
133
|
+
|
|
134
|
+
// Helper function to test dashboard rendering
|
|
135
|
+
const testDashboardRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
136
|
+
await step('Wait for dashboard to render', async () => {
|
|
137
|
+
await new Promise<void>((resolve, reject) => {
|
|
138
|
+
const startTime = Date.now()
|
|
139
|
+
const timeout = 30000 // Longer timeout for external data loading
|
|
140
|
+
|
|
141
|
+
const checkDashboard = () => {
|
|
142
|
+
const dashboardElement = canvasElement.querySelector('.cove-dashboard')
|
|
143
|
+
const loadingDiv = canvasElement.querySelector('div')
|
|
144
|
+
|
|
145
|
+
// Log current state for debugging
|
|
146
|
+
if (!dashboardElement && loadingDiv?.textContent?.includes('Loading')) {
|
|
147
|
+
console.log('Still loading config...')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (dashboardElement) {
|
|
151
|
+
resolve()
|
|
152
|
+
} else if (Date.now() - startTime > timeout) {
|
|
153
|
+
reject(new Error(`Timeout: Dashboard element not found after ${timeout}ms`))
|
|
154
|
+
} else {
|
|
155
|
+
setTimeout(checkDashboard, 100)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
checkDashboard()
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
await step('Verify dashboard wrapper is present', async () => {
|
|
163
|
+
const dashboard = canvasElement.querySelector('.cove-dashboard')
|
|
164
|
+
expect(dashboard).toBeInTheDocument()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
await step('Verify at least one visualization rendered', async () => {
|
|
168
|
+
const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
|
|
169
|
+
expect(coveModules.length).toBeGreaterThan(0)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
console.log(` ${storyName} dashboard rendered successfully`)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Oral Health Data Dashboard
|
|
177
|
+
*
|
|
178
|
+
* Interactive dashboard showing oral health surveillance data from the National Oral Health
|
|
179
|
+
* Surveillance System (NOHSS). The dashboard includes 4 views:
|
|
180
|
+
* - All States: State-level oral health data across all U.S. states
|
|
181
|
+
* - Single State: Detailed data for a selected state
|
|
182
|
+
* - All Counties: County-level oral health data
|
|
183
|
+
* - Single County: Detailed data for a selected county
|
|
184
|
+
*
|
|
185
|
+
* Data includes oral health indicators, intervention strategies, and public health programs.
|
|
186
|
+
*/
|
|
187
|
+
export const Oral_Health_Dashboard: DashboardStory = {
|
|
188
|
+
render: () => {
|
|
189
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.oralHealthData)
|
|
190
|
+
if (!config) return <div>Loading...</div>
|
|
191
|
+
return <Dashboard config={config} />
|
|
192
|
+
},
|
|
193
|
+
play: async ({ canvasElement }) => {
|
|
194
|
+
await testDashboardRendering(canvasElement, 'Oral Health Dashboard')
|
|
195
|
+
}
|
|
196
|
+
}
|