@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
@@ -1,5 +1,5 @@
1
1
  import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
2
- import React, { useContext, useEffect, useMemo, useState } from 'react'
2
+ import React, { useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
3
3
  import Toggle from './Toggle'
4
4
  import cloneDeep from 'lodash/cloneDeep'
5
5
  import { ConfigRow } from '../types/ConfigRow'
@@ -11,6 +11,12 @@ import CdcFilteredText from '@cdc/filtered-text/src/CdcFilteredText'
11
11
  import DashboardSharedFilters, { APIFilterDropdowns } from './DashboardFilters'
12
12
  import { DashboardContext } from '../DashboardContext'
13
13
  import { ViewPort } from '@cdc/core/types/ViewPort'
14
+ import { evaluateDashboardCondition } from '../helpers/dashboardConditions'
15
+ import { dashboardConditionsSupportedForRow } from '../helpers/dashboardFilterTargets'
16
+ import {
17
+ hasAuthoredWidgetEntries,
18
+ resolveColumnWidgetEntry as resolveDashboardColumnWidgetEntry
19
+ } from '../helpers/dashboardColumnWidgets'
14
20
  import { getVizConfig } from '../helpers/getVizConfig'
15
21
  import { TableConfig } from '@cdc/core/components/DataTable/types/TableConfig'
16
22
  import CollapsibleVisualizationRow from './CollapsibleVisualizationRow'
@@ -21,6 +27,7 @@ import ExpandCollapseButtons from './ExpandCollapseButtons'
21
27
  import { ChartConfig } from '@cdc/chart/src/types/ChartConfig'
22
28
  import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
23
29
  import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
30
+ import { hasVisibleDashboardFiltersForIndexes } from '../helpers/filterVisibility'
24
31
 
25
32
  type VisualizationWrapperProps = {
26
33
  allExpanded: boolean
@@ -63,6 +70,8 @@ type VizRowProps = {
63
70
  rowIndex: number
64
71
  inNoDataState: boolean
65
72
  setSharedFilter: Function
73
+ clearSharedFilter: (key: string) => void
74
+ hasActiveSharedFilter: (key: string) => boolean
66
75
  updateChildConfig: Function
67
76
  apiFilterDropdowns: APIFilterDropdowns
68
77
  currentViewport: ViewPort
@@ -79,6 +88,8 @@ const VisualizationRow: React.FC<VizRowProps> = ({
79
88
  rowIndex: index,
80
89
  inNoDataState,
81
90
  setSharedFilter,
91
+ clearSharedFilter,
92
+ hasActiveSharedFilter,
82
93
  updateChildConfig,
83
94
  apiFilterDropdowns,
84
95
  currentViewport,
@@ -87,7 +98,8 @@ const VisualizationRow: React.FC<VizRowProps> = ({
87
98
  interactionLabel = ''
88
99
  }) => {
89
100
  const { config, filteredData: dashboardFilteredData, data: rawData } = useContext(DashboardContext)
90
- const [toggledRow, setToggled] = React.useState<number>(0)
101
+ const [toggledRow, setToggled] = useState<number>(0)
102
+ const rowRef = useRef<HTMLDivElement>(null)
91
103
 
92
104
  useEffect(() => {
93
105
  if (row.toggle) setToggled(0)
@@ -103,15 +115,18 @@ const VisualizationRow: React.FC<VizRowProps> = ({
103
115
  }
104
116
  }, [toggledRow, row.toggle])
105
117
 
106
- const setupTP5MinHeightEqualizer = (rowElement: Element, itemSelector: string) => {
107
- const items = Array.from(rowElement.querySelectorAll(itemSelector)) as HTMLElement[]
108
- if (items.length <= 1) return undefined
118
+ const setupMinHeightEqualizer = (rowElement: HTMLElement, selectors: string[]) => {
119
+ let items: HTMLElement[] = []
120
+ const selector = selectors.join(', ')
121
+ const resizeObserver = new ResizeObserver(() => equalizeHeights())
109
122
 
110
123
  const equalizeHeights = () => {
111
124
  items.forEach(item => {
112
125
  item.style.minHeight = ''
113
126
  })
114
127
 
128
+ if (items.length <= 1) return
129
+
115
130
  let maxHeight = 0
116
131
  items.forEach(item => {
117
132
  const height = item.offsetHeight
@@ -125,42 +140,105 @@ const VisualizationRow: React.FC<VizRowProps> = ({
125
140
  }
126
141
  }
127
142
 
128
- equalizeHeights()
143
+ const refreshItems = () => {
144
+ const nextItems = Array.from(rowElement.querySelectorAll(selector)) as HTMLElement[]
145
+ const nextSet = new Set(nextItems)
146
+
147
+ items.forEach(item => {
148
+ if (!nextSet.has(item)) {
149
+ resizeObserver.unobserve(item)
150
+ item.style.minHeight = ''
151
+ }
152
+ })
129
153
 
130
- const resizeObserver = new ResizeObserver(() => {
154
+ nextItems.forEach(item => {
155
+ if (!items.includes(item)) {
156
+ resizeObserver.observe(item)
157
+ }
158
+ })
159
+
160
+ items = nextItems
131
161
  equalizeHeights()
132
- })
162
+ }
133
163
 
134
- items.forEach(item => {
135
- resizeObserver.observe(item)
164
+ const mutationObserver = new MutationObserver(() => {
165
+ refreshItems()
136
166
  })
137
167
 
138
- return () => resizeObserver.disconnect()
168
+ mutationObserver.observe(rowElement, { childList: true, subtree: true })
169
+ refreshItems()
170
+
171
+ return () => {
172
+ mutationObserver.disconnect()
173
+ resizeObserver.disconnect()
174
+ items.forEach(item => {
175
+ item.style.minHeight = ''
176
+ })
177
+ }
139
178
  }
140
179
 
141
- // Equalize TP5 callout title heights and TP5 gauge message blocks for like visualizations in the same row
142
- useEffect(() => {
143
- if (!row.equalHeight) return
180
+ const shouldIgnoreDashboardConditions = !dashboardConditionsSupportedForRow(row)
181
+ const columnDashboardConditionEvaluations = useMemo(
182
+ () =>
183
+ row.columns.map(column =>
184
+ resolveDashboardColumnWidgetEntry(column, {
185
+ evaluateCondition: dashboardCondition => {
186
+ if (shouldIgnoreDashboardConditions || !dashboardCondition) {
187
+ return { matches: true, resolved: true }
188
+ }
189
+
190
+ return evaluateDashboardCondition(dashboardCondition, dashboardFilteredData[dashboardCondition.id])
191
+ }
192
+ })
193
+ ),
194
+ [dashboardFilteredData, row.columns, shouldIgnoreDashboardConditions]
195
+ )
196
+
197
+ const tp5CountInRow = columnDashboardConditionEvaluations.reduce((count, evaluation) => {
198
+ const widgetKey = evaluation?.widget
199
+ if (!widgetKey) return count
200
+ const viz = config.visualizations[widgetKey]
201
+ if (!viz) return count
202
+
203
+ const isTp5DataBite = viz.type === 'data-bite' && (viz as any).biteStyle === 'tp5'
204
+ const isTp5WaffleOrGauge =
205
+ viz.type === 'waffle-chart' && (viz.visualizationType === 'TP5 Waffle' || viz.visualizationType === 'TP5 Gauge')
206
+ const isTp5MarkupInclude = viz.type === 'markup-include' && (viz as any).contentEditor?.style === 'tp5'
144
207
 
145
- const rowElement = document.querySelector(`[data-row-index="${index}"]`)
208
+ return isTp5DataBite || isTp5WaffleOrGauge || isTp5MarkupInclude ? count + 1 : count
209
+ }, 0)
210
+ const needsTP5AutoEqualization = tp5CountInRow > 1
211
+ const shouldEqualizeRow = !!row.equalHeight || needsTP5AutoEqualization
212
+
213
+ // Layer TP5 equalization for row-level title consistency and same-type internals.
214
+ useLayoutEffect(() => {
215
+ if (!shouldEqualizeRow) return
216
+
217
+ const rowElement = rowRef.current
146
218
  if (!rowElement) return
147
219
 
148
220
  const cleanups = [
149
- setupTP5MinHeightEqualizer(rowElement, '.bite__style--tp5 .cdc-callout__heading'),
150
- setupTP5MinHeightEqualizer(rowElement, '.waffle__style--tp5 .cdc-callout__heading'),
151
- setupTP5MinHeightEqualizer(rowElement, '.gauge__style--tp5 .cdc-callout__heading'),
152
- setupTP5MinHeightEqualizer(rowElement, '.gauge__style--tp5 .cove-gauge-chart__content')
153
- ].filter(Boolean) as Array<() => void>
221
+ // Cross-type TP5 title alignment in the row.
222
+ setupMinHeightEqualizer(rowElement, [
223
+ '.bite__style--tp5 .cdc-callout__heading',
224
+ '.waffle__style--tp5 .cdc-callout__heading',
225
+ '.gauge__style--tp5 .cdc-callout__heading',
226
+ '.markup-include__style--tp5 .cdc-callout__heading'
227
+ ]),
228
+ // Same-type gauge internals alignment so gauge meters line up despite content variance.
229
+ setupMinHeightEqualizer(rowElement, ['.gauge__style--tp5 .cove-gauge-chart__content'])
230
+ ]
154
231
 
155
232
  return () => {
156
233
  cleanups.forEach(cleanup => cleanup())
157
234
  }
158
- }, [index, row, config, filteredDataOverride])
235
+ }, [shouldEqualizeRow, row.columns, config.activeDashboard, filteredDataOverride, dashboardFilteredData[index]])
159
236
 
160
- const isFilterRow = row.columns.some(
161
- col => col.widget && config.visualizations[col.widget]?.type === 'dashboardFilters'
162
- )
163
- const needsEqualHeight = !!row.equalHeight && !isFilterRow
237
+ const isFilterRow = columnDashboardConditionEvaluations.some(evaluation => {
238
+ const widgetKey = evaluation?.widget
239
+ return widgetKey && config.visualizations[widgetKey]?.type === 'dashboardFilters'
240
+ })
241
+ const needsEqualHeight = shouldEqualizeRow && !isFilterRow
164
242
 
165
243
  const show = useMemo(() => {
166
244
  if (row.toggle) {
@@ -171,6 +249,33 @@ const VisualizationRow: React.FC<VizRowProps> = ({
171
249
  }, [config.activeDashboard, toggledRow])
172
250
 
173
251
  const _data = dashboardFilteredData[index] || row.formattedData || []
252
+ const isMultiVizGroupRow = !!row.originalMultiVizColumn && !!filteredDataOverride
253
+ const rowDashboardCondition = useMemo(() => {
254
+ if (shouldIgnoreDashboardConditions || !row.dashboardCondition) {
255
+ return { matches: true, resolved: true }
256
+ }
257
+
258
+ return evaluateDashboardCondition(row.dashboardCondition, dashboardFilteredData[row.dashboardCondition.id])
259
+ }, [dashboardFilteredData, row.dashboardCondition, shouldIgnoreDashboardConditions])
260
+ const hasVisibleWidgetColumn = row.columns.some((_column, columnIndex) => {
261
+ if (!row.columns[columnIndex].width) return false
262
+
263
+ const widgetKey = columnDashboardConditionEvaluations[columnIndex]?.widget
264
+ if (!widgetKey) return false
265
+
266
+ const visualization = config.visualizations[widgetKey]
267
+ if (visualization?.type !== 'dashboardFilters') return true
268
+
269
+ return hasVisibleDashboardFiltersForIndexes(
270
+ config.dashboard.sharedFilters,
271
+ (visualization as DashboardFilters).sharedFilterIndexes
272
+ )
273
+ })
274
+
275
+ if (!rowDashboardCondition.matches || !hasVisibleWidgetColumn) {
276
+ return null
277
+ }
278
+
174
279
  const dataGroups =
175
280
  row.multiVizColumn &&
176
281
  _data.reduce((acc, dataRow) => {
@@ -187,7 +292,9 @@ const VisualizationRow: React.FC<VizRowProps> = ({
187
292
  const applyFilters = dashboardFilters.filter(v => !v.autoLoad).flatMap(v => v.sharedFilterIndexes)
188
293
  if (hasDashboardApplyBehavior(config.visualizations) && vizConfig.autoLoad) {
189
294
  return applyFilters.some(index => {
190
- const { queuedActive, active, subGrouping } = config.dashboard.sharedFilters[index]
295
+ const filter = config.dashboard.sharedFilters[index]
296
+ if (!filter) return false
297
+ const { queuedActive, active, subGrouping } = filter
191
298
  if (!active && !queuedActive) return true
192
299
  if (!queuedActive) return false
193
300
  // for nested dropdowns
@@ -236,6 +343,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
236
343
 
237
344
  return (
238
345
  <div
346
+ ref={rowRef}
239
347
  className={`row${needsEqualHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`}
240
348
  key={`row__${index}`}
241
349
  data-row-index={index}
@@ -245,11 +353,27 @@ const VisualizationRow: React.FC<VizRowProps> = ({
245
353
  )}
246
354
  {row.columns.map((col, colIndex) => {
247
355
  if (col.width) {
248
- if (!col.widget)
249
- return <div key={`row__${index}__col__${colIndex}`} className={`col-12 col-md-${col.width}`}></div>
356
+ const resolvedWidget = columnDashboardConditionEvaluations[colIndex]?.widget
357
+ const hasAuthoredWidgets = hasAuthoredWidgetEntries(col)
358
+ const hiddenByDashboardCondition = hasAuthoredWidgets && !resolvedWidget
359
+ if (!resolvedWidget) {
360
+ if (!hasAuthoredWidgets && (isMultiVizGroupRow || row.toggle)) {
361
+ return null
362
+ }
363
+
364
+ return (
365
+ <div
366
+ key={`row__${index}__col__${colIndex}`}
367
+ className={`col-12 col-md-${col.width}${
368
+ hiddenByDashboardCondition ? ' dashboard-condition-hidden' : ''
369
+ }`}
370
+ data-dashboard-condition-hidden={hiddenByDashboardCondition || undefined}
371
+ ></div>
372
+ )
373
+ }
250
374
 
251
375
  const visualizationConfig = getVizConfig(
252
- col.widget,
376
+ resolvedWidget,
253
377
  index,
254
378
  config,
255
379
  rawData,
@@ -262,9 +386,9 @@ const VisualizationRow: React.FC<VizRowProps> = ({
262
386
 
263
387
  const setsSharedFilter =
264
388
  config.dashboard.sharedFilters &&
265
- config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === col.widget).length > 0
389
+ config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === resolvedWidget).length > 0
266
390
  const setSharedFilterValue = setsSharedFilter
267
- ? config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === col.widget)[0].active
391
+ ? config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === resolvedWidget)[0].active
268
392
  : undefined
269
393
  const tableLink = (
270
394
  <a
@@ -300,9 +424,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
300
424
 
301
425
  const hiddenDashboardFilters =
302
426
  type === 'dashboardFilters' &&
303
- sharedFilterIndexes &&
304
- sharedFilterIndexes.filter(idx => config.dashboard.sharedFilters?.[idx]?.showDropdown === false).length ===
305
- sharedFilterIndexes.length
427
+ !hasVisibleDashboardFiltersForIndexes(config.dashboard.sharedFilters, sharedFilterIndexes)
306
428
 
307
429
  const vizWrapperClass = `col-12 col-md-${col.width}${!shouldShow ? ' d-none' : ''}${
308
430
  hideVisualization ? ' hide-parent-visualization' : ''
@@ -322,12 +444,12 @@ const VisualizationRow: React.FC<VizRowProps> = ({
322
444
  >
323
445
  {type === 'chart' && (
324
446
  <CdcChart
325
- key={col.widget}
447
+ key={resolvedWidget}
326
448
  config={visualizationConfig as ChartConfig}
327
449
  dashboardConfig={config}
328
450
  datasets={config.datasets}
329
451
  setConfig={newConfig => {
330
- updateChildConfig(col.widget, newConfig)
452
+ updateChildConfig(resolvedWidget, newConfig)
331
453
  }}
332
454
  setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
333
455
  isDashboard={true}
@@ -337,13 +459,15 @@ const VisualizationRow: React.FC<VizRowProps> = ({
337
459
  )}
338
460
  {type === 'map' && (
339
461
  <CdcMap
340
- key={col.widget}
462
+ key={resolvedWidget}
341
463
  config={visualizationConfig}
342
464
  setConfig={newConfig => {
343
- updateChildConfig(col.widget, newConfig)
465
+ updateChildConfig(resolvedWidget, newConfig)
344
466
  }}
345
467
  showLoader={false}
346
468
  setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
469
+ clearSharedFilter={setsSharedFilter ? clearSharedFilter : undefined}
470
+ hasActiveSharedFilter={setsSharedFilter ? hasActiveSharedFilter(resolvedWidget) : false}
347
471
  setSharedFilterValue={setSharedFilterValue}
348
472
  isDashboard={true}
349
473
  link={link}
@@ -353,10 +477,11 @@ const VisualizationRow: React.FC<VizRowProps> = ({
353
477
  )}
354
478
  {type === 'data-bite' && (
355
479
  <CdcDataBite
356
- key={col.widget}
480
+ key={resolvedWidget}
357
481
  config={visualizationConfig}
482
+ rawData={rawData?.[visualizationConfig.dataKey] || []}
358
483
  setConfig={newConfig => {
359
- updateChildConfig(col.widget, newConfig)
484
+ updateChildConfig(resolvedWidget, newConfig)
360
485
  }}
361
486
  isDashboard={true}
362
487
  isEditor={config.editing === true}
@@ -365,10 +490,11 @@ const VisualizationRow: React.FC<VizRowProps> = ({
365
490
  )}
366
491
  {type === 'waffle-chart' && (
367
492
  <CdcWaffleChart
368
- key={col.widget}
493
+ key={resolvedWidget}
369
494
  config={visualizationConfig}
495
+ rawData={rawData?.[visualizationConfig.dataKey] || []}
370
496
  setConfig={newConfig => {
371
- updateChildConfig(col.widget, newConfig)
497
+ updateChildConfig(resolvedWidget, newConfig)
372
498
  }}
373
499
  isDashboard={true}
374
500
  interactionLabel={interactionLabel}
@@ -376,22 +502,22 @@ const VisualizationRow: React.FC<VizRowProps> = ({
376
502
  )}
377
503
  {type === 'markup-include' && (
378
504
  <CdcMarkupInclude
379
- key={col.widget}
505
+ key={resolvedWidget}
380
506
  config={visualizationConfig}
381
507
  datasets={config.datasets}
382
508
  isDashboard={true}
383
509
  setConfig={newConfig => {
384
- updateChildConfig(col.widget, newConfig)
510
+ updateChildConfig(resolvedWidget, newConfig)
385
511
  }}
386
512
  interactionLabel={interactionLabel}
387
513
  />
388
514
  )}
389
515
  {type === 'filtered-text' && (
390
516
  <CdcFilteredText
391
- key={col.widget}
517
+ key={resolvedWidget}
392
518
  config={visualizationConfig}
393
519
  setConfig={newConfig => {
394
- updateChildConfig(col.widget, newConfig)
520
+ updateChildConfig(resolvedWidget, newConfig)
395
521
  }}
396
522
  isDashboard={true}
397
523
  />
@@ -399,9 +525,9 @@ const VisualizationRow: React.FC<VizRowProps> = ({
399
525
  {type === 'dashboardFilters' && (
400
526
  <DashboardSharedFilters
401
527
  setConfig={newConfig => {
402
- updateChildConfig(col.widget, newConfig)
528
+ updateChildConfig(resolvedWidget, newConfig)
403
529
  }}
404
- key={col.widget}
530
+ key={resolvedWidget}
405
531
  visualizationConfig={visualizationConfig as DashboardFilters}
406
532
  apiFilterDropdowns={apiFilterDropdowns}
407
533
  currentViewport={currentViewport}
@@ -410,11 +536,11 @@ const VisualizationRow: React.FC<VizRowProps> = ({
410
536
  )}
411
537
  {type === 'table' && (
412
538
  <DataTableStandAlone
413
- key={col.widget}
539
+ key={resolvedWidget}
414
540
  updateConfig={newConfig => {
415
- updateChildConfig(col.widget, newConfig)
541
+ updateChildConfig(resolvedWidget, newConfig)
416
542
  }}
417
- visualizationKey={col.widget}
543
+ visualizationKey={resolvedWidget}
418
544
  config={visualizationConfig as TableConfig}
419
545
  viewport={currentViewport}
420
546
  interactionLabel={interactionLabel}
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { describe, expect, it, vi } from 'vitest'
4
+ import { DashboardContext, DashboardDispatchContext, initialState } from '../../DashboardContext'
5
+ import VisualizationsPanel from './VisualizationsPanel'
6
+
7
+ vi.mock('../Widget/Widget', () => ({
8
+ default: ({ type }: { type: string }) => <div data-testid='creation-widget'>{type}</div>
9
+ }))
10
+
11
+ vi.mock('@cdc/core/components/AdvancedEditor', () => ({
12
+ default: () => <div data-testid='advanced-editor' />
13
+ }))
14
+
15
+ describe('VisualizationsPanel', () => {
16
+ it('does not expose filtered-text in dashboard creation surfaces', () => {
17
+ render(
18
+ <DashboardContext.Provider
19
+ value={{
20
+ ...initialState,
21
+ config: {
22
+ type: 'dashboard',
23
+ dashboard: { sharedFilters: [] },
24
+ datasets: {},
25
+ rows: [],
26
+ visualizations: {}
27
+ } as any,
28
+ outerContainerRef: vi.fn(),
29
+ setParentConfig: vi.fn(),
30
+ isDebug: false,
31
+ isEditor: true,
32
+ reloadURLData: vi.fn(),
33
+ loadAPIFilters: vi.fn(),
34
+ setAPIFilterDropdowns: vi.fn(),
35
+ setAPILoading: vi.fn(),
36
+ data: {}
37
+ }}
38
+ >
39
+ <DashboardDispatchContext.Provider value={vi.fn()}>
40
+ <VisualizationsPanel />
41
+ </DashboardDispatchContext.Provider>
42
+ </DashboardContext.Provider>
43
+ )
44
+
45
+ const creationTypes = screen.getAllByTestId('creation-widget').map(widget => widget.textContent)
46
+ expect(creationTypes).toContain('markup-include')
47
+ expect(creationTypes).not.toContain('filtered-text')
48
+ })
49
+ })
@@ -12,6 +12,8 @@ const VisualizationsPanel = () => {
12
12
  const [advancedEditing, setAdvancedEditing] = useState(false)
13
13
  const { config, isEditor } = useContext(DashboardContext)
14
14
  const dispatch = useContext(DashboardDispatchContext)
15
+ const createVisualization = (type, subType) =>
16
+ addVisualization(type, subType, { existingIds: Object.keys(config.visualizations || {}) })
15
17
  const loadConfig = incomingConfig => {
16
18
  const newConfig = !incomingConfig.multiDashboards
17
19
  ? incomingConfig
@@ -29,25 +31,24 @@ const VisualizationsPanel = () => {
29
31
  <p style={{ fontSize: '14px' }}>Click and drag an item onto the grid to add it to your dashboard.</p>
30
32
  <span className='subheading-3'>Chart</span>
31
33
  <div className='drag-grid'>
32
- <Widget addVisualization={() => addVisualization('chart', 'Bar')} type='Bar' />
33
- <Widget addVisualization={() => addVisualization('chart', 'Line')} type='Line' />
34
- <Widget addVisualization={() => addVisualization('chart', 'Pie')} type='Pie' />
35
- <Widget addVisualization={() => addVisualization('chart', 'Sankey')} type='Sankey' />
34
+ <Widget addVisualization={() => createVisualization('chart', 'Bar')} type='Bar' />
35
+ <Widget addVisualization={() => createVisualization('chart', 'Line')} type='Line' />
36
+ <Widget addVisualization={() => createVisualization('chart', 'Pie')} type='Pie' />
37
+ <Widget addVisualization={() => createVisualization('chart', 'Sankey')} type='Sankey' />
36
38
  </div>
37
39
  <span className='subheading-3'>Map</span>
38
40
  <div className='drag-grid'>
39
- <Widget addVisualization={() => addVisualization('map', 'us')} type='us' />
40
- <Widget addVisualization={() => addVisualization('map', 'world')} type='world' />
41
- <Widget addVisualization={() => addVisualization('map', 'single-state')} type='single-state' />
41
+ <Widget addVisualization={() => createVisualization('map', 'us')} type='us' />
42
+ <Widget addVisualization={() => createVisualization('map', 'world')} type='world' />
43
+ <Widget addVisualization={() => createVisualization('map', 'single-state')} type='single-state' />
42
44
  </div>
43
45
  <span className='subheading-3'>Misc.</span>
44
46
  <div className='drag-grid'>
45
- <Widget addVisualization={() => addVisualization('data-bite', '')} type='data-bite' />
46
- <Widget addVisualization={() => addVisualization('waffle-chart', '')} type='waffle-chart' />
47
- <Widget addVisualization={() => addVisualization('markup-include', '')} type='markup-include' />
48
- <Widget addVisualization={() => addVisualization('filtered-text', '')} type='filtered-text' />
49
- <Widget addVisualization={() => addVisualization('dashboardFilters', '')} type='dashboardFilters' />
50
- <Widget addVisualization={() => addVisualization('table', '')} type='table' />
47
+ <Widget addVisualization={() => createVisualization('data-bite', '')} type='data-bite' />
48
+ <Widget addVisualization={() => createVisualization('waffle-chart', 'Waffle')} type='waffle-chart' />
49
+ <Widget addVisualization={() => createVisualization('markup-include', '')} type='markup-include' />
50
+ <Widget addVisualization={() => createVisualization('dashboardFilters', '')} type='dashboardFilters' />
51
+ <Widget addVisualization={() => createVisualization('table', '')} type='table' />
51
52
  </div>
52
53
  <AdvancedEditor
53
54
  loadConfig={loadConfig}