@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,86 +1,265 @@
1
- import _ from 'lodash'
2
- import { SharedFilter } from '../../types/SharedFilter'
3
- import { getFilteredData } from '../getFilteredData'
4
-
5
- describe('getFilteredData', () => {
6
- const sharedFilterDefaults = { values: [], showDropdown: true, id: 123, parents: [], key: 'key' }
7
- const data = {
8
- data1: [
9
- { id: 1, name: 'Alice', age: 25 },
10
- { id: 2, name: 'Bob', age: 30 },
11
- { id: 3, name: 'Charlie', age: 35 }
12
- ]
13
- }
14
- const state = {
15
- data,
16
- config: {
17
- dashboard: {
18
- sharedFilters: []
19
- },
20
- visualizations: {
21
- vizA: { dataKey: 'data1' }
22
- },
23
- rows: [{ dataKey: 'data1' }]
24
- }
25
- }
26
-
27
- it('should apply data to rows when there are no applicable filters', () => {
28
- expect(getFilteredData(state)).toEqual({ '0': data.data1 })
29
- })
30
-
31
- it('should filter visualizations', () => {
32
- const sharedFilters: SharedFilter[] = [
33
- {
34
- usedBy: ['vizA'],
35
- active: 'Alice',
36
- columnName: 'name',
37
- ...sharedFilterDefaults
38
- }
39
- ]
40
- const config = { ...state.config, dashboard: { sharedFilters } }
41
- expect(getFilteredData({ ...state, config })).toEqual({ '0': data.data1, vizA: [data.data1[0]] })
42
- })
43
-
44
- it('should filter visualizations and rows', () => {
45
- const sharedFilters: SharedFilter[] = [
46
- {
47
- usedBy: ['vizA', '0'],
48
- active: 'Alice',
49
- columnName: 'name',
50
- ...sharedFilterDefaults
51
- }
52
- ]
53
- const config = { ...state.config, dashboard: { sharedFilters } }
54
- expect(getFilteredData({ ...state, config })).toEqual({ '0': [data.data1[0]], vizA: [data.data1[0]] })
55
- })
56
-
57
- it('should use initialFilteredData', () => {
58
- const initialFilteredData = { newData: [data.data1[1]] }
59
- const sharedFilters: SharedFilter[] = [
60
- {
61
- usedBy: ['vizA', '0'],
62
- active: 'Alice',
63
- columnName: 'name',
64
- ...sharedFilterDefaults
65
- }
66
- ]
67
- const config = { ...state.config, dashboard: { sharedFilters } }
68
- expect(getFilteredData({ ...state, config }, initialFilteredData)).toEqual({ newData: [data.data1[1]], '0': [data.data1[0]], vizA: [data.data1[0]] })
69
- })
70
-
71
- it('should filter visualizations and rows', () => {
72
- const sharedFilters: SharedFilter[] = [
73
- {
74
- usedBy: ['vizA', '0'],
75
- active: 'Alice',
76
- columnName: 'name',
77
- ...sharedFilterDefaults
78
- }
79
- ]
80
- const dataOverride = _.cloneDeep(data)
81
- dataOverride.data1[0] = { id: 1, name: 'Alice', age: 30 }
82
- const config = { ...state.config, dashboard: { sharedFilters } }
83
- const filteredData = getFilteredData({ ...state, config }, undefined, dataOverride)
84
- expect(filteredData.vizA[0].age).toEqual(30)
85
- })
86
- })
1
+ import _ from 'lodash'
2
+ import { SharedFilter } from '../../types/SharedFilter'
3
+ import { getFilteredData } from '../getFilteredData'
4
+
5
+ describe('getFilteredData', () => {
6
+ const sharedFilterDefaults = { values: [], showDropdown: true, id: 123, parents: [], key: 'key' }
7
+ const data = {
8
+ data1: [
9
+ { id: 1, name: 'Alice', age: 25 },
10
+ { id: 2, name: 'Bob', age: 30 },
11
+ { id: 3, name: 'Charlie', age: 35 }
12
+ ]
13
+ }
14
+ const state = {
15
+ data,
16
+ config: {
17
+ dashboard: {
18
+ sharedFilters: []
19
+ },
20
+ visualizations: {
21
+ vizA: { dataKey: 'data1' }
22
+ },
23
+ rows: [{ dataKey: 'data1' }]
24
+ }
25
+ }
26
+
27
+ it('should apply data to rows when there are no applicable filters', () => {
28
+ expect(getFilteredData(state)).toEqual({ '0': data.data1 })
29
+ })
30
+
31
+ it('should filter visualizations', () => {
32
+ const sharedFilters: SharedFilter[] = [
33
+ {
34
+ usedBy: ['vizA'],
35
+ active: 'Alice',
36
+ columnName: 'name',
37
+ ...sharedFilterDefaults
38
+ }
39
+ ]
40
+ const config = { ...state.config, dashboard: { sharedFilters } }
41
+ expect(getFilteredData({ ...state, config })).toEqual({ '0': data.data1, vizA: [data.data1[0]] })
42
+ })
43
+
44
+ it('should filter visualizations and rows', () => {
45
+ const sharedFilters: SharedFilter[] = [
46
+ {
47
+ usedBy: ['vizA', '0'],
48
+ active: 'Alice',
49
+ columnName: 'name',
50
+ ...sharedFilterDefaults
51
+ }
52
+ ]
53
+ const config = { ...state.config, dashboard: { sharedFilters } }
54
+ expect(getFilteredData({ ...state, config })).toEqual({ '0': [data.data1[0]], vizA: [data.data1[0]] })
55
+ })
56
+
57
+ it('should use initialFilteredData', () => {
58
+ const initialFilteredData = { newData: [data.data1[1]] }
59
+ const sharedFilters: SharedFilter[] = [
60
+ {
61
+ usedBy: ['vizA', '0'],
62
+ active: 'Alice',
63
+ columnName: 'name',
64
+ ...sharedFilterDefaults
65
+ }
66
+ ]
67
+ const config = { ...state.config, dashboard: { sharedFilters } }
68
+ expect(getFilteredData({ ...state, config }, initialFilteredData)).toEqual({
69
+ newData: [data.data1[1]],
70
+ '0': [data.data1[0]],
71
+ vizA: [data.data1[0]]
72
+ })
73
+ })
74
+
75
+ it('should filter visualizations and rows', () => {
76
+ const sharedFilters: SharedFilter[] = [
77
+ {
78
+ usedBy: ['vizA', '0'],
79
+ active: 'Alice',
80
+ columnName: 'name',
81
+ ...sharedFilterDefaults
82
+ }
83
+ ]
84
+ const dataOverride = _.cloneDeep(data)
85
+ dataOverride.data1[0] = { id: 1, name: 'Alice', age: 30 }
86
+ const config = { ...state.config, dashboard: { sharedFilters } }
87
+ const filteredData = getFilteredData({ ...state, config }, undefined, dataOverride)
88
+ expect(filteredData.vizA[0].age).toEqual(30)
89
+ })
90
+
91
+ it('should apply shared filters with missing usedBy to visualizations and rows', () => {
92
+ const sharedFilters: SharedFilter[] = [
93
+ {
94
+ active: 'Alice',
95
+ columnName: 'name',
96
+ ...sharedFilterDefaults
97
+ }
98
+ ]
99
+ const config = { ...state.config, dashboard: { sharedFilters } }
100
+
101
+ expect(getFilteredData({ ...state, config })).toEqual({ '0': [data.data1[0]], vizA: [data.data1[0]] })
102
+ })
103
+
104
+ it('should apply shared filters with empty usedBy to visualizations and rows', () => {
105
+ const sharedFilters: SharedFilter[] = [
106
+ {
107
+ usedBy: [],
108
+ active: 'Alice',
109
+ columnName: 'name',
110
+ ...sharedFilterDefaults
111
+ }
112
+ ]
113
+ const config = { ...state.config, dashboard: { sharedFilters } }
114
+
115
+ expect(getFilteredData({ ...state, config })).toEqual({ '0': [data.data1[0]], vizA: [data.data1[0]] })
116
+ })
117
+
118
+ it('should keep explicit usedBy filters scoped to matching visualization and row targets', () => {
119
+ const sharedFilters: SharedFilter[] = [
120
+ {
121
+ usedBy: ['vizA'],
122
+ active: 'Alice',
123
+ columnName: 'name',
124
+ ...sharedFilterDefaults
125
+ },
126
+ {
127
+ usedBy: [0],
128
+ active: 'Bob',
129
+ columnName: 'name',
130
+ ...sharedFilterDefaults
131
+ }
132
+ ]
133
+ const config = { ...state.config, dashboard: { sharedFilters } }
134
+
135
+ expect(getFilteredData({ ...state, config })).toEqual({ '0': [data.data1[1]], vizA: [data.data1[0]] })
136
+ })
137
+
138
+ it('should precompute dashboard condition targets using row owner filters when data is configured on the row', () => {
139
+ const config = {
140
+ ...state.config,
141
+ rows: [
142
+ {
143
+ dataKey: 'data1',
144
+ dashboardCondition: {
145
+ id: 'row-condition-1',
146
+ datasetKey: 'data1',
147
+ operator: 'hasData'
148
+ },
149
+ columns: [
150
+ {
151
+ width: 12,
152
+ conditionalWidgets: [
153
+ {
154
+ widget: 'vizA',
155
+ dashboardCondition: {
156
+ id: 'column-condition-1',
157
+ datasetKey: 'data1',
158
+ operator: 'columnHasAnyValue',
159
+ columnName: 'name',
160
+ values: ['Alice']
161
+ }
162
+ }
163
+ ]
164
+ }
165
+ ]
166
+ }
167
+ ],
168
+ dashboard: {
169
+ sharedFilters: [
170
+ {
171
+ active: 'Alice',
172
+ columnName: 'name',
173
+ usedBy: [0],
174
+ ...sharedFilterDefaults
175
+ }
176
+ ]
177
+ }
178
+ }
179
+
180
+ expect(getFilteredData({ ...state, config } as any)).toEqual({
181
+ '0': [data.data1[0]],
182
+ 'row-condition-1': [data.data1[0]],
183
+ 'column-condition-1': [data.data1[0]]
184
+ })
185
+ })
186
+
187
+ it('should precompute component condition targets using widget owner filters when data is configured on the widget', () => {
188
+ const config = {
189
+ ...state.config,
190
+ rows: [
191
+ {
192
+ columns: [
193
+ {
194
+ width: 12,
195
+ conditionalWidgets: [
196
+ {
197
+ widget: 'vizA',
198
+ dashboardCondition: {
199
+ id: 'column-condition-1',
200
+ datasetKey: 'data1',
201
+ operator: 'columnHasAnyValue',
202
+ columnName: 'name',
203
+ values: ['Alice']
204
+ }
205
+ }
206
+ ]
207
+ }
208
+ ]
209
+ }
210
+ ],
211
+ dashboard: {
212
+ sharedFilters: [
213
+ {
214
+ active: 'Alice',
215
+ columnName: 'name',
216
+ usedBy: ['vizA'],
217
+ ...sharedFilterDefaults
218
+ }
219
+ ]
220
+ }
221
+ }
222
+
223
+ expect(getFilteredData({ ...state, config } as any)).toEqual({
224
+ 'column-condition-1': [data.data1[0]],
225
+ vizA: [data.data1[0]]
226
+ })
227
+ })
228
+
229
+ it('should clear stale dashboard condition data when a condition becomes unresolved', () => {
230
+ const config = {
231
+ ...state.config,
232
+ rows: [
233
+ {
234
+ dataKey: 'data1',
235
+ dashboardCondition: {
236
+ id: 'row-condition-1',
237
+ datasetKey: 'data1',
238
+ operator: 'hasData'
239
+ },
240
+ columns: [{ width: 12, widget: 'vizA' }]
241
+ }
242
+ ],
243
+ dashboard: {
244
+ sharedFilters: [
245
+ {
246
+ active: '',
247
+ columnName: 'name',
248
+ usedBy: [0],
249
+ ...sharedFilterDefaults
250
+ }
251
+ ]
252
+ }
253
+ }
254
+
255
+ const initialFilteredData = {
256
+ unrelatedCacheEntry: [data.data1[1]],
257
+ 'row-condition-1': [data.data1[0]]
258
+ }
259
+
260
+ expect(getFilteredData({ ...state, config } as any, initialFilteredData)).toEqual({
261
+ '0': [],
262
+ unrelatedCacheEntry: [data.data1[1]]
263
+ })
264
+ })
265
+ })
@@ -0,0 +1,338 @@
1
+ import { SharedFilter } from '../../types/SharedFilter'
2
+ import { getUpdateConfig } from '../getUpdateConfig'
3
+
4
+ describe('getUpdateConfig', () => {
5
+ const sharedFilterDefaults = { values: [], showDropdown: true, id: 123, parents: [], key: 'key' }
6
+ const data = {
7
+ data1: [
8
+ { id: 1, name: 'Alice', age: 25 },
9
+ { id: 2, name: 'Bob', age: 30 },
10
+ { id: 3, name: 'Charlie', age: 35 }
11
+ ]
12
+ }
13
+
14
+ const getConfig = (sharedFilters: SharedFilter[]) =>
15
+ ({
16
+ dashboard: { sharedFilters },
17
+ datasets: {
18
+ data1: { data: data.data1 }
19
+ },
20
+ visualizations: {
21
+ vizA: { dataKey: 'data1' }
22
+ },
23
+ rows: [{ dataKey: 'data1', data: data.data1, columns: [] }, { columns: [{ width: 12, widget: 'vizA' }] }]
24
+ } as any)
25
+
26
+ const getFilteredData = (sharedFilters: SharedFilter[]) => {
27
+ const [, filteredData] = getUpdateConfig({ data, filteredData: {} } as any)(getConfig(sharedFilters))
28
+ return filteredData
29
+ }
30
+
31
+ it('should precompute visualization and row data for shared filters with missing usedBy', () => {
32
+ const sharedFilters: SharedFilter[] = [
33
+ {
34
+ active: 'Alice',
35
+ columnName: 'name',
36
+ ...sharedFilterDefaults
37
+ }
38
+ ]
39
+
40
+ expect(getFilteredData(sharedFilters)).toEqual({ '0': [data.data1[0]], vizA: [data.data1[0]] })
41
+ })
42
+
43
+ it('should precompute visualization and row data for shared filters with empty usedBy', () => {
44
+ const sharedFilters: SharedFilter[] = [
45
+ {
46
+ usedBy: [],
47
+ active: 'Alice',
48
+ columnName: 'name',
49
+ ...sharedFilterDefaults
50
+ }
51
+ ]
52
+
53
+ expect(getFilteredData(sharedFilters)).toEqual({ '0': [data.data1[0]], vizA: [data.data1[0]] })
54
+ })
55
+
56
+ it('should keep explicit usedBy filters scoped during precompute', () => {
57
+ const sharedFilters: SharedFilter[] = [
58
+ {
59
+ usedBy: ['vizA'],
60
+ active: 'Alice',
61
+ columnName: 'name',
62
+ ...sharedFilterDefaults
63
+ },
64
+ {
65
+ usedBy: [0],
66
+ active: 'Bob',
67
+ columnName: 'name',
68
+ ...sharedFilterDefaults
69
+ }
70
+ ]
71
+
72
+ expect(getFilteredData(sharedFilters)).toEqual({ '0': [data.data1[1]], vizA: [data.data1[0]] })
73
+ })
74
+
75
+ it('should not precompute visualization data when the visualization uses row-level data', () => {
76
+ const sharedFilters: SharedFilter[] = [
77
+ {
78
+ active: 'Alice',
79
+ columnName: 'name',
80
+ ...sharedFilterDefaults
81
+ }
82
+ ]
83
+ const config = {
84
+ ...getConfig(sharedFilters),
85
+ rows: [{ dataKey: 'data1', data: data.data1, columns: [{ width: 12, widget: 'vizA' }] }]
86
+ }
87
+
88
+ const [, filteredData] = getUpdateConfig({ data, filteredData: {} } as any)(config)
89
+
90
+ expect(filteredData).toEqual({ '0': [data.data1[0]] })
91
+ })
92
+
93
+ it('should not create row cache entries for unscoped filters on visualization-data rows', () => {
94
+ const sharedFilters: SharedFilter[] = [
95
+ {
96
+ active: 'Alice',
97
+ columnName: 'name',
98
+ ...sharedFilterDefaults
99
+ }
100
+ ]
101
+
102
+ const [, filteredData] = getUpdateConfig({ data, filteredData: {} } as any)(getConfig(sharedFilters))
103
+
104
+ expect(filteredData).toHaveProperty('vizA', [data.data1[0]])
105
+ expect(filteredData).not.toHaveProperty('1')
106
+ })
107
+
108
+ it('should precompute row data from row.dataKey when row data is not stored by row index', () => {
109
+ const rowData = [
110
+ { id: 1, measure: 'Coverage', location: 'Alabama' },
111
+ { id: 2, measure: 'Barriers', location: 'Georgia' }
112
+ ]
113
+ const dataKey = 'private-multiviz-row-data'
114
+ const config = {
115
+ dashboard: {
116
+ sharedFilters: [
117
+ {
118
+ active: 'Alabama',
119
+ columnName: 'location',
120
+ ...sharedFilterDefaults
121
+ }
122
+ ]
123
+ },
124
+ datasets: {
125
+ [dataKey]: {}
126
+ },
127
+ visualizations: {
128
+ vizA: { dataKey }
129
+ },
130
+ rows: [
131
+ { columns: [] },
132
+ { columns: [] },
133
+ { columns: [] },
134
+ {
135
+ dataKey,
136
+ multiVizColumn: 'measure',
137
+ columns: [{ width: 12, widget: 'vizA' }]
138
+ }
139
+ ]
140
+ }
141
+
142
+ const [, filteredData] = getUpdateConfig({
143
+ data: { [dataKey]: rowData },
144
+ filteredData: {}
145
+ } as any)(config)
146
+
147
+ expect(filteredData).toEqual({ '3': [rowData[0]] })
148
+ })
149
+
150
+ it('should precompute row dashboard conditions using row owner filters', () => {
151
+ const sharedFilters: SharedFilter[] = [
152
+ {
153
+ usedBy: [0],
154
+ active: 'Alice',
155
+ columnName: 'name',
156
+ ...sharedFilterDefaults
157
+ }
158
+ ]
159
+ const config = {
160
+ ...getConfig(sharedFilters),
161
+ rows: [
162
+ {
163
+ dataKey: 'data1',
164
+ data: data.data1,
165
+ dashboardCondition: {
166
+ id: 'row-condition-1',
167
+ datasetKey: 'data1',
168
+ operator: 'hasData'
169
+ },
170
+ columns: []
171
+ }
172
+ ]
173
+ }
174
+
175
+ const [, filteredData] = getUpdateConfig({ data, filteredData: {} } as any)(config)
176
+
177
+ expect(filteredData).toEqual({
178
+ '0': [data.data1[0]],
179
+ 'row-condition-1': [data.data1[0]]
180
+ })
181
+ })
182
+
183
+ it('should precompute component conditions using widget owner filters when data is configured on the widget', () => {
184
+ const sharedFilters: SharedFilter[] = [
185
+ {
186
+ usedBy: ['vizA'],
187
+ active: 'Alice',
188
+ columnName: 'name',
189
+ ...sharedFilterDefaults
190
+ }
191
+ ]
192
+ const config = {
193
+ ...getConfig(sharedFilters),
194
+ rows: [
195
+ {
196
+ columns: [
197
+ {
198
+ width: 12,
199
+ conditionalWidgets: [
200
+ {
201
+ widget: 'vizA',
202
+ dashboardCondition: {
203
+ id: 'component-condition-1',
204
+ datasetKey: 'data1',
205
+ operator: 'columnHasAnyValue',
206
+ columnName: 'name',
207
+ values: ['Alice']
208
+ }
209
+ }
210
+ ]
211
+ }
212
+ ]
213
+ }
214
+ ]
215
+ }
216
+
217
+ const [, filteredData] = getUpdateConfig({ data, filteredData: {} } as any)(config)
218
+
219
+ expect(filteredData).toEqual({
220
+ vizA: [data.data1[0]],
221
+ 'component-condition-1': [data.data1[0]]
222
+ })
223
+ })
224
+
225
+ it('should precompute component conditions using row owner filters when data is configured on the row', () => {
226
+ const sharedFilters: SharedFilter[] = [
227
+ {
228
+ usedBy: [0],
229
+ active: 'Bob',
230
+ columnName: 'name',
231
+ ...sharedFilterDefaults
232
+ }
233
+ ]
234
+ const config = {
235
+ ...getConfig(sharedFilters),
236
+ rows: [
237
+ {
238
+ dataKey: 'data1',
239
+ data: data.data1,
240
+ columns: [
241
+ {
242
+ width: 12,
243
+ conditionalWidgets: [
244
+ {
245
+ widget: 'vizA',
246
+ dashboardCondition: {
247
+ id: 'component-condition-row-data',
248
+ datasetKey: 'data1',
249
+ operator: 'hasData'
250
+ }
251
+ }
252
+ ]
253
+ }
254
+ ]
255
+ }
256
+ ]
257
+ }
258
+
259
+ const [, filteredData] = getUpdateConfig({ data, filteredData: {} } as any)(config)
260
+
261
+ expect(filteredData).toEqual({
262
+ '0': [data.data1[1]],
263
+ 'component-condition-row-data': [data.data1[1]]
264
+ })
265
+ })
266
+
267
+ it('should leave reset-state data conditions unresolved during precompute', () => {
268
+ const sharedFilters: SharedFilter[] = [
269
+ {
270
+ usedBy: [0],
271
+ active: '',
272
+ columnName: 'name',
273
+ ...sharedFilterDefaults,
274
+ values: ['Alice', 'Bob', 'Charlie']
275
+ }
276
+ ]
277
+ const config = {
278
+ ...getConfig(sharedFilters),
279
+ rows: [
280
+ {
281
+ dataKey: 'data1',
282
+ data: data.data1,
283
+ dashboardCondition: {
284
+ id: 'row-condition-1',
285
+ datasetKey: 'data1',
286
+ operator: 'hasNoData'
287
+ },
288
+ columns: []
289
+ }
290
+ ]
291
+ }
292
+
293
+ const [, filteredData] = getUpdateConfig({ data, filteredData: {} } as any)(config)
294
+
295
+ expect(filteredData).toEqual({ '0': [] })
296
+ expect(filteredData).not.toHaveProperty('row-condition-1')
297
+ })
298
+
299
+ it('should precompute filtersIncomplete conditions from owner filter state', () => {
300
+ const sharedFilters: SharedFilter[] = [
301
+ {
302
+ usedBy: ['vizA'],
303
+ active: '',
304
+ columnName: 'name',
305
+ ...sharedFilterDefaults,
306
+ values: ['Alice', 'Bob', 'Charlie']
307
+ }
308
+ ]
309
+ const config = {
310
+ ...getConfig(sharedFilters),
311
+ rows: [
312
+ {
313
+ columns: [
314
+ {
315
+ width: 12,
316
+ conditionalWidgets: [
317
+ {
318
+ widget: 'vizA',
319
+ dashboardCondition: {
320
+ id: 'filters-incomplete-condition',
321
+ operator: 'filtersIncomplete'
322
+ }
323
+ }
324
+ ]
325
+ }
326
+ ]
327
+ }
328
+ ]
329
+ }
330
+
331
+ const [, filteredData] = getUpdateConfig({ data, filteredData: {} } as any)(config)
332
+
333
+ expect(filteredData).toEqual({
334
+ vizA: [],
335
+ 'filters-incomplete-condition': [{}]
336
+ })
337
+ })
338
+ })