@cdc/core 4.25.8 → 4.25.10

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 (117) hide show
  1. package/components/AdvancedEditor/AdvancedEditor.tsx +29 -8
  2. package/components/DataTable/DataTable.tsx +56 -38
  3. package/components/DataTable/components/ChartHeader.tsx +44 -14
  4. package/components/DataTable/components/ExpandCollapse.tsx +10 -1
  5. package/components/DataTable/components/MapHeader.tsx +24 -13
  6. package/components/DataTable/data-table.css +6 -0
  7. package/components/DataTable/helpers/chartCellMatrix.tsx +11 -8
  8. package/components/DataTable/helpers/mapCellMatrix.tsx +19 -1
  9. package/components/DownloadButton.tsx +40 -14
  10. package/components/EditorPanel/components/MarkupHighlightedTextField.tsx +227 -0
  11. package/components/EditorPanel/components/MarkupVariablesEditor.tsx +411 -0
  12. package/components/EditorPanel/components/PanelMarkup.tsx +59 -0
  13. package/components/ErrorBoundary.jsx +3 -1
  14. package/components/Filters/Filters.tsx +27 -20
  15. package/components/Filters/components/Tabs.tsx +1 -0
  16. package/components/Legend/Legend.Gradient.tsx +3 -6
  17. package/components/LegendShape.tsx +121 -3
  18. package/components/MediaControls.tsx +51 -3
  19. package/components/PaletteConversionModal.tsx +87 -0
  20. package/components/PaletteSelector/DeveloperPaletteRollback.tsx +114 -0
  21. package/components/PaletteSelector/PaletteSelector.css +51 -0
  22. package/components/PaletteSelector/PaletteSelector.tsx +112 -0
  23. package/components/PaletteSelector/index.ts +2 -0
  24. package/components/RichTooltip/RichTooltip.tsx +1 -0
  25. package/components/Table/Table.tsx +3 -1
  26. package/components/_stories/BlurStrokeTest.stories.tsx +1 -1
  27. package/components/_stories/DataTable.stories.tsx +1 -1
  28. package/components/_stories/Filters.stories.tsx +1 -1
  29. package/components/_stories/Footnotes.stories.tsx +1 -1
  30. package/components/_stories/Inputs.stories.tsx +1 -1
  31. package/components/_stories/MultiSelect.stories.tsx +3 -3
  32. package/components/_stories/NestedDropdown.stories.tsx +1 -1
  33. package/components/_stories/Table.stories.tsx +1 -1
  34. package/components/elements/_stories/Button.stories.tsx +1 -1
  35. package/components/elements/_stories/Card.stories.tsx +1 -1
  36. package/components/inputs/InputToggle.tsx +2 -0
  37. package/components/managers/DataDesigner.tsx +10 -9
  38. package/components/managers/_stories/DataDesigner.stories.tsx +1 -1
  39. package/components/ui/Tooltip.tsx +2 -1
  40. package/components/ui/_stories/Accordion.stories.tsx +1 -1
  41. package/components/ui/_stories/ColorPaletteMigration.stories.mdx +275 -0
  42. package/components/ui/_stories/Colors.stories.tsx +330 -0
  43. package/components/ui/_stories/IconGallery.stories.tsx +316 -0
  44. package/components/ui/_stories/Title.stories.tsx +1 -1
  45. package/contexts/EditorContext.ts +18 -0
  46. package/contexts/editor.actions.ts +28 -0
  47. package/contexts/editor.reducer.ts +94 -0
  48. package/data/chartColorPalettes.ts +118 -0
  49. package/data/colorPalettes.ts +9 -0
  50. package/data/mapColorPalettes.ts +45 -0
  51. package/data/sharedPalettes.ts +50 -0
  52. package/dist/cove-main.css +14 -11
  53. package/dist/cove-main.css.map +1 -1
  54. package/generateViteConfig.js +80 -0
  55. package/helpers/addValuesToFilters.ts +2 -3
  56. package/helpers/cloneConfig.ts +31 -0
  57. package/helpers/configDataHelpers.ts +128 -0
  58. package/helpers/configHelpers.ts +27 -0
  59. package/helpers/constants.ts +5 -2
  60. package/helpers/coveUpdateWorker.ts +13 -3
  61. package/helpers/filterColorPalettes.ts +152 -0
  62. package/helpers/generateColorsArray.ts +13 -0
  63. package/helpers/getColorPaletteVersion.ts +33 -0
  64. package/helpers/getPaletteAccessor.ts +18 -0
  65. package/helpers/markupProcessor.ts +205 -0
  66. package/helpers/metrics/helpers.ts +42 -19
  67. package/helpers/metrics/types.ts +48 -9
  68. package/helpers/metrics/utils.ts +34 -0
  69. package/helpers/palettes/colorDistributions.ts +56 -0
  70. package/helpers/palettes/migratePaletteName.ts +150 -0
  71. package/helpers/palettes/standardizePaletteNames.ts +77 -0
  72. package/helpers/palettes/utils.ts +267 -0
  73. package/helpers/queryStringUtils.ts +13 -0
  74. package/helpers/testing.ts +345 -0
  75. package/helpers/tests/addValuesToFilters.test.ts +1 -2
  76. package/helpers/tests/generateColorsArray.test.ts +24 -0
  77. package/helpers/tests/markupProcessor.test.ts +538 -0
  78. package/helpers/tests/testStandaloneBuild.ts +44 -0
  79. package/helpers/useMarkupVariables.ts +31 -0
  80. package/helpers/vegaConfig.ts +0 -1
  81. package/helpers/ver/4.24.10.ts +2 -1
  82. package/helpers/ver/4.24.11.ts +2 -1
  83. package/helpers/ver/4.24.3.ts +2 -1
  84. package/helpers/ver/4.24.4.ts +2 -1
  85. package/helpers/ver/4.24.5.ts +2 -1
  86. package/helpers/ver/4.24.7.ts +2 -1
  87. package/helpers/ver/4.24.9.ts +2 -1
  88. package/helpers/ver/4.25.1.ts +2 -1
  89. package/helpers/ver/4.25.10.ts +36 -0
  90. package/helpers/ver/4.25.3.ts +2 -1
  91. package/helpers/ver/4.25.4.ts +2 -1
  92. package/helpers/ver/4.25.6.ts +2 -1
  93. package/helpers/ver/4.25.7.ts +2 -1
  94. package/helpers/ver/4.25.8.ts +2 -1
  95. package/helpers/ver/4.25.9.ts +293 -0
  96. package/helpers/ver/tests/4.25.10.test.ts +204 -0
  97. package/helpers/ver/tests/4.25.8.test.ts +1 -1
  98. package/helpers/ver/tests/4.25.9.test.ts +51 -0
  99. package/hooks/useColorPalette.ts +79 -0
  100. package/package.json +12 -4
  101. package/styles/_global.scss +7 -5
  102. package/styles/base.scss +8 -5
  103. package/styles/v2/components/button.scss +4 -3
  104. package/styles/v2/components/editor.scss +2 -1
  105. package/styles/v2/layout/_data-table.scss +3 -2
  106. package/styles/v2/themes/_color-definitions.scss +18 -17
  107. package/testBuild.js +0 -0
  108. package/testing-setup.js +32 -0
  109. package/types/MarkupInclude.ts +6 -1
  110. package/types/MarkupVariable.ts +19 -0
  111. package/types/VizFilter.ts +1 -0
  112. package/vitest.config.ts +16 -0
  113. package/components/ui/_stories/Colors.stories.mdx +0 -220
  114. package/components/ui/_stories/IconGallery.stories.mdx +0 -14
  115. package/data/colorPalettes.js +0 -171
  116. package/helpers/formatConfigBeforeSave.ts +0 -135
  117. package/helpers/tests/formatConfigBeforeSave.test.ts +0 -68
@@ -1,4 +1,5 @@
1
1
  import _ from 'lodash'
2
+ import cloneConfig from '../cloneConfig'
2
3
  import { DashboardFilters } from '@cdc/dashboard/src/types/DashboardFilters'
3
4
  import { MultiDashboardConfig } from '@cdc/dashboard/src/types/MultiDashboard'
4
5
  import { AnyVisualization } from '../../types/Visualization'
@@ -130,7 +131,7 @@ const updateLogarithmicConfig = newConfig => {
130
131
  const update_4_24_7 = config => {
131
132
  const ver = '4.24.7'
132
133
 
133
- const newConfig = _.cloneDeep(config)
134
+ const newConfig = cloneConfig(config)
134
135
 
135
136
  mapUpdates(newConfig)
136
137
  dashboardFiltersMigrate(newConfig)
@@ -1,4 +1,5 @@
1
1
  import _ from 'lodash'
2
+ import cloneConfig from '../cloneConfig'
2
3
  import { AnyVisualization } from '../../types/Visualization'
3
4
  import { VizFilter } from '../../types/VizFilter'
4
5
  import versionNeedsUpdate from './versionNeedsUpdate'
@@ -49,7 +50,7 @@ const supportLineStyledLegend = newConfig => {
49
50
 
50
51
  const update_4_24_9 = config => {
51
52
  const ver = '4.24.9'
52
- const newConfig = _.cloneDeep(config)
53
+ const newConfig = cloneConfig(config)
53
54
  patchSingleStateZoom(newConfig)
54
55
  addIdsToVisFilters(newConfig)
55
56
  supportLineStyledLegend(config)
@@ -1,4 +1,5 @@
1
1
  import _ from 'lodash'
2
+ import cloneConfig from '../cloneConfig'
2
3
 
3
4
  const removeTerritoriesLabel = config => {
4
5
  if (config.general?.territoriesLabel) {
@@ -9,7 +10,7 @@ const removeTerritoriesLabel = config => {
9
10
 
10
11
  const update_4_25_1 = config => {
11
12
  const ver = '4.25.1'
12
- const newConfig = _.cloneDeep(config)
13
+ const newConfig = cloneConfig(config)
13
14
  removeTerritoriesLabel(newConfig)
14
15
  newConfig.version = ver
15
16
  return newConfig
@@ -0,0 +1,36 @@
1
+ import cloneConfig from '../cloneConfig'
2
+ import { DashboardConfig } from '@cdc/dashboard/src/types/DashboardConfig'
3
+
4
+ const migrateMarkupVariables = config => {
5
+ if (config.type === 'markup-include') {
6
+ // Move markupVariables from contentEditor to root level
7
+ if (config.contentEditor?.markupVariables) {
8
+ config.markupVariables = config.contentEditor.markupVariables
9
+ delete config.contentEditor.markupVariables
10
+ // Enable markup variables if any were found
11
+ if (config.markupVariables.length > 0) {
12
+ config.enableMarkupVariables = true
13
+ }
14
+ }
15
+ // Ensure markupVariables exists at root level
16
+ if (!config.markupVariables) {
17
+ config.markupVariables = []
18
+ }
19
+ }
20
+
21
+ if (config.type === 'dashboard') {
22
+ Object.values((config as DashboardConfig).visualizations).forEach(visualization => {
23
+ migrateMarkupVariables(visualization)
24
+ })
25
+ }
26
+ }
27
+
28
+ const update_4_25_10 = config => {
29
+ const ver = '4.25.10'
30
+ const newConfig = cloneConfig(config)
31
+ migrateMarkupVariables(newConfig)
32
+ newConfig.version = ver
33
+ return newConfig
34
+ }
35
+
36
+ export default update_4_25_10
@@ -1,4 +1,5 @@
1
1
  import _ from 'lodash'
2
+ import cloneConfig from '../cloneConfig'
2
3
 
3
4
  const remapTableDownloadCSV = config => {
4
5
  if (config.general?.showDownloadButton !== undefined) {
@@ -32,7 +33,7 @@ const migrateAreaChart = config => {
32
33
 
33
34
  const update_4_25_3 = config => {
34
35
  const ver = '4.25.3'
35
- const newConfig = _.cloneDeep(config)
36
+ const newConfig = cloneConfig(config)
36
37
  handleVisualizations(newConfig)
37
38
  remapTableDownloadCSV(newConfig)
38
39
  migrateAreaChart(newConfig)
@@ -1,4 +1,5 @@
1
1
  import _ from 'lodash'
2
+ import cloneConfig from '../cloneConfig'
2
3
 
3
4
  export const makeChartLegendsUnified = config => {
4
5
  if (config.type === 'chart') {
@@ -99,7 +100,7 @@ export const moveFootnotesToVizLevel = config => {
99
100
 
100
101
  const update_4_25_4 = config => {
101
102
  const ver = '4.25.4'
102
- const newConfig = _.cloneDeep(config)
103
+ const newConfig = cloneConfig(config)
103
104
  makeChartLegendsUnified(newConfig)
104
105
  migrateTableGeneralSettings(newConfig)
105
106
  moveFootnotesToVizLevel(newConfig)
@@ -1,4 +1,5 @@
1
1
  import _ from 'lodash'
2
+ import cloneConfig from '../cloneConfig'
2
3
 
3
4
  // *NOTE: This ends support for only showing the top prefix
4
5
  export const changeOnlyShowTopSuffixToInlineLabel = config => {
@@ -27,7 +28,7 @@ export const changeOnlyShowTopSuffixToInlineLabel = config => {
27
28
 
28
29
  const update_4_25_6 = config => {
29
30
  const ver = '4.25.6'
30
- const newConfig = _.cloneDeep(config)
31
+ const newConfig = cloneConfig(config)
31
32
  changeOnlyShowTopSuffixToInlineLabel(newConfig)
32
33
  newConfig.version = ver
33
34
  return newConfig
@@ -1,4 +1,5 @@
1
1
  import _ from 'lodash'
2
+ import cloneConfig from '../cloneConfig'
2
3
 
3
4
  export const updatePreliminaryDataSeriesKeys = config => {
4
5
  if (config.type === 'chart') {
@@ -17,7 +18,7 @@ export const updatePreliminaryDataSeriesKeys = config => {
17
18
 
18
19
  const update_4_25_7 = config => {
19
20
  const ver = '4.25.7'
20
- const newConfig = _.cloneDeep(config)
21
+ const newConfig = cloneConfig(config)
21
22
  updatePreliminaryDataSeriesKeys(newConfig)
22
23
  newConfig.version = ver
23
24
  return newConfig
@@ -1,4 +1,5 @@
1
1
  import _ from 'lodash'
2
+ import cloneConfig from '../cloneConfig'
2
3
 
3
4
  export const updateAxisColors = config => {
4
5
  if (config.type === 'chart') {
@@ -51,7 +52,7 @@ export const updateStatePickedToStatesPicked = config => {
51
52
 
52
53
  const update_4_25_8 = config => {
53
54
  const ver = '4.25.8'
54
- const newConfig = _.cloneDeep(config)
55
+ const newConfig = cloneConfig(config)
55
56
  updateAxisColors(newConfig)
56
57
  updateStatePickedToStatesPicked(newConfig)
57
58
  newConfig.version = ver
@@ -0,0 +1,293 @@
1
+ import _ from 'lodash'
2
+ import { getFallbackColorPalette } from '../palettes/utils'
3
+ import { newChartPaletteNames, newMapPaletteNames } from '../palettes/standardizePaletteNames'
4
+ import cloneConfig from '../cloneConfig'
5
+ import { DashboardConfig } from '@cdc/dashboard/src/types/DashboardConfig'
6
+
7
+ const addMissingDataFormatFields = (config) => {
8
+ if (config.type === 'chart' && config.visualizationType === 'Pie') {
9
+ // if we're missing the show pie percent field
10
+ if (config.data?.showPiePercent === undefined) {
11
+ config.data = config.data || {}
12
+ config.data.showPiePercent = false
13
+ }
14
+ }
15
+
16
+ if (config.type === 'dashboard') {
17
+ Object.values(config.visualizations).forEach(visualization => {
18
+ addMissingDataFormatFields(visualization)
19
+ })
20
+ }
21
+ }
22
+
23
+ const renameOriginalMapPalettes = config => {
24
+ if (config.general?.palette?.name && newMapPaletteNames[config.general.palette.name]) {
25
+ config.general.palette.name = newMapPaletteNames[config.general.palette.name]
26
+ }
27
+ }
28
+
29
+ const renameOriginalChartPalettes = config => {
30
+ if (config.general?.palette?.name && newChartPaletteNames[config.general.palette.name]) {
31
+ config.general.palette.name = newChartPaletteNames[config.general.palette.name]
32
+ }
33
+ }
34
+
35
+ const saveBackup = config => {
36
+ config.general = config.general || {}
37
+ config.general.palette = config.general.palette || {}
38
+
39
+ if (!config.general.palette) {
40
+ config.general = config.general || {}
41
+ config.general.palette = config.general.palette || {}
42
+ config.general.backups = config.general.palette.backups || []
43
+ config.general.palette.version = ''
44
+ }
45
+ const version = config?.general?.palette?.version || '1.0'
46
+ // Save a backup and set version to 1.0 for legacy palette names
47
+ if (version === '1.0' || !version) {
48
+ config.general.palette.version = '1.0'
49
+ config.general.palette.backups = config.general.palette.backups || []
50
+ config.general.palette.backups.push({
51
+ name: config.general.palette.name,
52
+ version: '1.0',
53
+ isReversed: config.general.palette.isReversed
54
+ })
55
+ }
56
+ }
57
+
58
+ // On maps move config.color to config.general.colorPalettes.colorName
59
+ const movePaletteName = config => {
60
+ if (config.type === 'map') {
61
+ // Move config.color to a normalized area...
62
+ if (config.color) {
63
+ config.general = config.general || {}
64
+ config.general.palette = config.general.palette || {}
65
+ config.general.palette.name = config.color
66
+ const version = config.general.palette.version
67
+ }
68
+
69
+ // Rename default palette names to new standardized names in mapColorPalettes.ts
70
+ renameOriginalMapPalettes(config)
71
+ }
72
+
73
+ if (config.type === 'chart') {
74
+ config.general = config.general || {}
75
+ config.general.palette = config.general.palette || {}
76
+
77
+ // Only set palette name if it doesn't already exist (avoid overriding new configs)
78
+ if (!config.general.palette.name) {
79
+ config.general.palette.name = config.palette || config.color || getFallbackColorPalette(config)
80
+ }
81
+
82
+ renameOriginalChartPalettes(config)
83
+ }
84
+
85
+ if (config.type === 'dashboard') {
86
+ Object.values(config.visualizations).forEach(visualization => {
87
+ movePaletteName(visualization)
88
+ })
89
+ }
90
+ }
91
+
92
+ // Move config.customColors to config.general.palette.customColors
93
+ const updateCustomColorsMigration = config => {
94
+ if (config.customColors) {
95
+ config.general = config.general || {}
96
+ config.general.palette = config.general.palette || {}
97
+ config.general.palette.customColors = config.customColors
98
+ }
99
+
100
+ if (config.type === 'dashboard') {
101
+ Object.values(config.visualizations).forEach(visualization => {
102
+ updateCustomColorsMigration(visualization)
103
+ })
104
+ }
105
+ }
106
+
107
+ const addDefaultPaletteVersion = config => {
108
+ if (config.type === 'map' || config.type === 'chart') {
109
+ config.general = config.general || {}
110
+ config.general.palette = config.general.palette || {}
111
+ if (!config.general.palette.version || config.general.palette.version === '1.0') {
112
+ config.general.palette.version = '1.0'
113
+ }
114
+ }
115
+ if (config.type === 'dashboard') {
116
+ Object.values(config.visualizations).forEach(visualization => {
117
+ addDefaultPaletteVersion(visualization)
118
+ })
119
+ }
120
+ }
121
+
122
+ export const changeSingleStateMapNoDataMessage = config => {
123
+ const changeMessage = config => {
124
+ const currentMessage = config.general.noStateFoundMessage || config.runtime?.noStateFoundMessage
125
+ delete config.general.noStateFoundMessage
126
+ delete config.runtime?.noStateFoundMessage
127
+ const isDefaultMessage = currentMessage === 'Map Unavailable'
128
+ // if message was customized, keep it.
129
+ config.general.noDataMessage = isDefaultMessage ? 'No State Selected' : currentMessage
130
+ }
131
+ if (config.type === 'map') {
132
+ changeMessage(config)
133
+ }
134
+ if (config.type === 'dashboard') {
135
+ Object.values((config as DashboardConfig).visualizations).forEach(visualization => {
136
+ if (visualization.type === 'map') {
137
+ changeMessage(visualization)
138
+ }
139
+ })
140
+ }
141
+ }
142
+
143
+ // Migration mapping for two-color palettes to divergent palettes
144
+ const twoColorPaletteMapping = {
145
+ 'monochrome-1': 'divergent_blue_purple',
146
+ 'monochrome-2': 'divergent_blue_purple',
147
+ 'monochrome-3': 'divergent_blue_purple',
148
+ 'monochrome-4': 'divergent_blue_purple',
149
+ 'monochrome-5': 'divergent_green_orange',
150
+ 'cool-1': 'divergent_blue_cyan',
151
+ 'cool-2': 'divergent_blue_cyan',
152
+ 'cool-3': 'divergent_blue_cyan',
153
+ 'cool-4': 'divergent_blue_cyan',
154
+ 'cool-5': 'divergent_blue_cyan',
155
+ 'warm-1': 'divergent_green_orange',
156
+ 'complementary-1': 'divergent_blue_orange',
157
+ 'complementary-2': 'divergent_blue_orange',
158
+ 'complementary-3': 'divergent_blue_orange',
159
+ 'complementary-4': 'divergent_blue_orange',
160
+ 'complementary-5': 'divergent_blue_orange'
161
+ }
162
+
163
+ // Migrate two-color palettes for paired bar and deviation charts
164
+ const migrateTwoColorPalettes = config => {
165
+ if (
166
+ config.type === 'chart' &&
167
+ (config.visualizationType === 'Paired Bar' || config.visualizationType === 'Deviation Bar')
168
+ ) {
169
+ if (config.twoColor?.palette && twoColorPaletteMapping[config.twoColor.palette]) {
170
+ // Update general palette from twoColor settings
171
+ config.general = config.general || {}
172
+ config.general.palette = config.general.palette || {}
173
+ config.general.palette.name = twoColorPaletteMapping[config.twoColor.palette]
174
+ config.general.palette.isReversed = config.twoColor.isPaletteReversed || false
175
+ }
176
+ }
177
+
178
+ if (config.type === 'dashboard') {
179
+ Object.values(config.visualizations).forEach(visualization => {
180
+ migrateTwoColorPalettes(visualization)
181
+ })
182
+ }
183
+ }
184
+
185
+ const normalizeForecastStageColors = config => {
186
+ if (config.type === 'chart' && config.series) {
187
+ const paletteVersion = config.general?.palette?.version === '2.0' ? 2 : 1
188
+
189
+ // Forecast palette migration map for v1 → v2 names (all lowercase-hyphen format)
190
+ const forecastPaletteMigrationMap = {
191
+ // Sequential Blue variants → sequential-blue
192
+ 'sequential-blue': 'sequential-blue',
193
+ 'sequential-blue-two': 'sequential-blue',
194
+ 'sequential-blue-three': 'sequential-blue',
195
+ 'sequential-blue-2-(mpx)': 'sequential-blue',
196
+ 'sequential-blue-2-mpx': 'sequential-blue',
197
+ // Sequential Orange variants → sequential-orange
198
+ 'sequential-orange': 'sequential-orange',
199
+ 'sequential-orange-two': 'sequential-orange',
200
+ 'sequential-orange-(mpx)': 'sequential-orange',
201
+ 'sequential-orange-mpx': 'sequential-orange',
202
+ // Other sequential palettes (no variants, just normalize)
203
+ 'sequential-green': 'sequential-green',
204
+ 'sequential-purple': 'sequential-purple',
205
+ 'sequential-teal': 'sequential-teal',
206
+ // Reverse variants - Sequential Blue
207
+ 'sequential-bluereverse': 'sequential-bluereverse',
208
+ 'sequential-blue-reverse': 'sequential-bluereverse',
209
+ 'sequential-blue-tworeverse': 'sequential-bluereverse',
210
+ 'sequential-blue-two-reverse': 'sequential-bluereverse',
211
+ 'sequential-blue-threereverse': 'sequential-bluereverse',
212
+ 'sequential-blue-three-reverse': 'sequential-bluereverse',
213
+ 'sequential-blue-2-(mpx)reverse': 'sequential-bluereverse',
214
+ 'sequential-blue-2-(mpx)-reverse': 'sequential-bluereverse',
215
+ 'sequential-blue-2-mpxreverse': 'sequential-bluereverse',
216
+ 'sequential-blue-2-mpx-reverse': 'sequential-bluereverse',
217
+ // Reverse variants - Sequential Orange
218
+ 'sequential-orangereverse': 'sequential-orangereverse',
219
+ 'sequential-orange-reverse': 'sequential-orangereverse',
220
+ 'sequential-orange-tworeverse': 'sequential-orangereverse',
221
+ 'sequential-orange-two-reverse': 'sequential-orangereverse',
222
+ 'sequential-orange-(mpx)reverse': 'sequential-orangereverse',
223
+ 'sequential-orange-(mpx)-reverse': 'sequential-orangereverse',
224
+ 'sequential-orange-mpxreverse': 'sequential-orangereverse',
225
+ 'sequential-orange-mpx-reverse': 'sequential-orangereverse',
226
+ // Reverse variants - Other sequential palettes
227
+ 'sequential-greenreverse': 'sequential-greenreverse',
228
+ 'sequential-green-reverse': 'sequential-greenreverse',
229
+ 'sequential-purplereverse': 'sequential-purplereverse',
230
+ 'sequential-purple-reverse': 'sequential-purplereverse',
231
+ 'sequential-tealreverse': 'sequential-tealreverse',
232
+ 'sequential-teal-reverse': 'sequential-tealreverse'
233
+ }
234
+
235
+ config.series.forEach(series => {
236
+ if (series.type === 'Forecasting' && series.stages) {
237
+ series.stages.forEach(stage => {
238
+ if (stage.color) {
239
+ // First, normalize to lowercase-hyphen format
240
+ let normalized = stage.color.toLowerCase().replace(/ /g, '-').replace(/_/g, '-')
241
+ // Then, migrate v1 palette names to v2 equivalents if applicable
242
+ stage.color = forecastPaletteMigrationMap[normalized] || normalized
243
+ }
244
+ })
245
+ }
246
+ })
247
+ }
248
+
249
+ if (config.type === 'dashboard') {
250
+ Object.values(config.visualizations).forEach(visualization => {
251
+ normalizeForecastStageColors(visualization)
252
+ })
253
+ }
254
+ }
255
+
256
+ const cleanConfig = config => {
257
+ // remove config.palette
258
+ if (config.palette) {
259
+ delete config.palette
260
+ }
261
+ // remove config.color
262
+ if (config.color) {
263
+ delete config.color
264
+ }
265
+ // remove config.customColors
266
+ if (config.customColors) {
267
+ delete config.customColors
268
+ }
269
+
270
+ if (config.type === 'dashboard') {
271
+ Object.values(config.visualizations).forEach(visualization => {
272
+ cleanConfig(visualization)
273
+ })
274
+ }
275
+ }
276
+
277
+ const update_4_25_9 = config => {
278
+ const ver = '4.25.9'
279
+ const newConfig = cloneConfig(config)
280
+ movePaletteName(newConfig)
281
+ updateCustomColorsMigration(newConfig)
282
+ migrateTwoColorPalettes(newConfig)
283
+ saveBackup(newConfig)
284
+ addDefaultPaletteVersion(newConfig)
285
+ normalizeForecastStageColors(newConfig)
286
+ cleanConfig(newConfig)
287
+ changeSingleStateMapNoDataMessage(newConfig)
288
+ addMissingDataFormatFields(newConfig)
289
+ newConfig.version = ver
290
+ return newConfig
291
+ }
292
+
293
+ export default update_4_25_9
@@ -0,0 +1,204 @@
1
+ import update_4_25_10 from '../4.25.10'
2
+ import { expect, describe, it } from 'vitest'
3
+
4
+ describe('update_4_25_10 - Markup Variables Migration', () => {
5
+ it('should migrate markup-include variables from contentEditor to root level', () => {
6
+ const config: any = {
7
+ type: 'markup-include',
8
+ version: '4.25.9',
9
+ contentEditor: {
10
+ markupVariables: [
11
+ { name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }
12
+ ]
13
+ }
14
+ }
15
+
16
+ const result = update_4_25_10(config)
17
+
18
+ expect(result.markupVariables).toEqual([
19
+ { name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }
20
+ ])
21
+ expect(result.contentEditor.markupVariables).toBeUndefined()
22
+ expect(result.enableMarkupVariables).toBe(true)
23
+ expect(result.version).toBe('4.25.10')
24
+ })
25
+
26
+ it('should enable markup variables flag when variables exist', () => {
27
+ const config: any = {
28
+ type: 'markup-include',
29
+ version: '4.25.9',
30
+ contentEditor: {
31
+ markupVariables: [
32
+ { name: 'Var1', tag: '{{var1}}', columnName: 'col1', conditions: [] },
33
+ { name: 'Var2', tag: '{{var2}}', columnName: 'col2', conditions: [] }
34
+ ]
35
+ }
36
+ }
37
+
38
+ const result = update_4_25_10(config)
39
+
40
+ expect(result.enableMarkupVariables).toBe(true)
41
+ expect(result.markupVariables).toHaveLength(2)
42
+ })
43
+
44
+ it('should initialize empty markupVariables array if none exist', () => {
45
+ const config: any = {
46
+ type: 'markup-include',
47
+ version: '4.25.9',
48
+ contentEditor: {}
49
+ }
50
+
51
+ const result = update_4_25_10(config)
52
+
53
+ expect(result.markupVariables).toEqual([])
54
+ expect(result.enableMarkupVariables).toBeUndefined()
55
+ })
56
+
57
+ it('should handle dashboard configs with nested visualizations', () => {
58
+ const config: any = {
59
+ type: 'dashboard',
60
+ version: '4.25.9',
61
+ visualizations: {
62
+ viz1: {
63
+ type: 'markup-include',
64
+ contentEditor: {
65
+ markupVariables: [
66
+ { name: 'Test', tag: '{{test}}', columnName: 'test', conditions: [] }
67
+ ]
68
+ }
69
+ },
70
+ viz2: {
71
+ type: 'chart',
72
+ title: 'Chart'
73
+ },
74
+ viz3: {
75
+ type: 'markup-include',
76
+ contentEditor: {
77
+ markupVariables: [
78
+ { name: 'Another', tag: '{{another}}', columnName: 'another', conditions: [] }
79
+ ]
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ const result = update_4_25_10(config)
86
+
87
+ expect(result.visualizations.viz1.markupVariables).toHaveLength(1)
88
+ expect(result.visualizations.viz1.enableMarkupVariables).toBe(true)
89
+ expect(result.visualizations.viz1.contentEditor.markupVariables).toBeUndefined()
90
+
91
+ expect(result.visualizations.viz3.markupVariables).toHaveLength(1)
92
+ expect(result.visualizations.viz3.enableMarkupVariables).toBe(true)
93
+ expect(result.visualizations.viz3.contentEditor.markupVariables).toBeUndefined()
94
+
95
+ // Chart should remain unchanged except version
96
+ expect(result.visualizations.viz2.title).toBe('Chart')
97
+ })
98
+
99
+ it('should not modify non-markup-include configs', () => {
100
+ const config: any = {
101
+ type: 'chart',
102
+ version: '4.25.9',
103
+ title: 'My Chart'
104
+ }
105
+
106
+ const result = update_4_25_10(config)
107
+
108
+ expect(result.markupVariables).toBeUndefined()
109
+ expect(result.enableMarkupVariables).toBeUndefined()
110
+ expect(result.title).toBe('My Chart')
111
+ expect(result.version).toBe('4.25.10')
112
+ })
113
+
114
+ it('should be idempotent - safe to run multiple times', () => {
115
+ const config: any = {
116
+ type: 'markup-include',
117
+ version: '4.25.9',
118
+ contentEditor: {
119
+ markupVariables: [
120
+ { name: 'State', tag: '{{state}}', columnName: 'state', conditions: [] }
121
+ ]
122
+ }
123
+ }
124
+
125
+ // Run migration twice
126
+ const result1 = update_4_25_10(config)
127
+ const result2 = update_4_25_10(result1)
128
+
129
+ // Should produce same result
130
+ expect(result2.markupVariables).toEqual(result1.markupVariables)
131
+ expect(result2.enableMarkupVariables).toBe(result1.enableMarkupVariables)
132
+ expect(result2.version).toBe('4.25.10')
133
+ })
134
+
135
+ it('should preserve other contentEditor properties', () => {
136
+ const config: any = {
137
+ type: 'markup-include',
138
+ version: '4.25.9',
139
+ contentEditor: {
140
+ markupVariables: [
141
+ { name: 'Test', tag: '{{test}}', columnName: 'test', conditions: [] }
142
+ ],
143
+ otherProperty: 'should remain',
144
+ nested: {
145
+ value: 123
146
+ }
147
+ }
148
+ }
149
+
150
+ const result = update_4_25_10(config)
151
+
152
+ expect(result.contentEditor.otherProperty).toBe('should remain')
153
+ expect(result.contentEditor.nested).toEqual({ value: 123 })
154
+ expect(result.contentEditor.markupVariables).toBeUndefined()
155
+ })
156
+
157
+ it('should handle empty markupVariables array in contentEditor', () => {
158
+ const config: any = {
159
+ type: 'markup-include',
160
+ version: '4.25.9',
161
+ contentEditor: {
162
+ markupVariables: []
163
+ }
164
+ }
165
+
166
+ const result = update_4_25_10(config)
167
+
168
+ expect(result.markupVariables).toEqual([])
169
+ expect(result.enableMarkupVariables).toBeUndefined() // Should not enable if array is empty
170
+ })
171
+
172
+ it('should preserve conditions in markup variables during migration', () => {
173
+ const config: any = {
174
+ type: 'markup-include',
175
+ version: '4.25.9',
176
+ contentEditor: {
177
+ markupVariables: [
178
+ {
179
+ name: 'Conditional Var',
180
+ tag: '{{conditional-var}}',
181
+ columnName: 'value',
182
+ conditions: [
183
+ { columnName: 'state', isOrIsNotEqualTo: 'is', value: 'California' },
184
+ { columnName: 'year', isOrIsNotEqualTo: 'is not', value: '2020' }
185
+ ],
186
+ addCommas: true,
187
+ hideOnNull: true
188
+ }
189
+ ]
190
+ }
191
+ }
192
+
193
+ const result = update_4_25_10(config)
194
+
195
+ expect(result.markupVariables[0].conditions).toHaveLength(2)
196
+ expect(result.markupVariables[0].conditions[0]).toEqual({
197
+ columnName: 'state',
198
+ isOrIsNotEqualTo: 'is',
199
+ value: 'California'
200
+ })
201
+ expect(result.markupVariables[0].addCommas).toBe(true)
202
+ expect(result.markupVariables[0].hideOnNull).toBe(true)
203
+ })
204
+ })
@@ -75,7 +75,7 @@ describe('4.25.8 update worker', () => {
75
75
 
76
76
  const updatedConfig = coveUpdateWorker(mockConfig)
77
77
 
78
- expect(updatedConfig.version).toBe('4.25.8')
78
+ // expect(updatedConfig.version).toBe('4.25.8')
79
79
  const mapViz = updatedConfig.visualizations.map1.general
80
80
  expect(mapViz.filterControlsStatePicked).toBeUndefined()
81
81
  expect(mapViz.filterControlsStatesPicked).toBe('State')