@cdc/dashboard 4.26.4 → 4.26.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONFIG.md +77 -30
- package/LICENSE +201 -0
- package/dist/cdcdashboard.js +49936 -49166
- package/examples/dashboard-conditions-filters-incomplete.json +221 -0
- package/examples/dashboard-missing-datasets-multi.json +174 -0
- package/examples/dashboard-missing-datasets-single.json +121 -0
- package/examples/dashboard-multi-dashboard-version-regression.json +146 -0
- package/examples/dashboard-shared-filter-row-delete-cleanup.json +186 -0
- package/examples/dashboard-stale-dataset-keys.json +181 -0
- package/examples/dashboard-tiered-filter-regression.json +190 -0
- package/examples/private/cfa-dashboard.json +651 -0
- package/examples/private/data-bite-wrap.json +6936 -0
- package/examples/private/multi-dash-fix.json +16963 -0
- package/examples/private/versions.json +41612 -0
- package/examples/us-map-filter-example.json +1074 -0
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +6 -2
- package/src/CdcDashboardComponent.tsx +178 -87
- package/src/DashboardCopyPasteContext.test.tsx +33 -0
- package/src/DashboardCopyPasteContext.tsx +48 -0
- package/src/_stories/Dashboard.EditorRegression.stories.tsx +72 -0
- package/src/_stories/Dashboard.Regression.stories.tsx +196 -0
- package/src/_stories/Dashboard.Zoom.stories.tsx +88 -0
- package/src/_stories/Dashboard.stories.tsx +294 -0
- package/src/_stories/FilteredTextMigrationComparison.stories.tsx +87 -0
- package/src/components/Column.test.tsx +176 -0
- package/src/components/Column.tsx +214 -13
- package/src/components/DashboardConditionModal.test.tsx +420 -0
- package/src/components/DashboardConditionModal.tsx +367 -0
- package/src/components/DashboardConditionSummary.tsx +59 -0
- package/src/components/DashboardEditors.tsx +8 -0
- package/src/components/DashboardFilters/DashboardFilters.test.tsx +139 -1
- package/src/components/DashboardFilters/DashboardFilters.tsx +192 -174
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.test.tsx +164 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +41 -2
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +180 -3
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +15 -32
- package/src/components/DashboardFilters/DashboardFiltersWrapper.test.tsx +142 -0
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +32 -27
- package/src/components/DashboardFilters/dashboardfilter.styles.css +42 -27
- package/src/components/DataDesignerModal.tsx +2 -1
- package/src/components/Grid.tsx +8 -4
- package/src/components/Header/Header.tsx +36 -17
- package/src/components/Row.test.tsx +228 -0
- package/src/components/Row.tsx +93 -18
- package/src/components/VisualizationRow.test.tsx +396 -0
- package/src/components/VisualizationRow.tsx +110 -35
- package/src/components/VisualizationsPanel/VisualizationsPanel.test.tsx +49 -0
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +14 -13
- package/src/components/Widget/Widget.test.tsx +218 -0
- package/src/components/Widget/Widget.tsx +119 -17
- package/src/components/Widget/widget.styles.css +31 -18
- package/src/components/dashboard-condition-modal.css +76 -0
- package/src/components/dashboard-condition-summary.css +87 -0
- package/src/helpers/addValuesToDashboardFilters.ts +3 -5
- package/src/helpers/addVisualization.ts +15 -4
- package/src/helpers/cloneDashboardWidget.ts +127 -0
- package/src/helpers/dashboardColumnWidgets.ts +99 -0
- package/src/helpers/dashboardConditionUi.ts +47 -0
- package/src/helpers/dashboardConditions.ts +200 -0
- package/src/helpers/dashboardFilterTargets.ts +156 -0
- package/src/helpers/filterData.ts +4 -9
- package/src/helpers/filterVisibility.ts +20 -0
- package/src/helpers/formatConfigBeforeSave.ts +2 -2
- package/src/helpers/getFilteredData.ts +18 -5
- package/src/helpers/getUpdateConfig.ts +43 -12
- package/src/helpers/getVizRowColumnLocator.ts +11 -1
- package/src/helpers/iconHash.tsx +9 -3
- package/src/helpers/mapDataToConfig.ts +31 -29
- package/src/helpers/reloadURLHelpers.ts +25 -5
- package/src/helpers/removeDashboardFilter.ts +33 -33
- package/src/helpers/tests/addVisualization.test.ts +53 -9
- package/src/helpers/tests/cloneDashboardWidget.test.ts +136 -0
- package/src/helpers/tests/dashboardColumnWidgets.test.ts +99 -0
- package/src/helpers/tests/dashboardConditionUi.test.ts +41 -0
- package/src/helpers/tests/dashboardConditions.test.ts +428 -0
- package/src/helpers/tests/formatConfigBeforeSave.test.ts +51 -0
- package/src/helpers/tests/getFilteredData.test.ts +265 -86
- package/src/helpers/tests/getUpdateConfig.test.ts +338 -0
- package/src/helpers/tests/reloadURLHelpers.test.ts +394 -238
- package/src/index.tsx +6 -3
- package/src/scss/grid.scss +249 -20
- package/src/scss/main.scss +108 -29
- package/src/store/dashboard.actions.ts +17 -4
- package/src/store/dashboard.reducer.test.ts +538 -0
- package/src/store/dashboard.reducer.ts +135 -22
- package/src/test/CdcDashboard.test.tsx +148 -0
- package/src/test/CdcDashboardComponent.test.tsx +935 -2
- package/src/types/ConfigRow.ts +15 -0
- package/src/types/DashboardFilters.ts +4 -0
- package/src/types/SharedFilter.ts +1 -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"¶m2="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"¶m1="value2"¶m2="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"¶m2="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"¶m1="value2"¶m2="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('
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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"¶m2="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"¶m1="value2"¶m2="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"¶m2="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"¶m1="value2"¶m2="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
|
-
|
|
18
|
-
|
|
19
|
+
config={injectedConfig}
|
|
20
|
+
configUrl={injectedConfig ? undefined : configUrl}
|
|
21
|
+
interactionLabel={configUrl}
|
|
19
22
|
isEditor={isEditor}
|
|
20
23
|
isDebug={isDebug}
|
|
21
24
|
/>
|