@cdc/dashboard 4.26.2 → 4.26.3

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 (52) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcdashboard-vr9HZwRt.es.js +6 -0
  3. package/dist/cdcdashboard.js +53345 -49681
  4. package/examples/custom/css/respiratory.css +1 -1
  5. package/examples/data/data-with-metadata.json +18 -0
  6. package/examples/default.json +7 -36
  7. package/examples/private/inline-markup.json +775 -0
  8. package/examples/private/recent-update.json +1456 -0
  9. package/examples/private/toggle.json +10137 -0
  10. package/package.json +9 -9
  11. package/src/CdcDashboard.tsx +2 -1
  12. package/src/CdcDashboardComponent.tsx +47 -27
  13. package/src/_stories/Dashboard.DataSetup.stories.tsx +6 -1
  14. package/src/_stories/Dashboard.Pages.stories.tsx +22 -0
  15. package/src/_stories/Dashboard.stories.tsx +4406 -7
  16. package/src/_stories/_mock/tab-simple-filter.json +153 -0
  17. package/src/components/DashboardFilters/DashboardFilters.tsx +19 -3
  18. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +7 -4
  19. package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +1 -1
  20. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +1 -2
  21. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +8 -7
  22. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +8 -8
  23. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +1 -1
  24. package/src/components/DashboardFilters/dashboardfilter.styles.css +3 -3
  25. package/src/components/DataDesignerModal.tsx +2 -2
  26. package/src/components/Header/Header.tsx +27 -5
  27. package/src/components/Header/index.scss +1 -1
  28. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +6 -6
  29. package/src/components/Row.tsx +21 -0
  30. package/src/components/Toggle/toggle-style.css +7 -7
  31. package/src/components/VisualizationRow.tsx +12 -4
  32. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +1 -54
  33. package/src/components/VisualizationsPanel/visualizations-panel-styles.css +2 -2
  34. package/src/components/Widget/Widget.tsx +2 -2
  35. package/src/components/Widget/widget.styles.css +12 -12
  36. package/src/data/initial-state.js +1 -1
  37. package/src/helpers/addVisualization.ts +71 -0
  38. package/src/helpers/formatConfigBeforeSave.ts +1 -1
  39. package/src/helpers/getVizConfig.ts +13 -3
  40. package/src/helpers/iconHash.tsx +45 -36
  41. package/src/helpers/processDataLegacy.ts +19 -14
  42. package/src/helpers/tests/addVisualization.test.ts +52 -0
  43. package/src/helpers/tests/formatConfigBeforeSave.test.ts +81 -1
  44. package/src/scss/editor-panel.scss +1 -1
  45. package/src/scss/main.scss +164 -39
  46. package/src/store/dashboard.reducer.ts +1 -1
  47. package/src/test/CdcDashboard.test.jsx +2 -2
  48. package/src/test/CdcDashboardComponent.test.tsx +74 -0
  49. package/src/types/FilterStyles.ts +2 -1
  50. package/tests/fixtures/dashboard-config-with-metadata.json +89 -0
  51. package/vite.config.js +2 -2
  52. package/dist/cdcdashboard-Cf9_fbQf.es.js +0 -6
@@ -1,7 +1,7 @@
1
1
  import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
2
2
  import React, { useContext, useEffect, useMemo, useState } from 'react'
3
3
  import Toggle from './Toggle'
4
- import _ from 'lodash'
4
+ import cloneDeep from 'lodash/cloneDeep'
5
5
  import { ConfigRow } from '../types/ConfigRow'
6
6
  import CdcDataBite from '@cdc/data-bite/src/CdcDataBite'
7
7
  import CdcMap from '@cdc/map/src/CdcMapComponent'
@@ -140,6 +140,8 @@ const VisualizationRow: React.FC<VizRowProps> = ({
140
140
 
141
141
  // Equalize TP5 callout title heights and TP5 gauge message blocks for like visualizations in the same row
142
142
  useEffect(() => {
143
+ if (!row.equalHeight) return
144
+
143
145
  const rowElement = document.querySelector(`[data-row-index="${index}"]`)
144
146
  if (!rowElement) return
145
147
 
@@ -155,6 +157,11 @@ const VisualizationRow: React.FC<VizRowProps> = ({
155
157
  }
156
158
  }, [index, row, config, filteredDataOverride])
157
159
 
160
+ const isFilterRow = row.columns.some(
161
+ col => col.widget && config.visualizations[col.widget]?.type === 'dashboardFilters'
162
+ )
163
+ const needsEqualHeight = !!row.equalHeight && !isFilterRow
164
+
158
165
  const show = useMemo(() => {
159
166
  if (row.toggle) {
160
167
  return row.columns.map((col, i) => i === toggledRow)
@@ -201,7 +208,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
201
208
  )}
202
209
  {Object.keys(dataGroups).map(groupName => {
203
210
  const dataValue = dataGroups[groupName]
204
- const _row = _.cloneDeep(row) // clone the row to avoid mutating the original row
211
+ const _row = cloneDeep(row) // clone the row to avoid mutating the original row
205
212
  const originalMultiVizColumn = _row.multiVizColumn // store original value before clearing
206
213
  _row.multiVizColumn = undefined // reset the multiVizColumn to avoid passing it to the child components
207
214
  _row.originalMultiVizColumn = originalMultiVizColumn // store for footnote filtering
@@ -229,7 +236,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
229
236
 
230
237
  return (
231
238
  <div
232
- className={`row${row.equalHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`}
239
+ className={`row${needsEqualHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`}
233
240
  key={`row__${index}`}
234
241
  data-row-index={index}
235
242
  >
@@ -352,6 +359,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
352
359
  updateChildConfig(col.widget, newConfig)
353
360
  }}
354
361
  isDashboard={true}
362
+ isEditor={config.editing === true}
355
363
  interactionLabel={interactionLabel}
356
364
  />
357
365
  )}
@@ -363,7 +371,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
363
371
  updateChildConfig(col.widget, newConfig)
364
372
  }}
365
373
  isDashboard={true}
366
- interactionLabel={link}
374
+ interactionLabel={interactionLabel}
367
375
  />
368
376
  )}
369
377
  {type === 'markup-include' && (
@@ -1,66 +1,13 @@
1
1
  import { useContext, useState } from 'react'
2
- import type { AnyVisualization } from '@cdc/core/types/Visualization'
3
2
  import Widget from '../Widget/Widget'
4
3
  import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
5
- import { Table } from '@cdc/core/types/Table'
6
4
  import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
5
+ import { addVisualization } from '../../helpers/addVisualization'
7
6
  import { mapDataToConfig } from '../../helpers/mapDataToConfig'
8
7
  import './visualizations-panel-styles.css'
9
8
  import { MultiDashboardConfig } from '../../types/MultiDashboard'
10
9
  import { stripConfig } from '../../helpers/formatConfigBeforeSave'
11
10
 
12
- const addVisualization = (type, subType) => {
13
- const modalWillOpen = type !== 'markup-include'
14
- const newVisualizationConfig: Partial<AnyVisualization> = {
15
- filters: [],
16
- filterBehavior: 'Filter Change',
17
- newViz: type !== 'table',
18
- openModal: modalWillOpen,
19
- uid: type + Date.now(),
20
- type
21
- }
22
-
23
- switch (type) {
24
- case 'chart':
25
- newVisualizationConfig.visualizationType = subType
26
- break
27
- case 'map':
28
- newVisualizationConfig.general = {}
29
- newVisualizationConfig.general.geoType = subType
30
- break
31
- case 'data-bite' || 'waffle-chart' || 'filtered-text':
32
- newVisualizationConfig.visualizationType = type
33
- break
34
- case 'table':
35
- const tableConfig: Table = {
36
- label: 'Data Table',
37
- show: true,
38
- showDownloadUrl: false,
39
- showVertical: true,
40
- expanded: true,
41
- collapsible: true
42
- }
43
- newVisualizationConfig.table = tableConfig
44
- newVisualizationConfig.columns = {}
45
- newVisualizationConfig.dataFormat = {}
46
- newVisualizationConfig.visualizationType = type
47
- break
48
- case 'markup-include':
49
- newVisualizationConfig.visualizationType = type
50
- break
51
- case 'dashboardFilters': {
52
- newVisualizationConfig.sharedFilterIndexes = []
53
- newVisualizationConfig.visualizationType = type
54
- break
55
- }
56
- default:
57
- newVisualizationConfig.visualizationType = type
58
- break
59
- }
60
-
61
- return newVisualizationConfig
62
- }
63
-
64
11
  const VisualizationsPanel = () => {
65
12
  const [advancedEditing, setAdvancedEditing] = useState(false)
66
13
  const { config, isEditor } = useContext(DashboardContext)
@@ -1,10 +1,10 @@
1
1
  .visualizations-panel {
2
2
  background-color: #fff;
3
+ border-right: #c7c7c7 1px solid;
4
+ overflow-y: scroll;
3
5
  padding: 1em;
4
6
  width: var(--editorWidth);
5
- border-right: #c7c7c7 1px solid;
6
7
  z-index: 1;
7
- overflow-y: scroll;
8
8
 
9
9
  &.advanced-editor {
10
10
  width: 50vw;
@@ -105,11 +105,11 @@ const Widget = ({
105
105
  const url = changeDataLimit(dataset.dataUrl, 100)
106
106
  if (url) {
107
107
  fetchRemoteData(url)
108
- .then(responseData => {
108
+ .then(({ data: responseData }) => {
109
109
  // this sample data is temporary.
110
110
  // the HEADER component removes the data when you toggle to the main viz panel.
111
111
  // data will be cached only when it's loaded via dashboard preview.
112
- responseData.sample = true
112
+ ;(responseData as any).sample = true
113
113
  dispatch({ type: 'SET_DATA', payload: { ...data, [dataKey]: responseData } })
114
114
  })
115
115
  .catch(error => {
@@ -1,13 +1,13 @@
1
1
  .widget__toggle-title {
2
- display: flex;
3
- flex-direction: column;
4
- justify-content: center;
5
2
  align-items: center;
6
- width: 100%;
7
- height: 25%;
8
3
  background-color: #005eaa;
9
4
  color: white;
5
+ display: flex;
6
+ flex-direction: column;
7
+ height: 25%;
8
+ justify-content: center;
10
9
  padding: 5px;
10
+ width: 100%;
11
11
  }
12
12
 
13
13
  .widget__edit-title-icon {
@@ -19,37 +19,37 @@
19
19
  }
20
20
 
21
21
  .widget--toggle .widget__content .widget-menu + svg {
22
+ flex-grow: unset;
22
23
  height: 35px !important;
23
24
  width: 35px !important;
24
- flex-grow: unset;
25
25
  }
26
26
 
27
27
  .widget-menu {
28
28
  position: absolute;
29
- top: 6px;
30
29
  right: 6px;
30
+ top: 6px;
31
31
  }
32
32
 
33
33
  .widget-menu {
34
- display: flex;
35
34
  align-items: center;
35
+ display: flex;
36
36
  justify-content: space-between;
37
37
  }
38
38
 
39
39
  .btn-configure {
40
40
  background: none;
41
- width: 20px;
42
41
  height: 20px;
43
- padding: 0;
44
42
  margin: 0 5px;
43
+ padding: 0;
44
+ width: 20px;
45
45
  }
46
46
 
47
47
  .widget-menu-item {
48
+ cursor: pointer;
48
49
  display: block;
49
- width: 20px;
50
50
  height: 20px;
51
- cursor: pointer;
52
51
  user-select: none;
52
+ width: 20px;
53
53
  }
54
54
 
55
55
  .widget-menu-item svg {
@@ -4,7 +4,7 @@ export default {
4
4
  titleStyle: 'small',
5
5
  sharedFilters: []
6
6
  },
7
- rows: [[{ width: 12 }, {}, {}]],
7
+ rows: [{ columns: [{ width: 12 }] }],
8
8
  visualizations: {},
9
9
  table: {
10
10
  label: 'Data Table',
@@ -0,0 +1,71 @@
1
+ import type { AnyVisualization } from '@cdc/core/types/Visualization'
2
+ import type { Table } from '@cdc/core/types/Table'
3
+
4
+ export const addVisualization = (type, subType) => {
5
+ const modalWillOpen = type !== 'markup-include'
6
+ const newVisualizationConfig: Partial<AnyVisualization> = {
7
+ filters: [],
8
+ filterBehavior: 'Filter Change',
9
+ newViz: type !== 'table',
10
+ openModal: modalWillOpen,
11
+ uid: type + Date.now(),
12
+ type
13
+ }
14
+
15
+ switch (type) {
16
+ case 'chart':
17
+ newVisualizationConfig.visual = {
18
+ border: false,
19
+ borderColorTheme: false,
20
+ accent: false,
21
+ background: false,
22
+ hideBackgroundColor: false
23
+ }
24
+ newVisualizationConfig.visualizationType = subType
25
+ break
26
+ case 'map':
27
+ newVisualizationConfig.general = {}
28
+ newVisualizationConfig.general.geoType = subType
29
+ newVisualizationConfig.visual = {
30
+ border: false,
31
+ borderColorTheme: false,
32
+ accent: false,
33
+ background: false,
34
+ hideBackgroundColor: false
35
+ }
36
+ break
37
+ case 'data-bite':
38
+ case 'waffle-chart':
39
+ case 'filtered-text':
40
+ newVisualizationConfig.visualizationType = type
41
+ break
42
+ case 'table': {
43
+ const tableConfig: Table = {
44
+ label: 'Data Table',
45
+ show: true,
46
+ showDownloadUrl: false,
47
+ showVertical: true,
48
+ expanded: true,
49
+ collapsible: true
50
+ }
51
+ newVisualizationConfig.table = tableConfig
52
+ newVisualizationConfig.columns = {}
53
+ newVisualizationConfig.dataFormat = {}
54
+ newVisualizationConfig.visualizationType = type
55
+ break
56
+ }
57
+ case 'markup-include':
58
+ newVisualizationConfig.visualizationType = type
59
+ break
60
+ case 'dashboardFilters': {
61
+ newVisualizationConfig.sharedFilterIndexes = []
62
+ newVisualizationConfig.visualizationType = type
63
+ break
64
+ }
65
+ default:
66
+ newVisualizationConfig.visualizationType = type
67
+ break
68
+ }
69
+
70
+ return newVisualizationConfig
71
+ }
@@ -129,7 +129,7 @@ export const stripConfig = (configToStrip, isEditor = false) => {
129
129
  } else {
130
130
  delete strippedConfig.runtime
131
131
  delete strippedConfig.formattedData
132
- if (strippedConfig.dataUrl) {
132
+ if (strippedConfig.dataUrl && !isEditor) {
133
133
  delete strippedConfig.data
134
134
  }
135
135
  }
@@ -90,6 +90,9 @@ export const getVizConfig = (
90
90
 
91
91
  if (visualizationConfig.formattedData) visualizationConfig.originalFormattedData = visualizationConfig.formattedData
92
92
  const filteredVizData = filteredData?.[rowNumber] ?? filteredData?.[visualizationKey]
93
+ const dataKey = visualizationConfig.dataKey || 'backwards-compatibility'
94
+
95
+ visualizationConfig.dataMetadata = config.datasets[dataKey]?.dataMetadata || {}
93
96
 
94
97
  if (filteredVizData) {
95
98
  visualizationConfig.data = filteredVizData || []
@@ -97,9 +100,16 @@ export const getVizConfig = (
97
100
  visualizationConfig.formattedData = visualizationConfig.data
98
101
  }
99
102
  } else {
100
- const dataKey = visualizationConfig.dataKey || 'backwards-compatibility'
101
- // Markup-includes need data even when shared filters exist (for markup variables)
102
- const shouldClearData = sharedFilterColumns.length && visualizationConfig.type !== 'markup-include'
103
+ // Clear data for charts/maps when shared filters exist but filtered data
104
+ // hasn't arrived yet prevents rendering the full unfiltered dataset as DOM.
105
+ // Lighter types (data-bite, waffle-chart, filtered-text, markup-include) are
106
+ // excluded: they only compute scalars or single elements, and their editor
107
+ // panels need data to populate column dropdowns. Ideally data filters would
108
+ // apply synchronously before render, but they currently go through the same
109
+ // async loadAPIFilters pipeline as API filters, so filtered data isn't
110
+ // available on first render.
111
+ const heavyVizTypes = ['chart', 'map']
112
+ const shouldClearData = sharedFilterColumns.length && heavyVizTypes.includes(visualizationConfig.type)
103
113
  visualizationConfig.data = shouldClearData ? [] : data[dataKey] || []
104
114
  if (visualizationConfig.formattedData) {
105
115
  visualizationConfig.formattedData =
@@ -1,36 +1,45 @@
1
- import Icon from '@cdc/core/components/ui/Icon'
2
- import { AnyVisualization } from '@cdc/core/types/Visualization'
3
-
4
- export const iconHash = {
5
- 'data-bite': <Icon display='databite' base />,
6
- Bar: <Icon display='chartBar' base />,
7
- 'Spark Line': <Icon display='chartLine' />,
8
- 'Bump Chart': <Icon display='chartLine' />,
9
- 'waffle-chart': <Icon display='grid' base />,
10
- 'markup-include': <Icon display='code' base />,
11
- Line: <Icon display='chartLine' base />,
12
- Pie: <Icon display='chartPie' base />,
13
- us: <Icon display='mapUsa' base />,
14
- 'us-county': <Icon display='mapUsa' base />,
15
- world: <Icon display='mapWorld' base />,
16
- 'single-state': <Icon display='mapAl' base />,
17
- gear: <Icon display='gear' base />,
18
- gearMulti: <Icon display='gearMulti' base />,
19
- tools: <Icon display='tools' base />,
20
- 'filtered-text': <Icon display='filtered-text' base />,
21
- dashboardFilters: <Icon display='dashboardFilters' base />,
22
- table: <Icon display='table' base />,
23
- Sankey: <Icon display='sankey' base />
24
- }
25
-
26
- export const getIcon = (visualization: AnyVisualization) => {
27
- const { type, visualizationType, general } = visualization
28
- if (visualizationType) return iconHash[visualizationType]
29
- if (general?.geoType) {
30
- // for visualizations, mismatching state and state icon is not desired
31
- // so instead of showing alabama as the default state icon we show the US icon.
32
- if (general.geoType === 'single-state') return iconHash['us']
33
- return iconHash[general.geoType]
34
- }
35
- return iconHash[type]
36
- }
1
+ import Icon from '@cdc/core/components/ui/Icon'
2
+ import { AnyVisualization } from '@cdc/core/types/Visualization'
3
+
4
+ export const iconHash = {
5
+ 'data-bite': <Icon display='databite' base />,
6
+ Bar: <Icon display='chartBar' base />,
7
+ 'Spark Line': <Icon display='chartLine' />,
8
+ 'Bump Chart': <Icon display='chartLine' />,
9
+ 'waffle-chart': <Icon display='grid' base />,
10
+ 'markup-include': <Icon display='code' base />,
11
+ Line: <Icon display='chartLine' base />,
12
+ Pie: <Icon display='chartPie' base />,
13
+ us: <Icon display='mapUsa' base />,
14
+ 'us-county': <Icon display='mapUsa' base />,
15
+ world: <Icon display='mapWorld' base />,
16
+ 'single-state': <Icon display='mapAl' base />,
17
+ gear: <Icon display='gear' base />,
18
+ gearMulti: <Icon display='gearMulti' base />,
19
+ tools: <Icon display='tools' base />,
20
+ 'filtered-text': <Icon display='filtered-text' base />,
21
+ dashboardFilters: <Icon display='dashboardFilters' base />,
22
+ table: <Icon display='table' base />,
23
+ Sankey: <Icon display='sankey' base />,
24
+ Combo: <Icon display='chartBar' base />,
25
+ 'Scatter Plot': <Icon display='chartBar' base />,
26
+ 'Area Chart': <Icon display='chartLine' base />,
27
+ 'Deviation Bar': <Icon display='chartBar' base />,
28
+ 'Paired Bar': <Icon display='chartBar' base />,
29
+ 'Box Plot': <Icon display='chartBar' base />,
30
+ 'Forest Plot': <Icon display='chartBar' base />,
31
+ Forecasting: <Icon display='chartLine' base />,
32
+ 'Warming Stripes': <Icon display='chartBar' base />
33
+ }
34
+
35
+ export const getIcon = (visualization: AnyVisualization) => {
36
+ const { type, visualizationType, general } = visualization
37
+ if (visualizationType) return iconHash[visualizationType]
38
+ if (general?.geoType) {
39
+ // for visualizations, mismatching state and state icon is not desired
40
+ // so instead of showing alabama as the default state icon we show the US icon.
41
+ if (general.geoType === 'single-state') return iconHash['us']
42
+ return iconHash[general.geoType]
43
+ }
44
+ return iconHash[type]
45
+ }
@@ -1,14 +1,19 @@
1
- import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
2
- import { getFormattedData } from './getFormattedData'
3
-
4
- export const processDataLegacy = async (response: any) => {
5
- let dataset = response.formattedData || response.data
6
-
7
- if (response.dataUrl) {
8
- dataset = await fetchRemoteData(`${response.dataUrl}`)
9
-
10
- dataset = getFormattedData(dataset, response.dataDescription)
11
- }
12
-
13
- return dataset
14
- }
1
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
2
+ import { getFormattedData } from './getFormattedData'
3
+
4
+ export const processDataLegacy = async (
5
+ response: any
6
+ ): Promise<{ data: any[]; dataMetadata: Record<string, string> }> => {
7
+ let dataset = response.formattedData || response.data
8
+ let dataMetadata: Record<string, string> = {}
9
+
10
+ if (response.dataUrl) {
11
+ const result = await fetchRemoteData(`${response.dataUrl}`)
12
+ dataset = result.data
13
+ dataMetadata = result.dataMetadata
14
+
15
+ dataset = getFormattedData(dataset, response.dataDescription)
16
+ }
17
+
18
+ return { data: dataset, dataMetadata }
19
+ }
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { addVisualization } from '../addVisualization'
3
+
4
+ describe('addVisualization', () => {
5
+ it('creates chart visual settings with extra theme toggles disabled by default', () => {
6
+ vi.spyOn(Date, 'now').mockReturnValue(12345)
7
+
8
+ const visualization = addVisualization('chart', 'Bar')
9
+
10
+ expect(visualization).toMatchObject({
11
+ uid: 'chart12345',
12
+ type: 'chart',
13
+ visualizationType: 'Bar',
14
+ visual: {
15
+ border: false,
16
+ borderColorTheme: false,
17
+ accent: false,
18
+ background: false,
19
+ hideBackgroundColor: false
20
+ }
21
+ })
22
+ })
23
+
24
+ it('creates map visual settings with extra theme toggles disabled by default', () => {
25
+ vi.spyOn(Date, 'now').mockReturnValue(12345)
26
+
27
+ const visualization = addVisualization('map', 'single-state')
28
+
29
+ expect(visualization).toMatchObject({
30
+ uid: 'map12345',
31
+ type: 'map',
32
+ general: {
33
+ geoType: 'single-state'
34
+ },
35
+ visual: {
36
+ border: false,
37
+ borderColorTheme: false,
38
+ accent: false,
39
+ background: false,
40
+ hideBackgroundColor: false
41
+ }
42
+ })
43
+ })
44
+
45
+ it('preserves visualizationType for data-bite family visualizations', () => {
46
+ vi.spyOn(Date, 'now').mockReturnValue(12345)
47
+
48
+ expect(addVisualization('data-bite')).toMatchObject({ visualizationType: 'data-bite' })
49
+ expect(addVisualization('waffle-chart')).toMatchObject({ visualizationType: 'waffle-chart' })
50
+ expect(addVisualization('filtered-text')).toMatchObject({ visualizationType: 'filtered-text' })
51
+ })
52
+ })
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest'
2
- import { cleanSharedFilters } from '../formatConfigBeforeSave'
2
+ import { cleanSharedFilters, stripConfig } from '../formatConfigBeforeSave'
3
3
  import { DashboardConfig } from '../../types/DashboardConfig'
4
4
 
5
5
  describe('cleanSharedFilters', () => {
@@ -67,3 +67,83 @@ describe('cleanSharedFilters', () => {
67
67
  expect(config.dashboard.sharedFilters).toEqual([{ id: 1, type: 'urlfilter' }])
68
68
  })
69
69
  })
70
+
71
+ describe('stripConfig', () => {
72
+ it('removes inline data for non-dashboard URL-backed configs when isEditor is false', () => {
73
+ const config = {
74
+ type: 'bar',
75
+ dataUrl: '/api/data.csv',
76
+ data: [{ value: 10 }],
77
+ runtime: { loaded: true },
78
+ formattedData: [{ value: 10 }]
79
+ }
80
+
81
+ const stripped = stripConfig(config)
82
+
83
+ expect(stripped.data).toBeUndefined()
84
+ expect(stripped.dataUrl).toBe('/api/data.csv')
85
+ expect(stripped.runtime).toBeUndefined()
86
+ expect(stripped.formattedData).toBeUndefined()
87
+ })
88
+
89
+ it('preserves inline data for non-dashboard URL-backed configs when isEditor is true', () => {
90
+ const config = {
91
+ type: 'bar',
92
+ dataUrl: '/api/data.csv',
93
+ data: [{ value: 10 }],
94
+ runtime: { loaded: true },
95
+ formattedData: [{ value: 10 }]
96
+ }
97
+
98
+ const stripped = stripConfig(config, true)
99
+
100
+ expect(stripped.data).toEqual([{ value: 10 }])
101
+ expect(stripped.dataUrl).toBe('/api/data.csv')
102
+ expect(stripped.runtime).toBeUndefined()
103
+ expect(stripped.formattedData).toBeUndefined()
104
+ })
105
+
106
+ it('removes dashboard dataset data when dataset has dataUrl and isEditor is false', () => {
107
+ const config = {
108
+ type: 'dashboard',
109
+ dashboard: { sharedFilters: [] },
110
+ datasets: {
111
+ data_1: {
112
+ dataUrl: '/api/dashboard.csv',
113
+ data: [{ a: 1 }],
114
+ formattedData: [{ a: 1 }]
115
+ }
116
+ },
117
+ visualizations: {},
118
+ rows: []
119
+ } as any
120
+
121
+ const stripped = stripConfig(config)
122
+
123
+ expect(stripped.datasets.data_1.data).toBeUndefined()
124
+ expect(stripped.datasets.data_1.formattedData).toBeUndefined()
125
+ expect(stripped.datasets.data_1.dataUrl).toBe('/api/dashboard.csv')
126
+ })
127
+
128
+ it('preserves dashboard dataset data when dataset has dataUrl and isEditor is true', () => {
129
+ const config = {
130
+ type: 'dashboard',
131
+ dashboard: { sharedFilters: [] },
132
+ datasets: {
133
+ data_1: {
134
+ dataUrl: '/api/dashboard.csv',
135
+ data: [{ a: 1 }],
136
+ formattedData: [{ a: 1 }]
137
+ }
138
+ },
139
+ visualizations: {},
140
+ rows: []
141
+ } as any
142
+
143
+ const stripped = stripConfig(config, true)
144
+
145
+ expect(stripped.datasets.data_1.data).toEqual([{ a: 1 }])
146
+ expect(stripped.datasets.data_1.formattedData).toBeUndefined()
147
+ expect(stripped.datasets.data_1.dataUrl).toBe('/api/dashboard.csv')
148
+ })
149
+ })
@@ -4,7 +4,7 @@
4
4
  // Shift down for top level Dashboard bar when in edit mode
5
5
  .layout-container,
6
6
  .editor-panel + .cdc-dashboard-inner-container,
7
- .editor-heading + .cdc-open-viz-module {
7
+ .editor-heading + .cove-visualization {
8
8
  position: relative;
9
9
  min-height: 80vh;
10
10
  }