@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,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/State Diabetes Profiles',
|
|
15
|
+
parameters: {
|
|
16
|
+
layout: 'fullscreen',
|
|
17
|
+
docs: {
|
|
18
|
+
description: {
|
|
19
|
+
component: 'Stories for visualizations from the CDC State Diabetes Profiles page (https://www.cdc.gov/diabetes-state-local/php/state-profiles/index.html)'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
tags: ['autodocs']
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default meta
|
|
27
|
+
|
|
28
|
+
// Config URL from the state diabetes profiles page
|
|
29
|
+
const CONFIG_URLS = {
|
|
30
|
+
stateDiabetesProfiles: 'https://www.cdc.gov/diabetes-state-local/php/state-profiles/Diabetes-State-Profiles.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
|
+
* State Diabetes Profiles Dashboard
|
|
177
|
+
*
|
|
178
|
+
* Interactive dashboard showing the economic impact and burden of diabetes, key program outcomes,
|
|
179
|
+
* and the Division of Diabetes Translation's (DDT) direct funding in all 50 states and Washington, DC.
|
|
180
|
+
*
|
|
181
|
+
* The dashboard displays state-by-state diabetes data including:
|
|
182
|
+
* - DDT Direct Investments by State
|
|
183
|
+
* - Diabetes Burden and Economic Impact
|
|
184
|
+
* - Program Outcomes and Prevention Initiatives
|
|
185
|
+
* - State-specific diabetes statistics and trends
|
|
186
|
+
*/
|
|
187
|
+
export const State_Diabetes_Profiles_Dashboard: DashboardStory = {
|
|
188
|
+
render: () => {
|
|
189
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.stateDiabetesProfiles)
|
|
190
|
+
if (!config) return <div>Loading...</div>
|
|
191
|
+
return <Dashboard config={config} />
|
|
192
|
+
},
|
|
193
|
+
play: async ({ canvasElement }) => {
|
|
194
|
+
await testDashboardRendering(canvasElement, 'State Diabetes Profiles Dashboard')
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { within, expect } from 'storybook/test'
|
|
3
|
+
import Chart from '@cdc/chart'
|
|
4
|
+
import CdcMap from '@cdc/map'
|
|
5
|
+
import Dashboard from '@cdc/dashboard'
|
|
6
|
+
import { useEffect, useState } from 'react'
|
|
7
|
+
|
|
8
|
+
// Fallback step function for test descriptions
|
|
9
|
+
const step = async (description: string, fn: () => Promise<void> | void) => {
|
|
10
|
+
console.log(`▶ ${description}`)
|
|
11
|
+
await fn()
|
|
12
|
+
console.log(`✓ ${description}`)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const meta: Meta = {
|
|
16
|
+
title: 'Regression Tests/Pages/Wastewater',
|
|
17
|
+
parameters: {
|
|
18
|
+
layout: 'fullscreen',
|
|
19
|
+
docs: {
|
|
20
|
+
description: {
|
|
21
|
+
component: 'Stories for all visualizations from the CDC National Wastewater Surveillance System (NWSS) pages'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
tags: ['autodocs']
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default meta
|
|
29
|
+
|
|
30
|
+
// Config URLs from the NWSS pages
|
|
31
|
+
const CONFIG_URLS = {
|
|
32
|
+
// Main NWSS page (https://www.cdc.gov/nwss/index.html)
|
|
33
|
+
homePageModules: 'https://www.cdc.gov/nwss/rv/modules/home-page-modules.json',
|
|
34
|
+
|
|
35
|
+
// Measles page (https://www.cdc.gov/nwss/rv/measles.html)
|
|
36
|
+
measlesTopModules: 'https://www.cdc.gov/nwss/rv/modules/measles/top-three-modules.json',
|
|
37
|
+
measlesMap: 'https://www.cdc.gov/nwss/rv/modules/measles/measles-us-map.json',
|
|
38
|
+
measlesTimePeriod: 'https://www.cdc.gov/nwss/rv/modules/measles/time-period.json',
|
|
39
|
+
|
|
40
|
+
// COVID-19 National Data page (https://www.cdc.gov/nwss/rv/COVID19-national-data.html)
|
|
41
|
+
covidTopModules: 'https://www.cdc.gov/nwss/rv/modules/sc2/covid-top-modules.json',
|
|
42
|
+
covidTimePeriodMap: 'https://www.cdc.gov/nwss/rv/modules/sc2/covid-time-period-state-map.json',
|
|
43
|
+
covidStateLevel: 'https://www.cdc.gov/nwss/rv/modules/sc2/covid-19-state-level.json',
|
|
44
|
+
covidNationalRegionalTrends: 'https://www.cdc.gov/nwss/rv/modules/sc2/covid-19-national-and-regional-trends.json',
|
|
45
|
+
|
|
46
|
+
// COVID-19 State Trend page (https://www.cdc.gov/nwss/rv/COVID19-statetrend.html)
|
|
47
|
+
covidStateLevelRest: 'https://www.cdc.gov/nwss/rv/modules/sc2/State-Level-covid-rest.json'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Helper to fetch config and update data URLs to use absolute cdc.gov paths
|
|
51
|
+
const useConfigWithAbsoluteDataUrl = (configUrl: string) => {
|
|
52
|
+
const [config, setConfig] = useState(null)
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
fetch(configUrl)
|
|
56
|
+
.then(res => res.json())
|
|
57
|
+
.then(data => {
|
|
58
|
+
// Convert relative data URLs to absolute cdc.gov URLs
|
|
59
|
+
if (data.dataUrl) {
|
|
60
|
+
// Handle different relative path formats (../../path or /path)
|
|
61
|
+
const dataUrl = data.dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
62
|
+
data.dataUrl = `https://www.cdc.gov/${dataUrl}`
|
|
63
|
+
}
|
|
64
|
+
if (data.dataFileName) {
|
|
65
|
+
const dataFileName = data.dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
66
|
+
data.dataFileName = `https://www.cdc.gov/${dataFileName}`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// For dashboard configs, convert dataKey references in visualizations
|
|
70
|
+
if (data.visualizations) {
|
|
71
|
+
Object.values(data.visualizations).forEach((viz: any) => {
|
|
72
|
+
if (viz.dataKey) {
|
|
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
|
|
80
|
+
if (data.datasets) {
|
|
81
|
+
const newDatasets = {}
|
|
82
|
+
Object.entries(data.datasets).forEach(([key, dataset]: [string, any]) => {
|
|
83
|
+
const newKey = key.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
84
|
+
const absoluteKey = `https://www.cdc.gov/${newKey}`
|
|
85
|
+
|
|
86
|
+
newDatasets[absoluteKey] = {
|
|
87
|
+
...dataset,
|
|
88
|
+
dataFileName: dataset.dataFileName
|
|
89
|
+
? `https://www.cdc.gov/${dataset.dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
|
|
90
|
+
: dataset.dataFileName,
|
|
91
|
+
dataUrl: dataset.dataUrl
|
|
92
|
+
? `https://www.cdc.gov/${dataset.dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
|
|
93
|
+
: dataset.dataUrl
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
data.datasets = newDatasets
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Set activeDashboard to 0 if it's null and multiDashboards exist
|
|
100
|
+
if (data.multiDashboards && data.multiDashboards.length > 0 && data.activeDashboard === null) {
|
|
101
|
+
data.activeDashboard = 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
setConfig(data)
|
|
105
|
+
})
|
|
106
|
+
.catch(err => {
|
|
107
|
+
console.error('Failed to fetch config:', configUrl, err)
|
|
108
|
+
})
|
|
109
|
+
}, [configUrl])
|
|
110
|
+
|
|
111
|
+
return config
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
type MapStory = StoryObj<typeof CdcMap>
|
|
115
|
+
type ChartStory = StoryObj<typeof Chart>
|
|
116
|
+
type DashboardStory = StoryObj<typeof Dashboard>
|
|
117
|
+
|
|
118
|
+
// Helper function to test map rendering
|
|
119
|
+
const testMapRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
120
|
+
const canvas = within(canvasElement)
|
|
121
|
+
|
|
122
|
+
await step('Wait for map to render', async () => {
|
|
123
|
+
const mapElement = await canvas.findByRole('img', { hidden: true }, { timeout: 10000 })
|
|
124
|
+
expect(mapElement).toBeInTheDocument()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
await step('Verify SVG element is present', async () => {
|
|
128
|
+
const svgElement = canvasElement.querySelector('svg')
|
|
129
|
+
expect(svgElement).toBeInTheDocument()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
await step('Verify COVE module wrapper is present', async () => {
|
|
133
|
+
const coveModule = canvasElement.querySelector('.cdc-open-viz-module')
|
|
134
|
+
expect(coveModule).toBeInTheDocument()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
console.log(` ${storyName} map rendered successfully`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Helper function to test chart rendering
|
|
141
|
+
const testChartRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
142
|
+
const canvas = within(canvasElement)
|
|
143
|
+
|
|
144
|
+
await step('Wait for chart to render', async () => {
|
|
145
|
+
const svgElement = await canvas.findByRole('img', { hidden: true }, { timeout: 10000 })
|
|
146
|
+
expect(svgElement).toBeInTheDocument()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
await step('Verify chart SVG is present', async () => {
|
|
150
|
+
const chartSvg = canvasElement.querySelector('svg')
|
|
151
|
+
expect(chartSvg).toBeInTheDocument()
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
await step('Verify COVE module wrapper is present', async () => {
|
|
155
|
+
const coveModule = canvasElement.querySelector('.cdc-open-viz-module')
|
|
156
|
+
expect(coveModule).toBeInTheDocument()
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
console.log(` ${storyName} chart rendered successfully`)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Helper function to test dashboard rendering
|
|
163
|
+
const testDashboardRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
164
|
+
await step('Wait for dashboard to render', async () => {
|
|
165
|
+
await new Promise<void>((resolve, reject) => {
|
|
166
|
+
const startTime = Date.now()
|
|
167
|
+
const timeout = 15000
|
|
168
|
+
|
|
169
|
+
const checkDashboard = () => {
|
|
170
|
+
const dashboardElement = canvasElement.querySelector('.cove-dashboard')
|
|
171
|
+
if (dashboardElement) {
|
|
172
|
+
resolve()
|
|
173
|
+
} else if (Date.now() - startTime > timeout) {
|
|
174
|
+
reject(new Error(`Timeout: Dashboard element not found after ${timeout}ms`))
|
|
175
|
+
} else {
|
|
176
|
+
setTimeout(checkDashboard, 100)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
checkDashboard()
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
await step('Verify dashboard wrapper is present', async () => {
|
|
184
|
+
const dashboard = canvasElement.querySelector('.cove-dashboard')
|
|
185
|
+
expect(dashboard).toBeInTheDocument()
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
console.log(` ${storyName} dashboard rendered successfully`)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* NWSS Home Page - Summary Modules
|
|
193
|
+
*
|
|
194
|
+
* Multi-virus wastewater surveillance summary from the main NWSS landing page.
|
|
195
|
+
*/
|
|
196
|
+
export const Home_Page_Modules: DashboardStory = {
|
|
197
|
+
render: () => {
|
|
198
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.homePageModules)
|
|
199
|
+
if (!config) return <div>Loading...</div>
|
|
200
|
+
return <Dashboard config={config} />
|
|
201
|
+
},
|
|
202
|
+
play: async ({ canvasElement }) => {
|
|
203
|
+
await testDashboardRendering(canvasElement, 'Home Page Modules')
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Measles - Top Summary Modules
|
|
209
|
+
*
|
|
210
|
+
* Key metrics for measles wastewater detections nationwide.
|
|
211
|
+
*/
|
|
212
|
+
export const Measles_Top_Modules: DashboardStory = {
|
|
213
|
+
render: () => {
|
|
214
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.measlesTopModules)
|
|
215
|
+
if (!config) return <div>Loading...</div>
|
|
216
|
+
return <Dashboard config={config} />
|
|
217
|
+
},
|
|
218
|
+
play: async ({ canvasElement }) => {
|
|
219
|
+
await testDashboardRendering(canvasElement, 'Measles Top Modules')
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Measles - US Map
|
|
225
|
+
*
|
|
226
|
+
* Geographic distribution of measles wastewater detections across the United States.
|
|
227
|
+
*/
|
|
228
|
+
export const Measles_Map: MapStory = {
|
|
229
|
+
render: () => {
|
|
230
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.measlesMap)
|
|
231
|
+
if (!config) return <div>Loading...</div>
|
|
232
|
+
return <CdcMap config={config} />
|
|
233
|
+
},
|
|
234
|
+
play: async ({ canvasElement }) => {
|
|
235
|
+
await testMapRendering(canvasElement, 'Measles Map')
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Measles - Time Period
|
|
241
|
+
*
|
|
242
|
+
* Timeline information for measles wastewater surveillance data.
|
|
243
|
+
*/
|
|
244
|
+
export const Measles_Time_Period: DashboardStory = {
|
|
245
|
+
render: () => {
|
|
246
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.measlesTimePeriod)
|
|
247
|
+
if (!config) return <div>Loading...</div>
|
|
248
|
+
return <Dashboard config={config} />
|
|
249
|
+
},
|
|
250
|
+
play: async ({ canvasElement }) => {
|
|
251
|
+
await testDashboardRendering(canvasElement, 'Measles Time Period')
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* COVID-19 - Top Summary Modules
|
|
257
|
+
*
|
|
258
|
+
* Key metrics for COVID-19 wastewater surveillance at the national level.
|
|
259
|
+
*/
|
|
260
|
+
export const COVID_Top_Modules: DashboardStory = {
|
|
261
|
+
render: () => {
|
|
262
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.covidTopModules)
|
|
263
|
+
if (!config) return <div>Loading...</div>
|
|
264
|
+
return <Dashboard config={config} />
|
|
265
|
+
},
|
|
266
|
+
play: async ({ canvasElement }) => {
|
|
267
|
+
await testDashboardRendering(canvasElement, 'COVID Top Modules')
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* COVID-19 - State Map with Time Period
|
|
273
|
+
*
|
|
274
|
+
* State-level COVID-19 wastewater activity levels across the US.
|
|
275
|
+
*/
|
|
276
|
+
export const COVID_Time_Period_Map: MapStory = {
|
|
277
|
+
render: () => {
|
|
278
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.covidTimePeriodMap)
|
|
279
|
+
if (!config) return <div>Loading...</div>
|
|
280
|
+
return <CdcMap config={config} />
|
|
281
|
+
},
|
|
282
|
+
play: async ({ canvasElement }) => {
|
|
283
|
+
await testMapRendering(canvasElement, 'COVID Time Period Map')
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* COVID-19 - State Level Data
|
|
289
|
+
*
|
|
290
|
+
* COVID-19 wastewater data visualization by state.
|
|
291
|
+
*/
|
|
292
|
+
export const COVID_State_Level: ChartStory = {
|
|
293
|
+
render: () => {
|
|
294
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.covidStateLevel)
|
|
295
|
+
if (!config) return <div>Loading...</div>
|
|
296
|
+
return <Chart config={config} />
|
|
297
|
+
},
|
|
298
|
+
play: async ({ canvasElement }) => {
|
|
299
|
+
await testChartRendering(canvasElement, 'COVID State Level')
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* COVID-19 - National and Regional Trends
|
|
305
|
+
*
|
|
306
|
+
* Trends in COVID-19 wastewater viral activity at national and HHS regional levels.
|
|
307
|
+
*/
|
|
308
|
+
export const COVID_National_Regional_Trends: ChartStory = {
|
|
309
|
+
render: () => {
|
|
310
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.covidNationalRegionalTrends)
|
|
311
|
+
if (!config) return <div>Loading...</div>
|
|
312
|
+
return <Chart config={config} />
|
|
313
|
+
},
|
|
314
|
+
play: async ({ canvasElement }) => {
|
|
315
|
+
await testChartRendering(canvasElement, 'COVID National Regional Trends')
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* COVID-19 - State Trend Data (Alternative View)
|
|
321
|
+
*
|
|
322
|
+
* State-level COVID-19 wastewater trend visualization from the state trend page.
|
|
323
|
+
*/
|
|
324
|
+
export const COVID_State_Level_Rest: ChartStory = {
|
|
325
|
+
render: () => {
|
|
326
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.covidStateLevelRest)
|
|
327
|
+
if (!config) return <div>Loading...</div>
|
|
328
|
+
return <Chart config={config} />
|
|
329
|
+
},
|
|
330
|
+
play: async ({ canvasElement }) => {
|
|
331
|
+
await testChartRendering(canvasElement, 'COVID State Level Rest')
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* All Wastewater Visualizations - Combined Test
|
|
337
|
+
*
|
|
338
|
+
* Tests all visualizations from the NWSS pages to ensure they all render correctly together.
|
|
339
|
+
*/
|
|
340
|
+
export const All_Wastewater_Visualizations: StoryObj = {
|
|
341
|
+
render: () => {
|
|
342
|
+
const homePageConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.homePageModules)
|
|
343
|
+
const measlesTopConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.measlesTopModules)
|
|
344
|
+
const measlesMapConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.measlesMap)
|
|
345
|
+
const measlesTimePeriodConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.measlesTimePeriod)
|
|
346
|
+
const covidTopConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.covidTopModules)
|
|
347
|
+
const covidMapConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.covidTimePeriodMap)
|
|
348
|
+
const covidStateLevelConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.covidStateLevel)
|
|
349
|
+
const covidNationalRegionalConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.covidNationalRegionalTrends)
|
|
350
|
+
const covidStateRestConfig = useConfigWithAbsoluteDataUrl(CONFIG_URLS.covidStateLevelRest)
|
|
351
|
+
|
|
352
|
+
const allLoaded =
|
|
353
|
+
homePageConfig &&
|
|
354
|
+
measlesTopConfig &&
|
|
355
|
+
measlesMapConfig &&
|
|
356
|
+
measlesTimePeriodConfig &&
|
|
357
|
+
covidTopConfig &&
|
|
358
|
+
covidMapConfig &&
|
|
359
|
+
covidStateLevelConfig &&
|
|
360
|
+
covidNationalRegionalConfig &&
|
|
361
|
+
covidStateRestConfig
|
|
362
|
+
|
|
363
|
+
if (!allLoaded) {
|
|
364
|
+
return <div>Loading...</div>
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
<div className="container-fluid p-4">
|
|
369
|
+
<h1 className="mb-4">NWSS - All Wastewater Visualizations</h1>
|
|
370
|
+
|
|
371
|
+
<section className="mb-5">
|
|
372
|
+
<h2>NWSS Home Page</h2>
|
|
373
|
+
<Dashboard config={homePageConfig} />
|
|
374
|
+
</section>
|
|
375
|
+
|
|
376
|
+
<section className="mb-5">
|
|
377
|
+
<h2>Measles - Summary Modules</h2>
|
|
378
|
+
<Dashboard config={measlesTopConfig} />
|
|
379
|
+
</section>
|
|
380
|
+
|
|
381
|
+
<section className="mb-5">
|
|
382
|
+
<h2>Measles - US Map</h2>
|
|
383
|
+
<CdcMap config={measlesMapConfig} />
|
|
384
|
+
</section>
|
|
385
|
+
|
|
386
|
+
<section className="mb-5">
|
|
387
|
+
<h2>Measles - Time Period</h2>
|
|
388
|
+
<Dashboard config={measlesTimePeriodConfig} />
|
|
389
|
+
</section>
|
|
390
|
+
|
|
391
|
+
<section className="mb-5">
|
|
392
|
+
<h2>COVID-19 - Summary Modules</h2>
|
|
393
|
+
<Dashboard config={covidTopConfig} />
|
|
394
|
+
</section>
|
|
395
|
+
|
|
396
|
+
<section className="mb-5">
|
|
397
|
+
<h2>COVID-19 - State Map</h2>
|
|
398
|
+
<CdcMap config={covidMapConfig} />
|
|
399
|
+
</section>
|
|
400
|
+
|
|
401
|
+
<section className="mb-5">
|
|
402
|
+
<h2>COVID-19 - State Level Data</h2>
|
|
403
|
+
<Chart config={covidStateLevelConfig} />
|
|
404
|
+
</section>
|
|
405
|
+
|
|
406
|
+
<section className="mb-5">
|
|
407
|
+
<h2>COVID-19 - National and Regional Trends</h2>
|
|
408
|
+
<Chart config={covidNationalRegionalConfig} />
|
|
409
|
+
</section>
|
|
410
|
+
|
|
411
|
+
<section className="mb-5">
|
|
412
|
+
<h2>COVID-19 - State Trends</h2>
|
|
413
|
+
<Chart config={covidStateRestConfig} />
|
|
414
|
+
</section>
|
|
415
|
+
</div>
|
|
416
|
+
)
|
|
417
|
+
},
|
|
418
|
+
play: async ({ canvasElement }) => {
|
|
419
|
+
await step('Wait for all configs to load', async () => {
|
|
420
|
+
await new Promise<void>(resolve => {
|
|
421
|
+
const checkLoading = () => {
|
|
422
|
+
const loadingDiv = canvasElement.querySelector('div:not(.container-fluid)')
|
|
423
|
+
if (!loadingDiv || !loadingDiv.textContent?.includes('Loading')) {
|
|
424
|
+
resolve()
|
|
425
|
+
} else {
|
|
426
|
+
setTimeout(checkLoading, 100)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
checkLoading()
|
|
430
|
+
})
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
await step('Wait for visualizations to start rendering', async () => {
|
|
434
|
+
await new Promise<void>(resolve => setTimeout(resolve, 2000))
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
await step('Wait for all 9 COVE modules to render', async () => {
|
|
438
|
+
await new Promise<void>((resolve, reject) => {
|
|
439
|
+
const startTime = Date.now()
|
|
440
|
+
const timeout = 30000
|
|
441
|
+
|
|
442
|
+
const checkModules = () => {
|
|
443
|
+
const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
|
|
444
|
+
if (coveModules.length >= 9) {
|
|
445
|
+
resolve()
|
|
446
|
+
} else if (Date.now() - startTime > timeout) {
|
|
447
|
+
reject(new Error(`Timeout: Only ${coveModules.length}/9 COVE modules found after ${timeout}ms`))
|
|
448
|
+
} else {
|
|
449
|
+
setTimeout(checkModules, 200)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
checkModules()
|
|
453
|
+
})
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
await step('Verify at least 9 visualizations are present', async () => {
|
|
457
|
+
const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
|
|
458
|
+
expect(coveModules.length).toBeGreaterThanOrEqual(9)
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
console.log(` All 9+ wastewater visualizations rendered successfully`)
|
|
462
|
+
}
|
|
463
|
+
}
|