@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,238 +1,394 @@
1
- import { isUpdateNeeded, getDataURL, getNewFileName, filterUsedByDataUrl } from '../reloadURLHelpers'
2
- import { SharedFilter } from '../../types/SharedFilter'
3
-
4
- describe('isUpdateNeeded', () => {
5
- it('should return false when there are no filters and no query params', () => {
6
- const filters: SharedFilter[] = []
7
- const currentQueryParams = {}
8
- const newQueryParams = {}
9
- expect(isUpdateNeeded(filters, currentQueryParams, newQueryParams)).toBe(false)
10
- })
11
-
12
- it('should return true when there is a filter with type "urlfilter" and filterBy "File Name"', () => {
13
- const filters: SharedFilter[] = [{ type: 'urlfilter', active: 'someValue', filterBy: 'File Name' }]
14
- const currentQueryParams = {}
15
- const newQueryParams = {}
16
- expect(isUpdateNeeded(filters, currentQueryParams, newQueryParams)).toBe(true)
17
- })
18
-
19
- it('should return true when query params are different', () => {
20
- const filters: SharedFilter[] = []
21
- const currentQueryParams = { param1: 'value1' }
22
- const newQueryParams = { param1: 'value2' }
23
- expect(isUpdateNeeded(filters, currentQueryParams, newQueryParams)).toBe(true)
24
- })
25
-
26
- it('should return false when query params are the same', () => {
27
- const filters: SharedFilter[] = []
28
- const currentQueryParams = { param1: 'value1' }
29
- const newQueryParams = { param1: 'value1' }
30
- expect(isUpdateNeeded(filters, currentQueryParams, newQueryParams)).toBe(false)
31
- })
32
-
33
- it('should return false when filter type is "urlfilter" but filterBy is not "File Name"', () => {
34
- const filters: SharedFilter[] = [{ type: 'urlfilter', active: 'someValue', filterBy: 'Other' }]
35
- const currentQueryParams = {}
36
- const newQueryParams = {}
37
- expect(isUpdateNeeded(filters, currentQueryParams, newQueryParams)).toBe(false)
38
- })
39
-
40
- it('should return false when filter type is "urlfilter" and filterBy is "File Name" but active is an array', () => {
41
- const filters: SharedFilter[] = [{ type: 'urlfilter', active: ['someValue'], filterBy: 'File Name' }]
42
- const currentQueryParams = {}
43
- const newQueryParams = {}
44
- expect(isUpdateNeeded(filters, currentQueryParams, newQueryParams)).toBe(false)
45
- })
46
- })
47
-
48
- describe('getDataURL', () => {
49
- it('should return the base URL when there are no query parameters and no new file name', () => {
50
- const updatedQSParams = {}
51
- const dataUrl = new URL('https://example.com/path/to/file.csv')
52
- const newFileName = ''
53
- expect(getDataURL(updatedQSParams, dataUrl, newFileName)).toBe('https://example.com/path/to/file.csv')
54
- })
55
-
56
- it('should append query parameters correctly when they are strings and there is no new file name', () => {
57
- const updatedQSParams = { param1: 'value1', param2: 'value2' }
58
- const dataUrl = new URL('https://example.com/path/to/file.csv')
59
- const newFileName = ''
60
- expect(getDataURL(updatedQSParams, dataUrl, newFileName)).toBe(
61
- 'https://example.com/path/to/file.csv?param1="value1"&param2="value2"'
62
- )
63
- })
64
-
65
- it('should append query parameters correctly when they are arrays and there is no new file name', () => {
66
- const updatedQSParams = { param1: ['value1', 'value2'], param2: 'value3' }
67
- const dataUrl = new URL('https://example.com/path/to/file.csv')
68
- const newFileName = ''
69
- expect(getDataURL(updatedQSParams, dataUrl, newFileName)).toBe(
70
- 'https://example.com/path/to/file.csv?param1="value1"&param1="value2"&param2="value3"'
71
- )
72
- })
73
-
74
- it('should change the file name correctly when there are no query parameters but there is a new file name', () => {
75
- const updatedQSParams = {}
76
- const dataUrl = new URL('https://example.com/path/to/file.csv')
77
- const newFileName = 'newfile'
78
- expect(getDataURL(updatedQSParams, dataUrl, newFileName)).toBe('https://example.com/path/to/newfile.csv')
79
- })
80
-
81
- it('should change the file name and append query parameters correctly when they are strings and there is a new file name', () => {
82
- const updatedQSParams = { param1: 'value1', param2: 'value2' }
83
- const dataUrl = new URL('https://example.com/path/to/file.csv')
84
- const newFileName = 'newfile'
85
- expect(getDataURL(updatedQSParams, dataUrl, newFileName)).toBe(
86
- 'https://example.com/path/to/newfile.csv?param1="value1"&param2="value2"'
87
- )
88
- })
89
-
90
- it('should change the file name and append query parameters correctly when they are arrays and there is a new file name', () => {
91
- const updatedQSParams = { param1: ['value1', 'value2'], param2: 'value3' }
92
- const dataUrl = new URL('https://example.com/path/to/file.csv')
93
- const newFileName = 'newfile'
94
- expect(getDataURL(updatedQSParams, dataUrl, newFileName)).toBe(
95
- 'https://example.com/path/to/newfile.csv?param1="value1"&param1="value2"&param2="value3"'
96
- )
97
- })
98
-
99
- it('should filter out empty query parameters', () => {
100
- const currentQSParams = {
101
- $datakey: 'topic',
102
- $limit: '5000',
103
- 'TEST TEST': '"undefined"',
104
- AgeGroupSelector: '"undefined"',
105
- Category: '"undefined"',
106
- Indicator: '"undefined"',
107
- Location: '"undefined"',
108
- Year: '"undefined"'
109
- }
110
- const updatedQSParams = {
111
- AgeGroupSelector: undefined,
112
- Category: undefined,
113
- Indicator: undefined,
114
- Location: undefined,
115
- Year: undefined
116
- }
117
- const runtimeURL = `http://example.com/public?$datakey=topic&$limit=5000&Location="undefined"&Category="undefined"&Indicator="undefined"&Year="undefined"&AgeGroupSelector="undefined"`
118
- const dataUrl = new URL(runtimeURL, 'http://example.com')
119
- const newFileName = ''
120
- const dataUrlFinal = getDataURL({ ...currentQSParams, ...updatedQSParams }, dataUrl, newFileName)
121
- expect(dataUrlFinal).toBe('http://example.com/public?$datakey=topic&$limit=5000')
122
- updatedQSParams.Category = 'something'
123
- expect(getDataURL({ ...currentQSParams, ...updatedQSParams }, dataUrl, newFileName)).toBe(
124
- 'http://example.com/public?$datakey=topic&$limit=5000&Category="something"'
125
- )
126
- })
127
- })
128
-
129
- describe('getNewFileName', () => {
130
- it('should return the formatted fileName when filter matches datasetKey and has a fileName', () => {
131
- const newFileName = 'defaultFile'
132
- const filter = {
133
- datasetKey: 'dataset1',
134
- fileName: 'state_${query}',
135
- active: 'activeFilter',
136
- whitespaceReplacement: 'Replace With Underscore'
137
- }
138
- const datasetKey = 'dataset1'
139
- expect(getNewFileName(newFileName, filter, datasetKey)).toBe('State_ActiveFilter')
140
- })
141
-
142
- it('should return the active filter when filter matches datasetKey and does not have a fileName', () => {
143
- const newFileName = 'defaultFile'
144
- const filter = {
145
- datasetKey: 'dataset1',
146
- active: 'activeFilter',
147
- whitespaceReplacement: 'Keep Spaces'
148
- }
149
- const datasetKey = 'dataset1'
150
- expect(getNewFileName(newFileName, filter, datasetKey)).toBe('activeFilter')
151
- })
152
-
153
- it('should return the newFileName as is when filter does not match datasetKey', () => {
154
- const newFileName = 'defaultFile'
155
- const filter = {
156
- datasetKey: 'dataset2',
157
- fileName: 'state_${query}',
158
- active: 'activeFilter',
159
- whitespaceReplacement: 'Replace With Underscore'
160
- }
161
- const datasetKey = 'dataset1'
162
- expect(getNewFileName(newFileName, filter, datasetKey)).toBe('defaultFile')
163
- })
164
-
165
- it('should replace ${query} with the active filter when filter matches datasetKey, has a fileName, and includes ${query}', () => {
166
- const newFileName = 'defaultFile'
167
- const filter = {
168
- datasetKey: 'dataset1',
169
- fileName: 'state_${query}',
170
- active: 'activeFilter',
171
- whitespaceReplacement: 'Keep Spaces'
172
- }
173
- const datasetKey = 'dataset1'
174
- expect(getNewFileName(newFileName, filter, datasetKey)).toBe('State_ActiveFilter')
175
- })
176
-
177
- it('should handle whitespace replacement options correctly', () => {
178
- const newFileName = 'defaultFile'
179
- const filter = {
180
- datasetKey: 'dataset1',
181
- fileName: 'state_${query}',
182
- active: 'active Filter',
183
- whitespaceReplacement: 'Remove Spaces'
184
- }
185
- const datasetKey = 'dataset1'
186
- expect(getNewFileName(newFileName, filter, datasetKey)).toBe('State_ActiveFilter')
187
-
188
- filter.whitespaceReplacement = 'Keep Spaces'
189
- expect(getNewFileName(newFileName, filter, datasetKey)).toBe('State_Active Filter')
190
-
191
- filter.whitespaceReplacement = 'Replace With Underscore'
192
- expect(getNewFileName(newFileName, filter, datasetKey)).toBe('State_Active_Filter')
193
- })
194
- })
195
-
196
- describe('filterUsedByDataUrl', () => {
197
- const visualizations = {
198
- viz1: { dataKey: 'dataset1' },
199
- viz2: { dataKey: 'dataset2' },
200
- viz3: { dataKey: 'dataset1' }
201
- }
202
-
203
- it('should return true when filter has no usedBy property', () => {
204
- const filter = { datasetKey: 'dataset1' }
205
- const datasetKey = 'dataset1'
206
- expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(true)
207
- })
208
-
209
- it('should return true when filter has an empty usedBy array', () => {
210
- const filter = { usedBy: [], datasetKey: 'dataset1' }
211
- const datasetKey = 'dataset1'
212
- expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(true)
213
- })
214
-
215
- it('should return true when filter has usedBy array with visualization keys that match the datasetKey', () => {
216
- const filter = { usedBy: ['viz1', 'viz3'], datasetKey: 'dataset1' }
217
- const datasetKey = 'dataset1'
218
- expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(true)
219
- })
220
-
221
- it('should return false when filter has usedBy array with visualization keys that do not match the datasetKey', () => {
222
- const filter = { usedBy: ['viz2'], datasetKey: 'dataset1' }
223
- const datasetKey = 'dataset1'
224
- expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(false)
225
- })
226
-
227
- it('should return true when filter has usedBy array with a mix of matching and non-matching visualization keys', () => {
228
- const filter = { usedBy: ['viz1', 'viz2'], datasetKey: 'dataset1' }
229
- const datasetKey = 'dataset1'
230
- expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(true)
231
- })
232
-
233
- it('should return true when used by a row', () => {
234
- const filter = { usedBy: ['viz1', 'viz2', 2], datasetKey: 'dataset1' }
235
- const datasetKey = 'dataset1'
236
- expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [{}, {}, { dataKey: 'dataset1' }])).toBe(true)
237
- })
238
- })
1
+ import { isUpdateNeeded, getDataURL, getDatasetKeys, getNewFileName, filterUsedByDataUrl } from '../reloadURLHelpers'
2
+ import { SharedFilter } from '../../types/SharedFilter'
3
+
4
+ describe('isUpdateNeeded', () => {
5
+ it('should return false when there are no filters and no query params', () => {
6
+ const filters: SharedFilter[] = []
7
+ const currentQueryParams = {}
8
+ const newQueryParams = {}
9
+ expect(isUpdateNeeded(filters, currentQueryParams, newQueryParams)).toBe(false)
10
+ })
11
+
12
+ it('should return true when there is a filter with type "urlfilter" and filterBy "File Name"', () => {
13
+ const filters: SharedFilter[] = [{ type: 'urlfilter', active: 'someValue', filterBy: 'File Name' }]
14
+ const currentQueryParams = {}
15
+ const newQueryParams = {}
16
+ expect(isUpdateNeeded(filters, currentQueryParams, newQueryParams)).toBe(true)
17
+ })
18
+
19
+ it('should return true when query params are different', () => {
20
+ const filters: SharedFilter[] = []
21
+ const currentQueryParams = { param1: 'value1' }
22
+ const newQueryParams = { param1: 'value2' }
23
+ expect(isUpdateNeeded(filters, currentQueryParams, newQueryParams)).toBe(true)
24
+ })
25
+
26
+ it('should return false when query params are the same', () => {
27
+ const filters: SharedFilter[] = []
28
+ const currentQueryParams = { param1: 'value1' }
29
+ const newQueryParams = { param1: 'value1' }
30
+ expect(isUpdateNeeded(filters, currentQueryParams, newQueryParams)).toBe(false)
31
+ })
32
+
33
+ it('should return false when filter type is "urlfilter" but filterBy is not "File Name"', () => {
34
+ const filters: SharedFilter[] = [{ type: 'urlfilter', active: 'someValue', filterBy: 'Other' }]
35
+ const currentQueryParams = {}
36
+ const newQueryParams = {}
37
+ expect(isUpdateNeeded(filters, currentQueryParams, newQueryParams)).toBe(false)
38
+ })
39
+
40
+ it('should return false when filter type is "urlfilter" and filterBy is "File Name" but active is an array', () => {
41
+ const filters: SharedFilter[] = [{ type: 'urlfilter', active: ['someValue'], filterBy: 'File Name' }]
42
+ const currentQueryParams = {}
43
+ const newQueryParams = {}
44
+ expect(isUpdateNeeded(filters, currentQueryParams, newQueryParams)).toBe(false)
45
+ })
46
+ })
47
+
48
+ describe('getDataURL', () => {
49
+ it('should return the base URL when there are no query parameters and no new file name', () => {
50
+ const updatedQSParams = {}
51
+ const dataUrl = new URL('https://example.com/path/to/file.csv')
52
+ const newFileName = ''
53
+ expect(getDataURL(updatedQSParams, dataUrl, newFileName)).toBe('https://example.com/path/to/file.csv')
54
+ })
55
+
56
+ it('should append query parameters correctly when they are strings and there is no new file name', () => {
57
+ const updatedQSParams = { param1: 'value1', param2: 'value2' }
58
+ const dataUrl = new URL('https://example.com/path/to/file.csv')
59
+ const newFileName = ''
60
+ expect(getDataURL(updatedQSParams, dataUrl, newFileName)).toBe(
61
+ 'https://example.com/path/to/file.csv?param1="value1"&param2="value2"'
62
+ )
63
+ })
64
+
65
+ it('should append query parameters correctly when they are arrays and there is no new file name', () => {
66
+ const updatedQSParams = { param1: ['value1', 'value2'], param2: 'value3' }
67
+ const dataUrl = new URL('https://example.com/path/to/file.csv')
68
+ const newFileName = ''
69
+ expect(getDataURL(updatedQSParams, dataUrl, newFileName)).toBe(
70
+ 'https://example.com/path/to/file.csv?param1="value1"&param1="value2"&param2="value3"'
71
+ )
72
+ })
73
+
74
+ it('should change the file name correctly when there are no query parameters but there is a new file name', () => {
75
+ const updatedQSParams = {}
76
+ const dataUrl = new URL('https://example.com/path/to/file.csv')
77
+ const newFileName = 'newfile'
78
+ expect(getDataURL(updatedQSParams, dataUrl, newFileName)).toBe('https://example.com/path/to/newfile.csv')
79
+ })
80
+
81
+ it('should change the file name and append query parameters correctly when they are strings and there is a new file name', () => {
82
+ const updatedQSParams = { param1: 'value1', param2: 'value2' }
83
+ const dataUrl = new URL('https://example.com/path/to/file.csv')
84
+ const newFileName = 'newfile'
85
+ expect(getDataURL(updatedQSParams, dataUrl, newFileName)).toBe(
86
+ 'https://example.com/path/to/newfile.csv?param1="value1"&param2="value2"'
87
+ )
88
+ })
89
+
90
+ it('should change the file name and append query parameters correctly when they are arrays and there is a new file name', () => {
91
+ const updatedQSParams = { param1: ['value1', 'value2'], param2: 'value3' }
92
+ const dataUrl = new URL('https://example.com/path/to/file.csv')
93
+ const newFileName = 'newfile'
94
+ expect(getDataURL(updatedQSParams, dataUrl, newFileName)).toBe(
95
+ 'https://example.com/path/to/newfile.csv?param1="value1"&param1="value2"&param2="value3"'
96
+ )
97
+ })
98
+
99
+ it('should filter out empty query parameters', () => {
100
+ const currentQSParams = {
101
+ $datakey: 'topic',
102
+ $limit: '5000',
103
+ 'TEST TEST': '"undefined"',
104
+ AgeGroupSelector: '"undefined"',
105
+ Category: '"undefined"',
106
+ Indicator: '"undefined"',
107
+ Location: '"undefined"',
108
+ Year: '"undefined"'
109
+ }
110
+ const updatedQSParams = {
111
+ AgeGroupSelector: undefined,
112
+ Category: undefined,
113
+ Indicator: undefined,
114
+ Location: undefined,
115
+ Year: undefined
116
+ }
117
+ const runtimeURL = `http://example.com/public?$datakey=topic&$limit=5000&Location="undefined"&Category="undefined"&Indicator="undefined"&Year="undefined"&AgeGroupSelector="undefined"`
118
+ const dataUrl = new URL(runtimeURL, 'http://example.com')
119
+ const newFileName = ''
120
+ const dataUrlFinal = getDataURL({ ...currentQSParams, ...updatedQSParams }, dataUrl, newFileName)
121
+ expect(dataUrlFinal).toBe('http://example.com/public?$datakey=topic&$limit=5000')
122
+ updatedQSParams.Category = 'something'
123
+ expect(getDataURL({ ...currentQSParams, ...updatedQSParams }, dataUrl, newFileName)).toBe(
124
+ 'http://example.com/public?$datakey=topic&$limit=5000&Category="something"'
125
+ )
126
+ })
127
+ })
128
+
129
+ describe('getNewFileName', () => {
130
+ it('should return the formatted fileName when filter matches datasetKey and has a fileName', () => {
131
+ const newFileName = 'defaultFile'
132
+ const filter = {
133
+ datasetKey: 'dataset1',
134
+ fileName: 'state_${query}',
135
+ active: 'activeFilter',
136
+ whitespaceReplacement: 'Replace With Underscore'
137
+ }
138
+ const datasetKey = 'dataset1'
139
+ expect(getNewFileName(newFileName, filter, datasetKey)).toBe('State_ActiveFilter')
140
+ })
141
+
142
+ it('should return the active filter when filter matches datasetKey and does not have a fileName', () => {
143
+ const newFileName = 'defaultFile'
144
+ const filter = {
145
+ datasetKey: 'dataset1',
146
+ active: 'activeFilter',
147
+ whitespaceReplacement: 'Keep Spaces'
148
+ }
149
+ const datasetKey = 'dataset1'
150
+ expect(getNewFileName(newFileName, filter, datasetKey)).toBe('activeFilter')
151
+ })
152
+
153
+ it('should return the newFileName as is when filter does not match datasetKey', () => {
154
+ const newFileName = 'defaultFile'
155
+ const filter = {
156
+ datasetKey: 'dataset2',
157
+ fileName: 'state_${query}',
158
+ active: 'activeFilter',
159
+ whitespaceReplacement: 'Replace With Underscore'
160
+ }
161
+ const datasetKey = 'dataset1'
162
+ expect(getNewFileName(newFileName, filter, datasetKey)).toBe('defaultFile')
163
+ })
164
+
165
+ it('should replace ${query} with the active filter when filter matches datasetKey, has a fileName, and includes ${query}', () => {
166
+ const newFileName = 'defaultFile'
167
+ const filter = {
168
+ datasetKey: 'dataset1',
169
+ fileName: 'state_${query}',
170
+ active: 'activeFilter',
171
+ whitespaceReplacement: 'Keep Spaces'
172
+ }
173
+ const datasetKey = 'dataset1'
174
+ expect(getNewFileName(newFileName, filter, datasetKey)).toBe('State_ActiveFilter')
175
+ })
176
+
177
+ it('should handle whitespace replacement options correctly', () => {
178
+ const newFileName = 'defaultFile'
179
+ const filter = {
180
+ datasetKey: 'dataset1',
181
+ fileName: 'state_${query}',
182
+ active: 'active Filter',
183
+ whitespaceReplacement: 'Remove Spaces'
184
+ }
185
+ const datasetKey = 'dataset1'
186
+ expect(getNewFileName(newFileName, filter, datasetKey)).toBe('State_ActiveFilter')
187
+
188
+ filter.whitespaceReplacement = 'Keep Spaces'
189
+ expect(getNewFileName(newFileName, filter, datasetKey)).toBe('State_Active Filter')
190
+
191
+ filter.whitespaceReplacement = 'Replace With Underscore'
192
+ expect(getNewFileName(newFileName, filter, datasetKey)).toBe('State_Active_Filter')
193
+ })
194
+ })
195
+
196
+ describe('getDatasetKeys', () => {
197
+ it('includes datasets used only by dashboard conditions', () => {
198
+ const datasetKeys = getDatasetKeys({
199
+ datasets: {
200
+ conditionData: { data: [{ county: 'Adams County' }] },
201
+ unusedData: { data: [{ county: 'Clark County' }] }
202
+ },
203
+ visualizations: {
204
+ filters: { type: 'dashboardFilters' },
205
+ explainer: { type: 'markup-include' }
206
+ },
207
+ rows: [
208
+ {
209
+ columns: [
210
+ {
211
+ width: 12,
212
+ conditionalWidgets: [
213
+ {
214
+ widget: 'explainer',
215
+ dashboardCondition: {
216
+ id: 'has-data-condition',
217
+ datasetKey: 'conditionData',
218
+ operator: 'hasData'
219
+ }
220
+ },
221
+ {
222
+ widget: 'filters-incomplete',
223
+ dashboardCondition: {
224
+ id: 'filters-incomplete-condition',
225
+ operator: 'filtersIncomplete'
226
+ }
227
+ }
228
+ ]
229
+ }
230
+ ]
231
+ }
232
+ ]
233
+ } as any)
234
+
235
+ expect(datasetKeys).toEqual(['conditionData'])
236
+ })
237
+
238
+ it('can exclude datasets used only by dashboard conditions', () => {
239
+ const datasetKeys = getDatasetKeys(
240
+ {
241
+ datasets: {
242
+ conditionData: { data: [{ county: 'Adams County' }] },
243
+ contentData: { data: [{ county: 'Baker County' }] }
244
+ },
245
+ visualizations: {
246
+ chart: { type: 'chart', dataKey: 'contentData' }
247
+ },
248
+ rows: [
249
+ {
250
+ columns: [{ width: 12, widget: 'chart' }],
251
+ dashboardCondition: {
252
+ id: 'has-data-condition',
253
+ datasetKey: 'conditionData',
254
+ operator: 'hasData'
255
+ }
256
+ }
257
+ ]
258
+ } as any,
259
+ { includeDashboardConditionDatasetKeys: false }
260
+ )
261
+
262
+ expect(datasetKeys).toEqual(['contentData'])
263
+ })
264
+ })
265
+
266
+ describe('filterUsedByDataUrl', () => {
267
+ const visualizations = {
268
+ viz1: { dataKey: 'dataset1' },
269
+ viz2: { dataKey: 'dataset2' },
270
+ viz3: { dataKey: 'dataset1' }
271
+ }
272
+
273
+ it('should return true when filter has no usedBy property', () => {
274
+ const filter = { datasetKey: 'dataset1' }
275
+ const datasetKey = 'dataset1'
276
+ expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(true)
277
+ })
278
+
279
+ it('should return true when filter has an empty usedBy array', () => {
280
+ const filter = { usedBy: [], datasetKey: 'dataset1' }
281
+ const datasetKey = 'dataset1'
282
+ expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(true)
283
+ })
284
+
285
+ it('should return true when filter has usedBy array with visualization keys that match the datasetKey', () => {
286
+ const filter = { usedBy: ['viz1', 'viz3'], datasetKey: 'dataset1' }
287
+ const datasetKey = 'dataset1'
288
+ expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(true)
289
+ })
290
+
291
+ it('should return false when filter has usedBy array with visualization keys that do not match the datasetKey', () => {
292
+ const filter = { usedBy: ['viz2'], datasetKey: 'dataset1' }
293
+ const datasetKey = 'dataset1'
294
+ expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(false)
295
+ })
296
+
297
+ it('should return true when filter has usedBy array with a mix of matching and non-matching visualization keys', () => {
298
+ const filter = { usedBy: ['viz1', 'viz2'], datasetKey: 'dataset1' }
299
+ const datasetKey = 'dataset1'
300
+ expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [])).toBe(true)
301
+ })
302
+
303
+ it('should return true when used by a row', () => {
304
+ const filter = { usedBy: ['viz1', 'viz2', 2], datasetKey: 'dataset1' }
305
+ const datasetKey = 'dataset1'
306
+ expect(filterUsedByDataUrl(filter, datasetKey, visualizations, [{}, {}, { dataKey: 'dataset1' }])).toBe(true)
307
+ })
308
+
309
+ it('should return true when used by a dashboard condition owner target that matches the datasetKey', () => {
310
+ const filter = { usedBy: ['viz1'], datasetKey: 'dataset1' }
311
+ const rows = [
312
+ {
313
+ columns: [
314
+ {
315
+ width: 12,
316
+ conditionalWidgets: [
317
+ {
318
+ widget: 'viz1',
319
+ dashboardCondition: {
320
+ id: 'condition-1',
321
+ datasetKey: 'dataset1',
322
+ operator: 'hasData'
323
+ }
324
+ }
325
+ ]
326
+ }
327
+ ]
328
+ }
329
+ ]
330
+
331
+ expect(filterUsedByDataUrl(filter, 'dataset1', visualizations, rows as any)).toBe(true)
332
+ })
333
+
334
+ it('should return true when used by a row-level dashboard condition owner target', () => {
335
+ const filter = { usedBy: [0], datasetKey: 'dataset1' }
336
+ const rows = [
337
+ {
338
+ dashboardCondition: {
339
+ id: 'row-condition-1',
340
+ datasetKey: 'dataset1',
341
+ operator: 'hasData'
342
+ },
343
+ columns: []
344
+ }
345
+ ]
346
+
347
+ expect(filterUsedByDataUrl(filter, 'dataset1', visualizations, rows as any)).toBe(true)
348
+ })
349
+
350
+ it('should return true when used by a component condition owner target on a row-data row', () => {
351
+ const filter = { usedBy: [0], datasetKey: 'dataset1' }
352
+ const rows = [
353
+ {
354
+ dataKey: 'row-data',
355
+ columns: [
356
+ {
357
+ width: 12,
358
+ conditionalWidgets: [
359
+ {
360
+ widget: 'viz2',
361
+ dashboardCondition: {
362
+ id: 'component-condition-1',
363
+ datasetKey: 'dataset1',
364
+ operator: 'hasData'
365
+ }
366
+ }
367
+ ]
368
+ }
369
+ ]
370
+ }
371
+ ]
372
+
373
+ expect(filterUsedByDataUrl(filter, 'dataset1', visualizations, rows as any)).toBe(true)
374
+ })
375
+
376
+ it('should return false when used by an unrelated row target for a dashboard condition dataset', () => {
377
+ const filter = { usedBy: [1], datasetKey: 'dataset1' }
378
+ const rows = [
379
+ {
380
+ dashboardCondition: {
381
+ id: 'row-condition-1',
382
+ datasetKey: 'dataset1',
383
+ operator: 'hasData'
384
+ },
385
+ columns: []
386
+ },
387
+ {
388
+ columns: []
389
+ }
390
+ ]
391
+
392
+ expect(filterUsedByDataUrl(filter, 'dataset1', visualizations, rows as any)).toBe(false)
393
+ })
394
+ })
package/src/index.tsx CHANGED
@@ -9,13 +9,16 @@ import MultiDashboardWrapper from './CdcDashboard'
9
9
  let isEditor = window.location.href.includes('editor=true')
10
10
  let isDebug = window.location.href.includes('debug=true')
11
11
 
12
- let domContainer = document.getElementsByClassName('react-container')[0]
12
+ let domContainer = document.getElementsByClassName('react-container')[0] as HTMLElement & { coveConfig?: any }
13
+ let configUrl = domContainer.dataset.configUrl
14
+ let injectedConfig = domContainer.coveConfig
13
15
 
14
16
  ReactDOM.createRoot(domContainer).render(
15
17
  <React.StrictMode>
16
18
  <MultiDashboardWrapper
17
- configUrl={domContainer.attributes['data-config'].value}
18
- interactionLabel={domContainer.attributes['data-config'].value}
19
+ config={injectedConfig}
20
+ configUrl={injectedConfig ? undefined : configUrl}
21
+ interactionLabel={configUrl}
19
22
  isEditor={isEditor}
20
23
  isDebug={isDebug}
21
24
  />