@cdc/dashboard 4.26.4 → 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 (91) hide show
  1. package/CONFIG.md +77 -30
  2. package/LICENSE +201 -0
  3. package/dist/cdcdashboard.js +49936 -49166
  4. package/examples/dashboard-conditions-filters-incomplete.json +221 -0
  5. package/examples/dashboard-missing-datasets-multi.json +174 -0
  6. package/examples/dashboard-missing-datasets-single.json +121 -0
  7. package/examples/dashboard-multi-dashboard-version-regression.json +146 -0
  8. package/examples/dashboard-shared-filter-row-delete-cleanup.json +186 -0
  9. package/examples/dashboard-stale-dataset-keys.json +181 -0
  10. package/examples/dashboard-tiered-filter-regression.json +190 -0
  11. package/examples/private/cfa-dashboard.json +651 -0
  12. package/examples/private/data-bite-wrap.json +6936 -0
  13. package/examples/private/multi-dash-fix.json +16963 -0
  14. package/examples/private/versions.json +41612 -0
  15. package/examples/us-map-filter-example.json +1074 -0
  16. package/package.json +9 -9
  17. package/src/CdcDashboard.tsx +6 -2
  18. package/src/CdcDashboardComponent.tsx +178 -87
  19. package/src/DashboardCopyPasteContext.test.tsx +33 -0
  20. package/src/DashboardCopyPasteContext.tsx +48 -0
  21. package/src/_stories/Dashboard.EditorRegression.stories.tsx +72 -0
  22. package/src/_stories/Dashboard.Regression.stories.tsx +196 -0
  23. package/src/_stories/Dashboard.Zoom.stories.tsx +88 -0
  24. package/src/_stories/Dashboard.stories.tsx +294 -0
  25. package/src/_stories/FilteredTextMigrationComparison.stories.tsx +87 -0
  26. package/src/components/Column.test.tsx +176 -0
  27. package/src/components/Column.tsx +214 -13
  28. package/src/components/DashboardConditionModal.test.tsx +420 -0
  29. package/src/components/DashboardConditionModal.tsx +367 -0
  30. package/src/components/DashboardConditionSummary.tsx +59 -0
  31. package/src/components/DashboardEditors.tsx +8 -0
  32. package/src/components/DashboardFilters/DashboardFilters.test.tsx +139 -1
  33. package/src/components/DashboardFilters/DashboardFilters.tsx +192 -174
  34. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.test.tsx +164 -0
  35. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +41 -2
  36. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +180 -3
  37. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +15 -32
  38. package/src/components/DashboardFilters/DashboardFiltersWrapper.test.tsx +142 -0
  39. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +32 -27
  40. package/src/components/DashboardFilters/dashboardfilter.styles.css +42 -27
  41. package/src/components/DataDesignerModal.tsx +2 -1
  42. package/src/components/Grid.tsx +8 -4
  43. package/src/components/Header/Header.tsx +36 -17
  44. package/src/components/Row.test.tsx +228 -0
  45. package/src/components/Row.tsx +93 -18
  46. package/src/components/VisualizationRow.test.tsx +396 -0
  47. package/src/components/VisualizationRow.tsx +110 -35
  48. package/src/components/VisualizationsPanel/VisualizationsPanel.test.tsx +49 -0
  49. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +14 -13
  50. package/src/components/Widget/Widget.test.tsx +218 -0
  51. package/src/components/Widget/Widget.tsx +119 -17
  52. package/src/components/Widget/widget.styles.css +31 -18
  53. package/src/components/dashboard-condition-modal.css +76 -0
  54. package/src/components/dashboard-condition-summary.css +87 -0
  55. package/src/helpers/addValuesToDashboardFilters.ts +3 -5
  56. package/src/helpers/addVisualization.ts +15 -4
  57. package/src/helpers/cloneDashboardWidget.ts +127 -0
  58. package/src/helpers/dashboardColumnWidgets.ts +99 -0
  59. package/src/helpers/dashboardConditionUi.ts +47 -0
  60. package/src/helpers/dashboardConditions.ts +200 -0
  61. package/src/helpers/dashboardFilterTargets.ts +156 -0
  62. package/src/helpers/filterData.ts +4 -9
  63. package/src/helpers/filterVisibility.ts +20 -0
  64. package/src/helpers/formatConfigBeforeSave.ts +2 -2
  65. package/src/helpers/getFilteredData.ts +18 -5
  66. package/src/helpers/getUpdateConfig.ts +43 -12
  67. package/src/helpers/getVizRowColumnLocator.ts +11 -1
  68. package/src/helpers/iconHash.tsx +9 -3
  69. package/src/helpers/mapDataToConfig.ts +31 -29
  70. package/src/helpers/reloadURLHelpers.ts +25 -5
  71. package/src/helpers/removeDashboardFilter.ts +33 -33
  72. package/src/helpers/tests/addVisualization.test.ts +53 -9
  73. package/src/helpers/tests/cloneDashboardWidget.test.ts +136 -0
  74. package/src/helpers/tests/dashboardColumnWidgets.test.ts +99 -0
  75. package/src/helpers/tests/dashboardConditionUi.test.ts +41 -0
  76. package/src/helpers/tests/dashboardConditions.test.ts +428 -0
  77. package/src/helpers/tests/formatConfigBeforeSave.test.ts +51 -0
  78. package/src/helpers/tests/getFilteredData.test.ts +265 -86
  79. package/src/helpers/tests/getUpdateConfig.test.ts +338 -0
  80. package/src/helpers/tests/reloadURLHelpers.test.ts +394 -238
  81. package/src/index.tsx +6 -3
  82. package/src/scss/grid.scss +249 -20
  83. package/src/scss/main.scss +108 -29
  84. package/src/store/dashboard.actions.ts +17 -4
  85. package/src/store/dashboard.reducer.test.ts +538 -0
  86. package/src/store/dashboard.reducer.ts +135 -22
  87. package/src/test/CdcDashboard.test.tsx +148 -0
  88. package/src/test/CdcDashboardComponent.test.tsx +935 -2
  89. package/src/types/ConfigRow.ts +15 -0
  90. package/src/types/DashboardFilters.ts +4 -0
  91. package/src/types/SharedFilter.ts +1 -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
+ })