@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.
- package/CONFIG.md +219 -0
- package/README.md +60 -20
- package/dist/cdcdashboard-CY9IcPSi.es.js +6 -0
- package/dist/cdcdashboard-DlpiY3fQ.es.js +4 -0
- package/dist/cdcdashboard.js +61559 -58048
- package/examples/__data__/data-2.json +6 -0
- package/examples/__data__/data.json +6 -0
- 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/legend-issue.json +1 -1
- package/examples/minimal-example.json +34 -0
- package/examples/private/cfa-dashboard.json +651 -0
- package/examples/private/data-bite-wrap.json +6936 -0
- package/examples/private/dengue.json +4640 -0
- package/examples/private/link_to_file.json +16662 -0
- package/examples/private/multi-dash-fix.json +16963 -0
- package/examples/private/versions.json +41612 -0
- package/examples/sankey.json +3 -3
- package/examples/test-api-filter-reset.json +4 -4
- package/examples/tp5-test.json +86 -4
- package/examples/us-map-filter-example.json +1074 -0
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +6 -2
- package/src/CdcDashboardComponent.tsx +179 -88
- 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.smoke.stories.tsx +33 -0
- package/src/_stories/Dashboard.stories.tsx +337 -2
- package/src/_stories/FilteredTextMigrationComparison.stories.tsx +87 -0
- package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
- package/src/_stories/_mock/tp5-test.json +86 -5
- 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 +23 -0
- package/src/components/DashboardFilters/DashboardFilters.test.tsx +267 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +193 -172
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.test.tsx +164 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +46 -6
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +5 -3
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +59 -58
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +304 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +43 -36
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +2 -2
- 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/ExpandCollapseButtons.tsx +6 -4
- package/src/components/Grid.tsx +12 -7
- package/src/components/Header/Header.tsx +36 -17
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
- package/src/components/Row.test.tsx +228 -0
- package/src/components/Row.tsx +104 -28
- package/src/components/VisualizationRow.test.tsx +396 -0
- package/src/components/VisualizationRow.tsx +177 -51
- 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 +123 -20
- package/src/components/Widget/widget.styles.css +58 -14
- package/src/components/dashboard-condition-modal.css +76 -0
- package/src/components/dashboard-condition-summary.css +87 -0
- package/src/data/initial-state.js +1 -0
- package/src/helpers/addValuesToDashboardFilters.ts +3 -5
- package/src/helpers/addVisualization.ts +17 -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 +281 -22
- package/src/scss/main.scss +215 -64
- package/src/store/dashboard.actions.ts +17 -4
- package/src/store/dashboard.reducer.test.ts +538 -0
- package/src/store/dashboard.reducer.ts +136 -22
- package/src/test/CdcDashboard.test.jsx +24 -0
- 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 +2 -0
- package/tests/fixtures/dashboard-config-with-metadata.json +1 -1
- package/dist/cdcdashboard-vr9HZwRt.es.js +0 -6
- package/examples/DEV-6574.json +0 -2224
- package/examples/api-dashboard-data.json +0 -272
- package/examples/api-dashboard-years.json +0 -11
- package/examples/api-geographies-data.json +0 -11
- package/examples/chart-data.json +0 -5409
- package/examples/custom/css/respiratory.css +0 -236
- package/examples/custom/js/respiratory.js +0 -242
- package/examples/default-data.json +0 -368
- package/examples/default-filter-control.json +0 -209
- package/examples/default-multi-dataset-shared-filter.json +0 -1729
- package/examples/default-multi-dataset.json +0 -506
- package/examples/ed-visits-county-file.json +0 -402
- package/examples/filters/Alabama.json +0 -72
- package/examples/filters/Alaska.json +0 -1737
- package/examples/filters/Arkansas.json +0 -4713
- package/examples/filters/California.json +0 -212
- package/examples/filters/Colorado.json +0 -1500
- package/examples/filters/Connecticut.json +0 -559
- package/examples/filters/Delaware.json +0 -63
- package/examples/filters/DistrictofColumbia.json +0 -63
- package/examples/filters/Florida.json +0 -4217
- package/examples/filters/States.json +0 -146
- package/examples/state-level.json +0 -90136
- package/examples/state-points.json +0 -10474
- package/examples/temp-example-data.json +0 -130
- package/examples/test-dashboard-simple.json +0 -503
- package/examples/test-example.json +0 -752
- package/examples/test-file.json +0 -147
- package/examples/test.json +0 -752
- package/examples/testing.json +0 -94456
- /package/examples/{data → __data__}/data-with-metadata.json +0 -0
- /package/examples/{legend-issue-data.json → __data__/legend-issue-data.json} +0 -0
- /package/examples/api-test/{categories.json → __data__/categories.json} +0 -0
- /package/examples/api-test/{chart-data.json → __data__/chart-data.json} +0 -0
- /package/examples/api-test/{topics.json → __data__/topics.json} +0 -0
- /package/examples/api-test/{years.json → __data__/years.json} +0 -0
- /package/src/_stories/{Dashboard.Pages.stories.tsx → Dashboard.Pages.smoke.stories.tsx} +0 -0
package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import FilterEditor from './FilterEditor'
|
|
4
|
+
|
|
5
|
+
vi.mock('@cdc/core/components/ui/Icon', () => ({
|
|
6
|
+
default: props => <span data-testid='mock-icon' {...props} />
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
const baseConfig = {
|
|
10
|
+
dashboard: {
|
|
11
|
+
sharedFilters: []
|
|
12
|
+
},
|
|
13
|
+
datasets: {
|
|
14
|
+
'nested-data.json': {
|
|
15
|
+
data: [
|
|
16
|
+
{ region: 'North', year: '2023', quarter: 'Q1' },
|
|
17
|
+
{ region: 'North', year: '2023', quarter: 'Q2' }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
rows: [
|
|
22
|
+
{
|
|
23
|
+
columns: [
|
|
24
|
+
{
|
|
25
|
+
width: 12,
|
|
26
|
+
conditionalWidgets: [
|
|
27
|
+
{
|
|
28
|
+
widget: 'viz-1',
|
|
29
|
+
dashboardCondition: {
|
|
30
|
+
id: 'row-1-col-1-condition',
|
|
31
|
+
datasetKey: 'nested-data.json',
|
|
32
|
+
operator: 'hasData'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
dashboardCondition: {
|
|
39
|
+
id: 'row-1-condition',
|
|
40
|
+
datasetKey: 'nested-data.json',
|
|
41
|
+
operator: 'hasData'
|
|
42
|
+
},
|
|
43
|
+
expandCollapseAllButtons: false
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
columns: [
|
|
47
|
+
{
|
|
48
|
+
width: 12,
|
|
49
|
+
widget: 'viz-2',
|
|
50
|
+
dashboardCondition: {
|
|
51
|
+
id: 'row-2-col-1-condition',
|
|
52
|
+
datasetKey: 'nested-data.json',
|
|
53
|
+
operator: 'hasData'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
dashboardCondition: {
|
|
58
|
+
id: 'row-2-condition',
|
|
59
|
+
datasetKey: 'nested-data.json',
|
|
60
|
+
operator: 'hasData'
|
|
61
|
+
},
|
|
62
|
+
expandCollapseAllButtons: false,
|
|
63
|
+
toggle: true
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
visualizations: {
|
|
67
|
+
'viz-1': {
|
|
68
|
+
uid: 'viz-1',
|
|
69
|
+
type: 'markup-include',
|
|
70
|
+
contentEditor: {
|
|
71
|
+
title: 'First Markup'
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
'viz-2': {
|
|
75
|
+
uid: 'viz-2',
|
|
76
|
+
type: 'markup-include',
|
|
77
|
+
contentEditor: {
|
|
78
|
+
title: 'Toggle Markup'
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} as any
|
|
83
|
+
|
|
84
|
+
const createNestedFilter = (type: 'datafilter' | 'urlfilter') =>
|
|
85
|
+
({
|
|
86
|
+
key: 'Year and Quarter',
|
|
87
|
+
type,
|
|
88
|
+
filterStyle: 'nested-dropdown',
|
|
89
|
+
showDropdown: true,
|
|
90
|
+
values: ['2023', '2024'],
|
|
91
|
+
columnName: 'year',
|
|
92
|
+
id: 0,
|
|
93
|
+
parents: [],
|
|
94
|
+
order: 'asc',
|
|
95
|
+
subGrouping: {
|
|
96
|
+
columnName: 'quarter',
|
|
97
|
+
valuesLookup: {
|
|
98
|
+
'2023': { values: ['Q1', 'Q2'] },
|
|
99
|
+
'2024': { values: ['Q3', 'Q4'] }
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
...(type === 'urlfilter'
|
|
103
|
+
? {
|
|
104
|
+
apiFilter: {
|
|
105
|
+
apiEndpoint: '/api/nested-options',
|
|
106
|
+
valueSelector: 'year',
|
|
107
|
+
subgroupValueSelector: 'quarter'
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
: {})
|
|
111
|
+
} as any)
|
|
112
|
+
|
|
113
|
+
describe('FilterEditor API filter subgroup text selector', () => {
|
|
114
|
+
it('displays subgroupTextSelector value from apiFilter', () => {
|
|
115
|
+
const filter = {
|
|
116
|
+
...createNestedFilter('urlfilter'),
|
|
117
|
+
apiFilter: {
|
|
118
|
+
apiEndpoint: '/api/nested-options',
|
|
119
|
+
valueSelector: 'year',
|
|
120
|
+
subgroupValueSelector: 'quarter',
|
|
121
|
+
subgroupTextSelector: 'quarterName'
|
|
122
|
+
}
|
|
123
|
+
} as any
|
|
124
|
+
|
|
125
|
+
render(
|
|
126
|
+
<FilterEditor
|
|
127
|
+
config={{
|
|
128
|
+
...baseConfig,
|
|
129
|
+
dashboard: { sharedFilters: [filter] }
|
|
130
|
+
}}
|
|
131
|
+
filter={filter}
|
|
132
|
+
filterIndex={0}
|
|
133
|
+
onNestedDragAreaHover={vi.fn()}
|
|
134
|
+
toggleNestedQueryParameters={vi.fn()}
|
|
135
|
+
updateFilterProp={vi.fn()}
|
|
136
|
+
/>
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
expect(screen.getByDisplayValue('quarterName')).toBeInTheDocument()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('shows empty string when subgroupTextSelector is absent', () => {
|
|
143
|
+
const filter = createNestedFilter('urlfilter') as any
|
|
144
|
+
|
|
145
|
+
render(
|
|
146
|
+
<FilterEditor
|
|
147
|
+
config={{
|
|
148
|
+
...baseConfig,
|
|
149
|
+
dashboard: { sharedFilters: [filter] }
|
|
150
|
+
}}
|
|
151
|
+
filter={filter}
|
|
152
|
+
filterIndex={0}
|
|
153
|
+
onNestedDragAreaHover={vi.fn()}
|
|
154
|
+
toggleNestedQueryParameters={vi.fn()}
|
|
155
|
+
updateFilterProp={vi.fn()}
|
|
156
|
+
/>
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
const inputs = screen.getAllByRole('textbox')
|
|
160
|
+
const subgroupTextInput = inputs.find(el =>
|
|
161
|
+
el.closest('label')?.textContent?.includes('Subgroup Display Text Selector')
|
|
162
|
+
)
|
|
163
|
+
expect(subgroupTextInput).toHaveValue('')
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe('FilterEditor nested dropdown display toggle', () => {
|
|
168
|
+
it.each([
|
|
169
|
+
['data-backed nested filters', createNestedFilter('datafilter')],
|
|
170
|
+
['api-backed nested filters', createNestedFilter('urlfilter')]
|
|
171
|
+
])('renders the checkbox below Create query parameters for %s', (_label, filter) => {
|
|
172
|
+
const updateFilterProp = vi.fn()
|
|
173
|
+
|
|
174
|
+
render(
|
|
175
|
+
<FilterEditor
|
|
176
|
+
config={{
|
|
177
|
+
...baseConfig,
|
|
178
|
+
dashboard: { sharedFilters: [filter] }
|
|
179
|
+
}}
|
|
180
|
+
filter={filter}
|
|
181
|
+
filterIndex={0}
|
|
182
|
+
onNestedDragAreaHover={vi.fn()}
|
|
183
|
+
toggleNestedQueryParameters={vi.fn()}
|
|
184
|
+
updateFilterProp={updateFilterProp}
|
|
185
|
+
/>
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
const queryParameters = screen.getByLabelText('Create query parameters')
|
|
189
|
+
const displaySubgroupingOnly = screen.getByLabelText('Display subgrouping only')
|
|
190
|
+
|
|
191
|
+
expect(displaySubgroupingOnly).not.toBeChecked()
|
|
192
|
+
|
|
193
|
+
const queryParametersLabel = queryParameters.closest('label')
|
|
194
|
+
const displaySubgroupingOnlyLabel = displaySubgroupingOnly.closest('label')
|
|
195
|
+
const isBelowQueryParameters = !!(
|
|
196
|
+
queryParametersLabel &&
|
|
197
|
+
displaySubgroupingOnlyLabel &&
|
|
198
|
+
queryParametersLabel.compareDocumentPosition(displaySubgroupingOnlyLabel) & Node.DOCUMENT_POSITION_FOLLOWING
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
expect(isBelowQueryParameters).toBe(true)
|
|
202
|
+
|
|
203
|
+
fireEvent.click(displaySubgroupingOnly)
|
|
204
|
+
|
|
205
|
+
expect(updateFilterProp).toHaveBeenCalledWith('displaySubgroupingOnly', true)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it.each([
|
|
209
|
+
[
|
|
210
|
+
'data-backed non-nested filters',
|
|
211
|
+
{
|
|
212
|
+
...createNestedFilter('datafilter'),
|
|
213
|
+
filterStyle: 'dropdown'
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
[
|
|
217
|
+
'api-backed non-nested filters',
|
|
218
|
+
{
|
|
219
|
+
...createNestedFilter('urlfilter'),
|
|
220
|
+
filterStyle: 'dropdown'
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
])('does not render the checkbox for %s', (_label, filter) => {
|
|
224
|
+
render(
|
|
225
|
+
<FilterEditor
|
|
226
|
+
config={{
|
|
227
|
+
...baseConfig,
|
|
228
|
+
dashboard: { sharedFilters: [filter] }
|
|
229
|
+
}}
|
|
230
|
+
filter={filter}
|
|
231
|
+
filterIndex={0}
|
|
232
|
+
onNestedDragAreaHover={vi.fn()}
|
|
233
|
+
toggleNestedQueryParameters={vi.fn()}
|
|
234
|
+
updateFilterProp={vi.fn()}
|
|
235
|
+
/>
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
expect(screen.queryByLabelText('Display subgrouping only')).not.toBeInTheDocument()
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('uses row targets for row conditions and does not expose dashboard condition ids in Used By options', () => {
|
|
242
|
+
render(
|
|
243
|
+
<FilterEditor
|
|
244
|
+
config={{
|
|
245
|
+
...baseConfig,
|
|
246
|
+
dashboard: {
|
|
247
|
+
sharedFilters: [
|
|
248
|
+
{
|
|
249
|
+
...createNestedFilter('datafilter'),
|
|
250
|
+
filterStyle: 'dropdown'
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
}}
|
|
255
|
+
filter={{
|
|
256
|
+
...createNestedFilter('datafilter'),
|
|
257
|
+
filterStyle: 'dropdown'
|
|
258
|
+
}}
|
|
259
|
+
filterIndex={0}
|
|
260
|
+
onNestedDragAreaHover={vi.fn()}
|
|
261
|
+
toggleNestedQueryParameters={vi.fn()}
|
|
262
|
+
updateFilterProp={vi.fn()}
|
|
263
|
+
/>
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
const expandButtons = screen.getAllByLabelText('Expand')
|
|
267
|
+
fireEvent.click(expandButtons[0])
|
|
268
|
+
|
|
269
|
+
expect(screen.getByText('Row 1')).toBeInTheDocument()
|
|
270
|
+
expect(screen.queryByText('Row 1 Dashboard Condition')).not.toBeInTheDocument()
|
|
271
|
+
expect(screen.queryByText('Row 1 Column 1 Component 1 Dashboard Condition')).not.toBeInTheDocument()
|
|
272
|
+
expect(screen.queryByText('Row 2 Dashboard Condition')).not.toBeInTheDocument()
|
|
273
|
+
expect(screen.queryByText('Row 2 Column 1 Dashboard Condition')).not.toBeInTheDocument()
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it('updates dashboard shared filter note text', async () => {
|
|
277
|
+
const filter = {
|
|
278
|
+
...createNestedFilter('datafilter'),
|
|
279
|
+
filterStyle: 'dropdown',
|
|
280
|
+
note: 'Existing note'
|
|
281
|
+
}
|
|
282
|
+
const updateFilterProp = vi.fn()
|
|
283
|
+
|
|
284
|
+
render(
|
|
285
|
+
<FilterEditor
|
|
286
|
+
config={{
|
|
287
|
+
...baseConfig,
|
|
288
|
+
dashboard: { sharedFilters: [filter] }
|
|
289
|
+
}}
|
|
290
|
+
filter={filter}
|
|
291
|
+
filterIndex={0}
|
|
292
|
+
onNestedDragAreaHover={vi.fn()}
|
|
293
|
+
toggleNestedQueryParameters={vi.fn()}
|
|
294
|
+
updateFilterProp={updateFilterProp}
|
|
295
|
+
/>
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
fireEvent.change(screen.getByLabelText('Note'), { target: { value: 'Helpful note' } })
|
|
299
|
+
|
|
300
|
+
await waitFor(() => {
|
|
301
|
+
expect(updateFilterProp).toHaveBeenCalledWith('note', 'Helpful note')
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { getVizRowColumnLocator } from '../../../../helpers/getVizRowColumnLocator'
|
|
2
1
|
import { Select, TextField } from '@cdc/core/components/EditorPanel/Inputs'
|
|
3
2
|
import DataTransform from '@cdc/core/helpers/DataTransform'
|
|
4
3
|
import { useEffect, useMemo, useState } from 'react'
|
|
4
|
+
import { getSharedFilterTargetOptions } from '../../../../helpers/dashboardFilterTargets'
|
|
5
5
|
import { SharedFilter } from '../../../../types/SharedFilter'
|
|
6
6
|
|
|
7
7
|
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
@@ -10,7 +10,6 @@ import Icon from '@cdc/core/components/ui/Icon'
|
|
|
10
10
|
import MultiSelect from '@cdc/core/components/MultiSelect'
|
|
11
11
|
import Loading from '@cdc/core/components/Loading'
|
|
12
12
|
import { DashboardConfig } from '../../../../types/DashboardConfig'
|
|
13
|
-
import { Visualization } from '@cdc/core/types/Visualization'
|
|
14
13
|
import { hasDashboardApplyBehavior } from '../../../../helpers/hasDashboardApplyBehavior'
|
|
15
14
|
import APIModal from './APIModal'
|
|
16
15
|
import NestedDropDownDashboard from './NestedDropDownDashboard'
|
|
@@ -19,6 +18,7 @@ import { filterOrderOptions } from '@cdc/core/helpers/filterOrderOptions'
|
|
|
19
18
|
import FilterOrder from '@cdc/core/components/EditorPanel/VizFilterEditor/components/FilterOrder'
|
|
20
19
|
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
21
20
|
import Modal from '@cdc/core/components/ui/Modal'
|
|
21
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
22
22
|
|
|
23
23
|
type FilterEditorProps = {
|
|
24
24
|
config: DashboardConfig
|
|
@@ -51,43 +51,18 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
51
51
|
.filter(({ key }) => key !== filter.key)
|
|
52
52
|
.map(({ key }) => key)
|
|
53
53
|
|
|
54
|
-
const vizRowColumnLocator = getVizRowColumnLocator(config.rows)
|
|
55
|
-
|
|
56
54
|
const getVizTitle = (viz, vizKey) => {
|
|
57
55
|
let vizName = viz.general?.title || viz.title || vizKey
|
|
58
56
|
if (viz.visualizationType === 'markup-include') {
|
|
59
|
-
vizName = viz.contentEditor
|
|
57
|
+
vizName = viz.contentEditor?.title || vizKey
|
|
60
58
|
}
|
|
61
59
|
return vizName
|
|
62
60
|
}
|
|
63
61
|
|
|
64
62
|
const [usedByNameLookup, usedByOptions] = useMemo(() => {
|
|
65
|
-
const nameLookup =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (!vizLookup) return false
|
|
69
|
-
const viz = config.visualizations[vizKey] as Visualization
|
|
70
|
-
if (viz.type === 'dashboardFilters') return false
|
|
71
|
-
const vizName = getVizTitle(viz, vizKey)
|
|
72
|
-
|
|
73
|
-
nameLookup[vizKey] = vizName
|
|
74
|
-
const usesSharedFilter = viz.usesSharedFilter
|
|
75
|
-
const rowIndex = vizLookup.row
|
|
76
|
-
const dataConfiguredOnRow = config.rows[rowIndex].dataKey
|
|
77
|
-
return filter.setBy !== vizKey && !usesSharedFilter && !dataConfiguredOnRow
|
|
78
|
-
})
|
|
79
|
-
const rowOptions: number[] = []
|
|
80
|
-
|
|
81
|
-
config.rows.forEach((row, rowIndex) => {
|
|
82
|
-
if (!!row.dataKey) {
|
|
83
|
-
nameLookup[rowIndex] = `Row ${rowIndex + 1}`
|
|
84
|
-
rowOptions.push(rowIndex)
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
const rowsNotSelected = rowOptions.filter(row => !filter.usedBy || filter.usedBy.indexOf(row.toString()) === -1)
|
|
89
|
-
return [nameLookup, [...vizOptions, ...rowsNotSelected]]
|
|
90
|
-
}, [config.visualizations, filter.usedBy, filter.setBy, vizRowColumnLocator])
|
|
63
|
+
const { nameLookup, options } = getSharedFilterTargetOptions(config, filter)
|
|
64
|
+
return [nameLookup, options]
|
|
65
|
+
}, [config, filter])
|
|
91
66
|
|
|
92
67
|
const useParameters = useMemo(() => {
|
|
93
68
|
if (filter.subGrouping) return !!(filter.setByQueryParameter && filter.subGrouping?.setByQueryParameter)
|
|
@@ -223,11 +198,20 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
223
198
|
|
|
224
199
|
<TextField
|
|
225
200
|
label='Label'
|
|
201
|
+
fieldName='key'
|
|
226
202
|
value={filter.key}
|
|
227
203
|
updateField={(_section, _subSection, _key, value) => {
|
|
228
204
|
updateLabel(value)
|
|
229
205
|
}}
|
|
230
206
|
/>
|
|
207
|
+
<TextField
|
|
208
|
+
type='textarea'
|
|
209
|
+
className='filter-editor__compact-textarea'
|
|
210
|
+
label='Note'
|
|
211
|
+
fieldName='note'
|
|
212
|
+
value={filter.note || ''}
|
|
213
|
+
updateField={(_section, _subSection, _key, value) => updateFilterProp('note', value)}
|
|
214
|
+
/>
|
|
231
215
|
{filter.filterStyle === FILTER_STYLE.multiSelect && (
|
|
232
216
|
<TextField
|
|
233
217
|
label='Select Limit'
|
|
@@ -292,7 +276,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
292
276
|
{filter.filterBy === 'Query String' && filter.usedBy && filter.usedBy.length > 0 && (
|
|
293
277
|
<div className='bg-info-subtle p-2 my-2' style={{ fontSize: '0.9em' }}>
|
|
294
278
|
<Icon display='info' style={{ marginRight: '0.5rem' }} />
|
|
295
|
-
Will apply to datasets used by selected
|
|
279
|
+
Will apply to datasets used by selected targets
|
|
296
280
|
</div>
|
|
297
281
|
)}
|
|
298
282
|
{filter.filterBy === 'File Name' && (
|
|
@@ -405,7 +389,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
405
389
|
</label>
|
|
406
390
|
<label>
|
|
407
391
|
<span>Subgroup Display Text Selector: </span>
|
|
408
|
-
<input value={filter?.
|
|
392
|
+
<input value={filter?.apiFilter?.subgroupTextSelector || ''} disabled />
|
|
409
393
|
<Tooltip style={{ textTransform: 'none' }}>
|
|
410
394
|
<Tooltip.Target>
|
|
411
395
|
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
@@ -419,12 +403,13 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
419
403
|
</div>
|
|
420
404
|
)}
|
|
421
405
|
|
|
422
|
-
<
|
|
406
|
+
<Button
|
|
407
|
+
variant='primary'
|
|
408
|
+
className='mt-2'
|
|
423
409
|
onClick={() => handleEditAPIValues(filter, isNestedDropdown, updateAPIFilter)}
|
|
424
|
-
className='btn btn-primary mt-2'
|
|
425
410
|
>
|
|
426
411
|
Edit API Values
|
|
427
|
-
</
|
|
412
|
+
</Button>
|
|
428
413
|
</div>
|
|
429
414
|
|
|
430
415
|
<label>
|
|
@@ -451,6 +436,18 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
451
436
|
</span>
|
|
452
437
|
</label>
|
|
453
438
|
|
|
439
|
+
{isNestedDropdown && (
|
|
440
|
+
<label>
|
|
441
|
+
<input
|
|
442
|
+
type='checkbox'
|
|
443
|
+
checked={!!filter.displaySubgroupingOnly}
|
|
444
|
+
aria-label='Display subgrouping only'
|
|
445
|
+
onChange={e => updateFilterProp('displaySubgroupingOnly', e.target.checked)}
|
|
446
|
+
/>
|
|
447
|
+
<span> Display subgrouping only</span>
|
|
448
|
+
</label>
|
|
449
|
+
)}
|
|
450
|
+
|
|
454
451
|
{!!parentFilters.length && (
|
|
455
452
|
<label>
|
|
456
453
|
<span className='edit-label column-heading mt-1'>Parent Filter(s): </span>
|
|
@@ -596,6 +593,16 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
596
593
|
</Tooltip>
|
|
597
594
|
</span>
|
|
598
595
|
</label>
|
|
596
|
+
|
|
597
|
+
<label>
|
|
598
|
+
<input
|
|
599
|
+
type='checkbox'
|
|
600
|
+
checked={!!filter.displaySubgroupingOnly}
|
|
601
|
+
aria-label='Display subgrouping only'
|
|
602
|
+
onChange={e => updateFilterProp('displaySubgroupingOnly', e.target.checked)}
|
|
603
|
+
/>
|
|
604
|
+
<span> Display subgrouping only</span>
|
|
605
|
+
</label>
|
|
599
606
|
</>
|
|
600
607
|
)}
|
|
601
608
|
<Select
|
|
@@ -242,7 +242,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
|
|
|
242
242
|
/>
|
|
243
243
|
)}
|
|
244
244
|
|
|
245
|
-
{/* Default Value for
|
|
245
|
+
{/* Default Value for Subgroup */}
|
|
246
246
|
{subGrouping?.columnName && (filter.defaultValue || filter.active) && subGrouping.valuesLookup && (
|
|
247
247
|
<Select
|
|
248
248
|
value={subGrouping.defaultValue}
|
|
@@ -255,7 +255,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
|
|
|
255
255
|
const newSubGrouping = { ...subGrouping, defaultValue: value }
|
|
256
256
|
updateFilterProp('subGrouping', newSubGrouping)
|
|
257
257
|
}}
|
|
258
|
-
label={'
|
|
258
|
+
label={'Subgroup Default Value'}
|
|
259
259
|
initial={'Select'}
|
|
260
260
|
/>
|
|
261
261
|
)}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render } from '@testing-library/react'
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
+
import { DashboardContext, DashboardDispatchContext, initialState } from '../../DashboardContext'
|
|
5
|
+
import DashboardFiltersWrapper from './DashboardFiltersWrapper'
|
|
6
|
+
|
|
7
|
+
vi.mock('@cdc/core/components/ui/Icon', () => ({
|
|
8
|
+
default: props => <span data-testid='mock-icon' {...props} />
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
const createSharedFilter = (overrides = {}) =>
|
|
12
|
+
({
|
|
13
|
+
key: 'Year',
|
|
14
|
+
type: 'datafilter',
|
|
15
|
+
filterStyle: 'dropdown',
|
|
16
|
+
showDropdown: true,
|
|
17
|
+
values: ['2023', '2024'],
|
|
18
|
+
active: '2023',
|
|
19
|
+
columnName: 'year',
|
|
20
|
+
parents: [],
|
|
21
|
+
...overrides
|
|
22
|
+
} as any)
|
|
23
|
+
|
|
24
|
+
const renderWrapper = ({
|
|
25
|
+
grayBackground,
|
|
26
|
+
sharedFilters = [createSharedFilter()],
|
|
27
|
+
sharedFilterIndexes = [0],
|
|
28
|
+
isEditor = false
|
|
29
|
+
}: {
|
|
30
|
+
grayBackground?: boolean
|
|
31
|
+
sharedFilters?: any[]
|
|
32
|
+
sharedFilterIndexes?: any[]
|
|
33
|
+
isEditor?: boolean
|
|
34
|
+
} = {}) => {
|
|
35
|
+
const visualizationConfig = {
|
|
36
|
+
uid: 'dashboardFilters1',
|
|
37
|
+
type: 'dashboardFilters',
|
|
38
|
+
visualizationType: 'dashboardFilters',
|
|
39
|
+
filterBehavior: 'Filter Change',
|
|
40
|
+
filterIntro: 'Choose a <strong>year</strong> to update the dashboard.',
|
|
41
|
+
sharedFilterIndexes,
|
|
42
|
+
visual: grayBackground === undefined ? undefined : { grayBackground }
|
|
43
|
+
} as any
|
|
44
|
+
|
|
45
|
+
return render(
|
|
46
|
+
<DashboardContext.Provider
|
|
47
|
+
value={{
|
|
48
|
+
...initialState,
|
|
49
|
+
config: {
|
|
50
|
+
type: 'dashboard',
|
|
51
|
+
dashboard: { sharedFilters },
|
|
52
|
+
datasets: {},
|
|
53
|
+
rows: [],
|
|
54
|
+
visualizations: {
|
|
55
|
+
dashboardFilters1: visualizationConfig
|
|
56
|
+
}
|
|
57
|
+
} as any,
|
|
58
|
+
data: {},
|
|
59
|
+
outerContainerRef: vi.fn(),
|
|
60
|
+
setParentConfig: vi.fn(),
|
|
61
|
+
isDebug: false,
|
|
62
|
+
isEditor: false,
|
|
63
|
+
reloadURLData: vi.fn(),
|
|
64
|
+
loadAPIFilters: vi.fn(),
|
|
65
|
+
setAPIFilterDropdowns: vi.fn(),
|
|
66
|
+
setAPILoading: vi.fn()
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
<DashboardDispatchContext.Provider value={vi.fn()}>
|
|
70
|
+
<DashboardFiltersWrapper
|
|
71
|
+
apiFilterDropdowns={{}}
|
|
72
|
+
visualizationConfig={visualizationConfig}
|
|
73
|
+
setConfig={vi.fn()}
|
|
74
|
+
currentViewport={'lg' as any}
|
|
75
|
+
isEditor={isEditor}
|
|
76
|
+
interactionLabel='dashboard-test'
|
|
77
|
+
/>
|
|
78
|
+
</DashboardDispatchContext.Provider>
|
|
79
|
+
</DashboardContext.Provider>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe('DashboardFiltersWrapper visual styles', () => {
|
|
84
|
+
it('wraps filters in the dashboard filters callout when grey background is enabled', () => {
|
|
85
|
+
const { container } = renderWrapper({ grayBackground: true })
|
|
86
|
+
|
|
87
|
+
expect(container.querySelector('.cdc-callout.cdc-callout--dashboard-filters')).toBeInTheDocument()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it.each([false, undefined])('keeps the existing unwrapped layout when grayBackground is %s', value => {
|
|
91
|
+
const { container } = renderWrapper({ grayBackground: value })
|
|
92
|
+
|
|
93
|
+
expect(container.querySelector('.cdc-callout.cdc-callout--dashboard-filters')).not.toBeInTheDocument()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('renders filter intro text above dashboard filter controls', () => {
|
|
97
|
+
const { container } = renderWrapper()
|
|
98
|
+
const intro = container.querySelector('.filters-section__intro-text')
|
|
99
|
+
|
|
100
|
+
expect(intro).toBeInTheDocument()
|
|
101
|
+
expect(intro).toHaveTextContent('Choose a year to update the dashboard.')
|
|
102
|
+
expect(intro?.querySelector('strong')).toHaveTextContent('year')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('renders no dashboard filters container when every referenced filter is hidden', () => {
|
|
106
|
+
const { container } = renderWrapper({ sharedFilters: [createSharedFilter({ showDropdown: false })] })
|
|
107
|
+
|
|
108
|
+
expect(container.querySelector('.cove-dashboard-filters-container')).not.toBeInTheDocument()
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('renders no dashboard filters container when sharedFilterIndexes is empty', () => {
|
|
112
|
+
const { container } = renderWrapper({ sharedFilterIndexes: [] })
|
|
113
|
+
|
|
114
|
+
expect(container.querySelector('.cove-dashboard-filters-container')).not.toBeInTheDocument()
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('renders no dashboard filters container when sharedFilterIndexes only references missing filters', () => {
|
|
118
|
+
const { container } = renderWrapper({ sharedFilterIndexes: [4] })
|
|
119
|
+
|
|
120
|
+
expect(container.querySelector('.cove-dashboard-filters-container')).not.toBeInTheDocument()
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('keeps the editor sidebar available without rendering an empty runtime filters container', () => {
|
|
124
|
+
const { container, getByText } = renderWrapper({
|
|
125
|
+
sharedFilters: [createSharedFilter({ showDropdown: false })],
|
|
126
|
+
isEditor: true
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
expect(getByText('Configure Dashboard Filters')).toBeInTheDocument()
|
|
130
|
+
expect(container.querySelector('.cove-dashboard-filters-container')).not.toBeInTheDocument()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it.each([
|
|
134
|
+
['urlfilter', createSharedFilter({ type: 'urlfilter', showDropdown: false })],
|
|
135
|
+
['nestedDropdown', createSharedFilter({ filterStyle: 'nested-dropdown', showDropdown: false })],
|
|
136
|
+
['tabSimple', createSharedFilter({ filterStyle: 'tab-simple', showDropdown: false })]
|
|
137
|
+
])('keeps %s dashboard filters visible under dashboard-specific rules', (_label, filter) => {
|
|
138
|
+
const { container } = renderWrapper({ sharedFilters: [filter] })
|
|
139
|
+
|
|
140
|
+
expect(container.querySelector('.cove-dashboard-filters-container')).toBeInTheDocument()
|
|
141
|
+
})
|
|
142
|
+
})
|