@cdc/dashboard 4.26.3 → 4.26.5

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 (151) hide show
  1. package/CONFIG.md +219 -0
  2. package/README.md +60 -20
  3. package/dist/cdcdashboard-CY9IcPSi.es.js +6 -0
  4. package/dist/cdcdashboard-DlpiY3fQ.es.js +4 -0
  5. package/dist/cdcdashboard.js +61559 -58048
  6. package/examples/__data__/data-2.json +6 -0
  7. package/examples/__data__/data.json +6 -0
  8. package/examples/dashboard-conditions-filters-incomplete.json +221 -0
  9. package/examples/dashboard-missing-datasets-multi.json +174 -0
  10. package/examples/dashboard-missing-datasets-single.json +121 -0
  11. package/examples/dashboard-multi-dashboard-version-regression.json +146 -0
  12. package/examples/dashboard-shared-filter-row-delete-cleanup.json +186 -0
  13. package/examples/dashboard-stale-dataset-keys.json +181 -0
  14. package/examples/dashboard-tiered-filter-regression.json +190 -0
  15. package/examples/legend-issue.json +1 -1
  16. package/examples/minimal-example.json +34 -0
  17. package/examples/private/cfa-dashboard.json +651 -0
  18. package/examples/private/data-bite-wrap.json +6936 -0
  19. package/examples/private/dengue.json +4640 -0
  20. package/examples/private/link_to_file.json +16662 -0
  21. package/examples/private/multi-dash-fix.json +16963 -0
  22. package/examples/private/versions.json +41612 -0
  23. package/examples/sankey.json +3 -3
  24. package/examples/test-api-filter-reset.json +4 -4
  25. package/examples/tp5-test.json +86 -4
  26. package/examples/us-map-filter-example.json +1074 -0
  27. package/package.json +9 -9
  28. package/src/CdcDashboard.tsx +6 -2
  29. package/src/CdcDashboardComponent.tsx +179 -88
  30. package/src/DashboardCopyPasteContext.test.tsx +33 -0
  31. package/src/DashboardCopyPasteContext.tsx +48 -0
  32. package/src/_stories/Dashboard.EditorRegression.stories.tsx +72 -0
  33. package/src/_stories/Dashboard.Regression.stories.tsx +196 -0
  34. package/src/_stories/Dashboard.Zoom.stories.tsx +88 -0
  35. package/src/_stories/Dashboard.smoke.stories.tsx +33 -0
  36. package/src/_stories/Dashboard.stories.tsx +337 -2
  37. package/src/_stories/FilteredTextMigrationComparison.stories.tsx +87 -0
  38. package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
  39. package/src/_stories/_mock/tp5-test.json +86 -5
  40. package/src/components/Column.test.tsx +176 -0
  41. package/src/components/Column.tsx +214 -13
  42. package/src/components/DashboardConditionModal.test.tsx +420 -0
  43. package/src/components/DashboardConditionModal.tsx +367 -0
  44. package/src/components/DashboardConditionSummary.tsx +59 -0
  45. package/src/components/DashboardEditors.tsx +23 -0
  46. package/src/components/DashboardFilters/DashboardFilters.test.tsx +267 -0
  47. package/src/components/DashboardFilters/DashboardFilters.tsx +193 -172
  48. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.test.tsx +164 -0
  49. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +46 -6
  50. package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +5 -3
  51. package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +59 -58
  52. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +304 -0
  53. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +43 -36
  54. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +2 -2
  55. package/src/components/DashboardFilters/DashboardFiltersWrapper.test.tsx +142 -0
  56. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +32 -27
  57. package/src/components/DashboardFilters/dashboardfilter.styles.css +42 -27
  58. package/src/components/DataDesignerModal.tsx +2 -1
  59. package/src/components/ExpandCollapseButtons.tsx +6 -4
  60. package/src/components/Grid.tsx +12 -7
  61. package/src/components/Header/Header.tsx +36 -17
  62. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
  63. package/src/components/Row.test.tsx +228 -0
  64. package/src/components/Row.tsx +104 -28
  65. package/src/components/VisualizationRow.test.tsx +396 -0
  66. package/src/components/VisualizationRow.tsx +177 -51
  67. package/src/components/VisualizationsPanel/VisualizationsPanel.test.tsx +49 -0
  68. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +14 -13
  69. package/src/components/Widget/Widget.test.tsx +218 -0
  70. package/src/components/Widget/Widget.tsx +123 -20
  71. package/src/components/Widget/widget.styles.css +58 -14
  72. package/src/components/dashboard-condition-modal.css +76 -0
  73. package/src/components/dashboard-condition-summary.css +87 -0
  74. package/src/data/initial-state.js +1 -0
  75. package/src/helpers/addValuesToDashboardFilters.ts +3 -5
  76. package/src/helpers/addVisualization.ts +17 -4
  77. package/src/helpers/cloneDashboardWidget.ts +127 -0
  78. package/src/helpers/dashboardColumnWidgets.ts +99 -0
  79. package/src/helpers/dashboardConditionUi.ts +47 -0
  80. package/src/helpers/dashboardConditions.ts +200 -0
  81. package/src/helpers/dashboardFilterTargets.ts +156 -0
  82. package/src/helpers/filterData.ts +4 -9
  83. package/src/helpers/filterVisibility.ts +20 -0
  84. package/src/helpers/formatConfigBeforeSave.ts +2 -2
  85. package/src/helpers/getFilteredData.ts +18 -5
  86. package/src/helpers/getUpdateConfig.ts +43 -12
  87. package/src/helpers/getVizRowColumnLocator.ts +11 -1
  88. package/src/helpers/iconHash.tsx +9 -3
  89. package/src/helpers/mapDataToConfig.ts +31 -29
  90. package/src/helpers/reloadURLHelpers.ts +25 -5
  91. package/src/helpers/removeDashboardFilter.ts +33 -33
  92. package/src/helpers/tests/addVisualization.test.ts +53 -9
  93. package/src/helpers/tests/cloneDashboardWidget.test.ts +136 -0
  94. package/src/helpers/tests/dashboardColumnWidgets.test.ts +99 -0
  95. package/src/helpers/tests/dashboardConditionUi.test.ts +41 -0
  96. package/src/helpers/tests/dashboardConditions.test.ts +428 -0
  97. package/src/helpers/tests/formatConfigBeforeSave.test.ts +51 -0
  98. package/src/helpers/tests/getFilteredData.test.ts +265 -86
  99. package/src/helpers/tests/getUpdateConfig.test.ts +338 -0
  100. package/src/helpers/tests/reloadURLHelpers.test.ts +394 -238
  101. package/src/index.tsx +6 -3
  102. package/src/scss/grid.scss +281 -22
  103. package/src/scss/main.scss +215 -64
  104. package/src/store/dashboard.actions.ts +17 -4
  105. package/src/store/dashboard.reducer.test.ts +538 -0
  106. package/src/store/dashboard.reducer.ts +136 -22
  107. package/src/test/CdcDashboard.test.jsx +24 -0
  108. package/src/test/CdcDashboard.test.tsx +148 -0
  109. package/src/test/CdcDashboardComponent.test.tsx +935 -2
  110. package/src/types/ConfigRow.ts +15 -0
  111. package/src/types/DashboardFilters.ts +4 -0
  112. package/src/types/SharedFilter.ts +2 -0
  113. package/tests/fixtures/dashboard-config-with-metadata.json +1 -1
  114. package/dist/cdcdashboard-vr9HZwRt.es.js +0 -6
  115. package/examples/DEV-6574.json +0 -2224
  116. package/examples/api-dashboard-data.json +0 -272
  117. package/examples/api-dashboard-years.json +0 -11
  118. package/examples/api-geographies-data.json +0 -11
  119. package/examples/chart-data.json +0 -5409
  120. package/examples/custom/css/respiratory.css +0 -236
  121. package/examples/custom/js/respiratory.js +0 -242
  122. package/examples/default-data.json +0 -368
  123. package/examples/default-filter-control.json +0 -209
  124. package/examples/default-multi-dataset-shared-filter.json +0 -1729
  125. package/examples/default-multi-dataset.json +0 -506
  126. package/examples/ed-visits-county-file.json +0 -402
  127. package/examples/filters/Alabama.json +0 -72
  128. package/examples/filters/Alaska.json +0 -1737
  129. package/examples/filters/Arkansas.json +0 -4713
  130. package/examples/filters/California.json +0 -212
  131. package/examples/filters/Colorado.json +0 -1500
  132. package/examples/filters/Connecticut.json +0 -559
  133. package/examples/filters/Delaware.json +0 -63
  134. package/examples/filters/DistrictofColumbia.json +0 -63
  135. package/examples/filters/Florida.json +0 -4217
  136. package/examples/filters/States.json +0 -146
  137. package/examples/state-level.json +0 -90136
  138. package/examples/state-points.json +0 -10474
  139. package/examples/temp-example-data.json +0 -130
  140. package/examples/test-dashboard-simple.json +0 -503
  141. package/examples/test-example.json +0 -752
  142. package/examples/test-file.json +0 -147
  143. package/examples/test.json +0 -752
  144. package/examples/testing.json +0 -94456
  145. /package/examples/{data → __data__}/data-with-metadata.json +0 -0
  146. /package/examples/{legend-issue-data.json → __data__/legend-issue-data.json} +0 -0
  147. /package/examples/api-test/{categories.json → __data__/categories.json} +0 -0
  148. /package/examples/api-test/{chart-data.json → __data__/chart-data.json} +0 -0
  149. /package/examples/api-test/{topics.json → __data__/topics.json} +0 -0
  150. /package/examples/api-test/{years.json → __data__/years.json} +0 -0
  151. /package/src/_stories/{Dashboard.Pages.stories.tsx → Dashboard.Pages.smoke.stories.tsx} +0 -0
@@ -0,0 +1,396 @@
1
+ import React from 'react'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { describe, expect, it, vi } from 'vitest'
4
+ import { DashboardContext, initialState } from '../DashboardContext'
5
+ import VisualizationRow from './VisualizationRow'
6
+
7
+ vi.mock('@cdc/markup-include/src/CdcMarkupInclude', () => ({
8
+ default: ({ config }) => <div>{config.contentEditor?.title}</div>
9
+ }))
10
+
11
+ vi.mock('@cdc/filtered-text/src/CdcFilteredText', () => ({
12
+ default: ({ config }) => <div>{config.title}</div>
13
+ }))
14
+
15
+ vi.mock('./Toggle', () => ({
16
+ default: ({ row }) => <div>{row.columns.filter(column => column.widget).length} toggle options</div>
17
+ }))
18
+
19
+ describe('VisualizationRow', () => {
20
+ it('renders the first matching conditional entry and hides rows with no resolved widgets', () => {
21
+ const matchingRow = {
22
+ columns: [
23
+ {
24
+ width: 12,
25
+ conditionalWidgets: [
26
+ {
27
+ widget: 'markup-primary',
28
+ dashboardCondition: {
29
+ id: 'conditional-primary',
30
+ datasetKey: 'conditional-data',
31
+ operator: 'columnHasAnyValue',
32
+ columnName: 'flag',
33
+ values: ['show-primary']
34
+ }
35
+ },
36
+ {
37
+ widget: 'markup-fallback',
38
+ dashboardCondition: {
39
+ id: 'conditional-fallback',
40
+ datasetKey: 'conditional-data',
41
+ operator: 'columnHasAnyValue',
42
+ columnName: 'flag',
43
+ values: ['show-fallback']
44
+ }
45
+ }
46
+ ]
47
+ }
48
+ ],
49
+ expandCollapseAllButtons: false
50
+ } as any
51
+
52
+ const hiddenRow = {
53
+ columns: [
54
+ {
55
+ width: 12,
56
+ conditionalWidgets: [
57
+ {
58
+ widget: 'markup-hidden',
59
+ dashboardCondition: {
60
+ id: 'conditional-hidden',
61
+ datasetKey: 'conditional-data',
62
+ operator: 'columnHasAnyValue',
63
+ columnName: 'flag',
64
+ values: ['never']
65
+ }
66
+ }
67
+ ]
68
+ }
69
+ ],
70
+ expandCollapseAllButtons: false
71
+ } as any
72
+
73
+ const contextValue = {
74
+ ...initialState,
75
+ config: {
76
+ type: 'dashboard',
77
+ dashboard: { sharedFilters: [] },
78
+ datasets: {},
79
+ rows: [matchingRow, hiddenRow],
80
+ visualizations: {
81
+ 'markup-primary': {
82
+ uid: 'markup-primary',
83
+ type: 'markup-include',
84
+ visualizationType: 'markup-include',
85
+ contentEditor: { title: 'Primary conditional content' }
86
+ },
87
+ 'markup-fallback': {
88
+ uid: 'markup-fallback',
89
+ type: 'markup-include',
90
+ visualizationType: 'markup-include',
91
+ contentEditor: { title: 'Fallback conditional content' }
92
+ },
93
+ 'markup-hidden': {
94
+ uid: 'markup-hidden',
95
+ type: 'markup-include',
96
+ visualizationType: 'markup-include',
97
+ contentEditor: { title: 'Should not render' }
98
+ }
99
+ }
100
+ } as any,
101
+ filteredData: {
102
+ 'conditional-primary': [{ flag: 'show-fallback' }],
103
+ 'conditional-fallback': [{ flag: 'show-fallback' }],
104
+ 'conditional-hidden': [{ flag: 'show-fallback' }]
105
+ },
106
+ data: {},
107
+ outerContainerRef: vi.fn(),
108
+ setParentConfig: vi.fn(),
109
+ isDebug: false,
110
+ isEditor: false,
111
+ reloadURLData: vi.fn(),
112
+ loadAPIFilters: vi.fn(),
113
+ setAPIFilterDropdowns: vi.fn(),
114
+ setAPILoading: vi.fn()
115
+ }
116
+
117
+ const { container } = render(
118
+ <DashboardContext.Provider value={contextValue}>
119
+ <>
120
+ <VisualizationRow
121
+ allExpanded
122
+ groupName=''
123
+ row={matchingRow}
124
+ rowIndex={0}
125
+ inNoDataState={false}
126
+ setSharedFilter={vi.fn()}
127
+ updateChildConfig={vi.fn()}
128
+ apiFilterDropdowns={{}}
129
+ currentViewport={{} as any}
130
+ isLastRow={false}
131
+ interactionLabel='dashboard-test'
132
+ />
133
+ <VisualizationRow
134
+ allExpanded
135
+ groupName=''
136
+ row={hiddenRow}
137
+ rowIndex={1}
138
+ inNoDataState={false}
139
+ setSharedFilter={vi.fn()}
140
+ updateChildConfig={vi.fn()}
141
+ apiFilterDropdowns={{}}
142
+ currentViewport={{} as any}
143
+ isLastRow={true}
144
+ interactionLabel='dashboard-test'
145
+ />
146
+ </>
147
+ </DashboardContext.Provider>
148
+ )
149
+
150
+ expect(screen.queryByText('Primary conditional content')).not.toBeInTheDocument()
151
+ expect(screen.getByText('Fallback conditional content')).toBeInTheDocument()
152
+ expect(screen.queryByText('Should not render')).not.toBeInTheDocument()
153
+ expect(container.querySelectorAll('[data-row-index]').length).toBe(1)
154
+ })
155
+
156
+ it('still renders existing legacy filtered-text dashboard widgets during phase one', () => {
157
+ const legacyRow = {
158
+ columns: [{ width: 12, widget: 'legacy-filtered-text' }],
159
+ expandCollapseAllButtons: false
160
+ } as any
161
+
162
+ const contextValue = {
163
+ ...initialState,
164
+ config: {
165
+ type: 'dashboard',
166
+ dashboard: { sharedFilters: [] },
167
+ datasets: {},
168
+ rows: [legacyRow],
169
+ visualizations: {
170
+ 'legacy-filtered-text': {
171
+ uid: 'legacy-filtered-text',
172
+ type: 'filtered-text',
173
+ visualizationType: 'filtered-text',
174
+ title: 'Legacy filtered text'
175
+ }
176
+ }
177
+ } as any,
178
+ filteredData: {},
179
+ data: {},
180
+ outerContainerRef: vi.fn(),
181
+ setParentConfig: vi.fn(),
182
+ isDebug: false,
183
+ isEditor: false,
184
+ reloadURLData: vi.fn(),
185
+ loadAPIFilters: vi.fn(),
186
+ setAPIFilterDropdowns: vi.fn(),
187
+ setAPILoading: vi.fn()
188
+ }
189
+
190
+ render(
191
+ <DashboardContext.Provider value={contextValue}>
192
+ <VisualizationRow
193
+ allExpanded
194
+ groupName=''
195
+ row={legacyRow}
196
+ rowIndex={0}
197
+ inNoDataState={false}
198
+ setSharedFilter={vi.fn()}
199
+ updateChildConfig={vi.fn()}
200
+ apiFilterDropdowns={{}}
201
+ currentViewport={{} as any}
202
+ isLastRow={true}
203
+ interactionLabel='dashboard-test'
204
+ />
205
+ </DashboardContext.Provider>
206
+ )
207
+
208
+ expect(screen.getByText('Legacy filtered text')).toBeInTheDocument()
209
+ })
210
+
211
+ it('skips truly empty runtime columns in toggle rows', () => {
212
+ const toggleRow = {
213
+ toggle: true,
214
+ columns: [{ width: 12, widget: 'markup-visible' }, { width: 12 }],
215
+ expandCollapseAllButtons: false
216
+ } as any
217
+
218
+ const contextValue = {
219
+ ...initialState,
220
+ config: {
221
+ type: 'dashboard',
222
+ dashboard: { sharedFilters: [] },
223
+ datasets: {},
224
+ rows: [toggleRow],
225
+ visualizations: {
226
+ 'markup-visible': {
227
+ uid: 'markup-visible',
228
+ type: 'markup-include',
229
+ visualizationType: 'markup-include',
230
+ contentEditor: { title: 'Visible toggle content' }
231
+ }
232
+ }
233
+ } as any,
234
+ filteredData: {},
235
+ data: {},
236
+ outerContainerRef: vi.fn(),
237
+ setParentConfig: vi.fn(),
238
+ isDebug: false,
239
+ isEditor: false,
240
+ reloadURLData: vi.fn(),
241
+ loadAPIFilters: vi.fn(),
242
+ setAPIFilterDropdowns: vi.fn(),
243
+ setAPILoading: vi.fn()
244
+ }
245
+
246
+ const { container } = render(
247
+ <DashboardContext.Provider value={contextValue}>
248
+ <VisualizationRow
249
+ allExpanded
250
+ groupName=''
251
+ row={toggleRow}
252
+ rowIndex={0}
253
+ inNoDataState={false}
254
+ setSharedFilter={vi.fn()}
255
+ updateChildConfig={vi.fn()}
256
+ apiFilterDropdowns={{}}
257
+ currentViewport={{} as any}
258
+ isLastRow={true}
259
+ interactionLabel='dashboard-test'
260
+ />
261
+ </DashboardContext.Provider>
262
+ )
263
+
264
+ expect(screen.getByText('Visible toggle content')).toBeInTheDocument()
265
+ expect(screen.getByText('1 toggle options')).toBeInTheDocument()
266
+
267
+ const row = container.querySelector('[data-row-index="0"]')
268
+ expect(row?.querySelectorAll('[data-dashboard-condition-hidden="true"]').length).toBe(0)
269
+ expect(row?.querySelectorAll('.col-md-12').length).toBe(1)
270
+ })
271
+
272
+ it('does not render a row that only contains dashboard filters with no visible referenced filters', () => {
273
+ const filterRow = {
274
+ columns: [{ width: 12, widget: 'dashboard-filters-hidden' }],
275
+ expandCollapseAllButtons: false
276
+ } as any
277
+
278
+ const contextValue = {
279
+ ...initialState,
280
+ config: {
281
+ type: 'dashboard',
282
+ dashboard: {
283
+ sharedFilters: [
284
+ {
285
+ key: 'Hidden year',
286
+ type: 'datafilter',
287
+ filterStyle: 'dropdown',
288
+ showDropdown: false,
289
+ values: ['2024'],
290
+ active: '2024',
291
+ columnName: 'year',
292
+ parents: []
293
+ }
294
+ ]
295
+ },
296
+ datasets: {},
297
+ rows: [filterRow],
298
+ visualizations: {
299
+ 'dashboard-filters-hidden': {
300
+ uid: 'dashboard-filters-hidden',
301
+ type: 'dashboardFilters',
302
+ visualizationType: 'dashboardFilters',
303
+ filterBehavior: 'Filter Change',
304
+ sharedFilterIndexes: [0]
305
+ }
306
+ }
307
+ } as any,
308
+ filteredData: {},
309
+ data: {},
310
+ outerContainerRef: vi.fn(),
311
+ setParentConfig: vi.fn(),
312
+ isDebug: false,
313
+ isEditor: false,
314
+ reloadURLData: vi.fn(),
315
+ loadAPIFilters: vi.fn(),
316
+ setAPIFilterDropdowns: vi.fn(),
317
+ setAPILoading: vi.fn()
318
+ }
319
+
320
+ const { container } = render(
321
+ <DashboardContext.Provider value={contextValue}>
322
+ <VisualizationRow
323
+ allExpanded
324
+ groupName=''
325
+ row={filterRow}
326
+ rowIndex={0}
327
+ inNoDataState={false}
328
+ setSharedFilter={vi.fn()}
329
+ updateChildConfig={vi.fn()}
330
+ apiFilterDropdowns={{}}
331
+ currentViewport={{} as any}
332
+ isLastRow={true}
333
+ interactionLabel='dashboard-test'
334
+ />
335
+ </DashboardContext.Provider>
336
+ )
337
+
338
+ expect(container.querySelector('[data-row-index]')).not.toBeInTheDocument()
339
+ })
340
+
341
+ it('does not render a row that only contains a dashboard filter widget with empty sharedFilterIndexes', () => {
342
+ const filterRow = {
343
+ columns: [{ width: 12, widget: 'dashboard-filters-empty' }],
344
+ expandCollapseAllButtons: false
345
+ } as any
346
+
347
+ const contextValue = {
348
+ ...initialState,
349
+ config: {
350
+ type: 'dashboard',
351
+ dashboard: { sharedFilters: [] },
352
+ datasets: {},
353
+ rows: [filterRow],
354
+ visualizations: {
355
+ 'dashboard-filters-empty': {
356
+ uid: 'dashboard-filters-empty',
357
+ type: 'dashboardFilters',
358
+ visualizationType: 'dashboardFilters',
359
+ filterBehavior: 'Filter Change',
360
+ sharedFilterIndexes: []
361
+ }
362
+ }
363
+ } as any,
364
+ filteredData: {},
365
+ data: {},
366
+ outerContainerRef: vi.fn(),
367
+ setParentConfig: vi.fn(),
368
+ isDebug: false,
369
+ isEditor: false,
370
+ reloadURLData: vi.fn(),
371
+ loadAPIFilters: vi.fn(),
372
+ setAPIFilterDropdowns: vi.fn(),
373
+ setAPILoading: vi.fn()
374
+ }
375
+
376
+ const { container } = render(
377
+ <DashboardContext.Provider value={contextValue}>
378
+ <VisualizationRow
379
+ allExpanded
380
+ groupName=''
381
+ row={filterRow}
382
+ rowIndex={0}
383
+ inNoDataState={false}
384
+ setSharedFilter={vi.fn()}
385
+ updateChildConfig={vi.fn()}
386
+ apiFilterDropdowns={{}}
387
+ currentViewport={{} as any}
388
+ isLastRow={true}
389
+ interactionLabel='dashboard-test'
390
+ />
391
+ </DashboardContext.Provider>
392
+ )
393
+
394
+ expect(container.querySelector('[data-row-index]')).not.toBeInTheDocument()
395
+ })
396
+ })