@cdc/dashboard 4.26.3 → 4.26.4
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 +172 -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 +51744 -49003
- package/examples/__data__/data-2.json +6 -0
- package/examples/__data__/data.json +6 -0
- package/examples/legend-issue.json +1 -1
- package/examples/minimal-example.json +34 -0
- package/examples/private/dengue.json +4640 -0
- package/examples/private/link_to_file.json +16662 -0
- package/examples/sankey.json +3 -3
- package/examples/test-api-filter-reset.json +4 -4
- package/examples/tp5-test.json +86 -4
- package/package.json +9 -9
- package/src/CdcDashboardComponent.tsx +1 -1
- package/src/_stories/Dashboard.smoke.stories.tsx +33 -0
- package/src/_stories/Dashboard.stories.tsx +43 -2
- package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
- package/src/_stories/_mock/tp5-test.json +86 -5
- package/src/components/DashboardEditors.tsx +15 -0
- package/src/components/DashboardFilters/DashboardFilters.test.tsx +129 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +10 -7
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +5 -4
- 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 +127 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +28 -4
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +2 -2
- package/src/components/ExpandCollapseButtons.tsx +6 -4
- package/src/components/Grid.tsx +4 -3
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
- package/src/components/Row.tsx +11 -10
- package/src/components/VisualizationRow.tsx +71 -20
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +1 -1
- package/src/components/Widget/Widget.tsx +5 -4
- package/src/components/Widget/widget.styles.css +41 -10
- package/src/data/initial-state.js +1 -0
- package/src/helpers/addVisualization.ts +3 -1
- package/src/helpers/tests/addVisualization.test.ts +1 -1
- package/src/scss/grid.scss +38 -8
- package/src/scss/main.scss +110 -38
- package/src/store/dashboard.reducer.ts +1 -0
- package/src/test/CdcDashboard.test.jsx +24 -0
- package/src/types/SharedFilter.ts +1 -0
- package/tests/fixtures/dashboard-config-with-metadata.json +1 -1
- package/LICENSE +0 -201
- 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
|
@@ -77,6 +77,11 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
|
|
|
77
77
|
<CdcDataBite
|
|
78
78
|
key={visualizationKey}
|
|
79
79
|
config={{ ...visualizationConfig, newViz: true }}
|
|
80
|
+
rawData={
|
|
81
|
+
state.data?.[visualizationConfig.dataKey] ||
|
|
82
|
+
state.config.datasets?.[visualizationConfig.dataKey]?.data ||
|
|
83
|
+
[]
|
|
84
|
+
}
|
|
80
85
|
isEditor={true}
|
|
81
86
|
setConfig={_updateConfig}
|
|
82
87
|
isDashboard={true}
|
|
@@ -89,6 +94,11 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
|
|
|
89
94
|
<CdcWaffleChart
|
|
90
95
|
key={visualizationKey}
|
|
91
96
|
config={visualizationConfig}
|
|
97
|
+
rawData={
|
|
98
|
+
state.data?.[visualizationConfig.dataKey] ||
|
|
99
|
+
state.config.datasets?.[visualizationConfig.dataKey]?.data ||
|
|
100
|
+
[]
|
|
101
|
+
}
|
|
92
102
|
isEditor={true}
|
|
93
103
|
setConfig={_updateConfig}
|
|
94
104
|
isDashboard={true}
|
|
@@ -101,6 +111,11 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
|
|
|
101
111
|
<CdcMarkupInclude
|
|
102
112
|
key={visualizationKey}
|
|
103
113
|
config={visualizationConfig}
|
|
114
|
+
rawData={
|
|
115
|
+
state.data?.[visualizationConfig.dataKey] ||
|
|
116
|
+
state.config.datasets?.[visualizationConfig.dataKey]?.data ||
|
|
117
|
+
[]
|
|
118
|
+
}
|
|
104
119
|
isEditor={true}
|
|
105
120
|
setConfig={_updateConfig}
|
|
106
121
|
isDashboard={true}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { fireEvent, render } from '@testing-library/react'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import DashboardFilters from './DashboardFilters'
|
|
4
|
+
|
|
5
|
+
vi.mock('@cdc/core/components/ui/Icon', () => ({
|
|
6
|
+
default: props => <span data-testid='mock-icon' {...props} />
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
const createDataBackedFilter = (displaySubgroupingOnly = false) =>
|
|
10
|
+
({
|
|
11
|
+
key: 'Year and Quarter',
|
|
12
|
+
type: 'datafilter',
|
|
13
|
+
filterStyle: 'nested-dropdown',
|
|
14
|
+
showDropdown: true,
|
|
15
|
+
values: ['2023', '2024'],
|
|
16
|
+
columnName: 'year',
|
|
17
|
+
id: 0,
|
|
18
|
+
parents: [],
|
|
19
|
+
order: 'asc',
|
|
20
|
+
active: '2023',
|
|
21
|
+
displaySubgroupingOnly,
|
|
22
|
+
subGrouping: {
|
|
23
|
+
columnName: 'quarter',
|
|
24
|
+
active: 'Q2',
|
|
25
|
+
valuesLookup: {
|
|
26
|
+
'2023': { values: ['Q1', 'Q2'] },
|
|
27
|
+
'2024': { values: ['Q3', 'Q4'] }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} as any)
|
|
31
|
+
|
|
32
|
+
const createApiBackedFilter = (displaySubgroupingOnly = false) =>
|
|
33
|
+
({
|
|
34
|
+
key: 'API Year and Quarter',
|
|
35
|
+
type: 'urlfilter',
|
|
36
|
+
filterStyle: 'nested-dropdown',
|
|
37
|
+
showDropdown: true,
|
|
38
|
+
values: [],
|
|
39
|
+
columnName: 'year',
|
|
40
|
+
id: 0,
|
|
41
|
+
parents: [],
|
|
42
|
+
order: 'asc',
|
|
43
|
+
active: '2023',
|
|
44
|
+
displaySubgroupingOnly,
|
|
45
|
+
apiFilter: {
|
|
46
|
+
apiEndpoint: '/api/nested-options',
|
|
47
|
+
valueSelector: 'year',
|
|
48
|
+
subgroupValueSelector: 'quarter'
|
|
49
|
+
},
|
|
50
|
+
subGrouping: {
|
|
51
|
+
columnName: 'quarter',
|
|
52
|
+
active: 'Q2',
|
|
53
|
+
valuesLookup: {}
|
|
54
|
+
}
|
|
55
|
+
} as any)
|
|
56
|
+
|
|
57
|
+
const apiFilterDropdowns = {
|
|
58
|
+
'/api/nested-options': [
|
|
59
|
+
{
|
|
60
|
+
value: '2023',
|
|
61
|
+
text: '2023',
|
|
62
|
+
subOptions: [
|
|
63
|
+
{ value: 'Q1', text: 'Q1' },
|
|
64
|
+
{ value: 'Q2', text: 'Q2' }
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
value: '2024',
|
|
69
|
+
text: '2024',
|
|
70
|
+
subOptions: [
|
|
71
|
+
{ value: 'Q3', text: 'Q3' },
|
|
72
|
+
{ value: 'Q4', text: 'Q4' }
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe('DashboardFilters nested dropdown display', () => {
|
|
79
|
+
it.each([
|
|
80
|
+
['data-backed', createDataBackedFilter(false), {}, '2023 - Q2'],
|
|
81
|
+
['data-backed subgroup only', createDataBackedFilter(true), {}, 'Q2'],
|
|
82
|
+
['api-backed', createApiBackedFilter(false), apiFilterDropdowns, '2023 - Q2'],
|
|
83
|
+
['api-backed subgroup only', createApiBackedFilter(true), apiFilterDropdowns, 'Q2']
|
|
84
|
+
])('shows the expected closed text for %s filters', (_label, filter, dropdowns, expectedValue) => {
|
|
85
|
+
const { container } = render(
|
|
86
|
+
<DashboardFilters
|
|
87
|
+
applyFilters={vi.fn()}
|
|
88
|
+
apiFilterDropdowns={dropdowns as any}
|
|
89
|
+
filters={[filter]}
|
|
90
|
+
handleOnChange={vi.fn()}
|
|
91
|
+
show={[0]}
|
|
92
|
+
showSubmit={false}
|
|
93
|
+
/>
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const input = container.querySelector('.nested-dropdown input')
|
|
97
|
+
expect(input).toHaveValue(expectedValue)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it.each([
|
|
101
|
+
['data-backed', createDataBackedFilter(false), {}],
|
|
102
|
+
['data-backed subgroup only', createDataBackedFilter(true), {}],
|
|
103
|
+
['api-backed', createApiBackedFilter(false), apiFilterDropdowns],
|
|
104
|
+
['api-backed subgroup only', createApiBackedFilter(true), apiFilterDropdowns]
|
|
105
|
+
])('keeps nested dropdown selection behavior unchanged for %s filters', (_label, filter, dropdowns) => {
|
|
106
|
+
const handleOnChange = vi.fn()
|
|
107
|
+
const { container, getByText, queryByText } = render(
|
|
108
|
+
<DashboardFilters
|
|
109
|
+
applyFilters={vi.fn()}
|
|
110
|
+
apiFilterDropdowns={dropdowns as any}
|
|
111
|
+
filters={[filter]}
|
|
112
|
+
handleOnChange={handleOnChange}
|
|
113
|
+
show={[0]}
|
|
114
|
+
showSubmit={false}
|
|
115
|
+
/>
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const input = container.querySelector('.nested-dropdown input') as HTMLInputElement
|
|
119
|
+
fireEvent.focus(input)
|
|
120
|
+
fireEvent.change(input, { target: { value: 'Q3' } })
|
|
121
|
+
|
|
122
|
+
expect(getByText('2024')).toBeInTheDocument()
|
|
123
|
+
expect(queryByText('2023')).not.toBeInTheDocument()
|
|
124
|
+
|
|
125
|
+
fireEvent.click(getByText('Q3'))
|
|
126
|
+
|
|
127
|
+
expect(handleOnChange).toHaveBeenCalledWith(0, ['2024', 'Q3'])
|
|
128
|
+
})
|
|
129
|
+
})
|
|
@@ -9,6 +9,7 @@ import NestedDropdown from '@cdc/core/components/NestedDropdown'
|
|
|
9
9
|
import { getNestedOptions } from '@cdc/core/components/Filters/helpers/getNestedOptions'
|
|
10
10
|
import { MouseEventHandler } from 'react'
|
|
11
11
|
import Loader from '@cdc/core/components/Loader'
|
|
12
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
12
13
|
import _ from 'lodash'
|
|
13
14
|
import { DROPDOWN_STYLES } from '@cdc/core/components/Filters/components/Dropdown'
|
|
14
15
|
import Tabs from '@cdc/core/components/Filters/components/Tabs'
|
|
@@ -163,6 +164,7 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
163
164
|
<NestedDropdown
|
|
164
165
|
activeGroup={(filter.queuedActive?.[0] || filter.active) as string}
|
|
165
166
|
activeSubGroup={(filter.queuedActive?.[1] || filter.subGrouping?.active) as string}
|
|
167
|
+
displaySubgroupingOnly={filter.displaySubgroupingOnly}
|
|
166
168
|
filterIndex={filterIndex}
|
|
167
169
|
options={_key ? getNestedDropdownOptions(apiFilterDropdowns[_key]) : nestedOptions}
|
|
168
170
|
listLabel={label}
|
|
@@ -213,9 +215,10 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
213
215
|
)
|
|
214
216
|
})}
|
|
215
217
|
{showSubmit && (
|
|
216
|
-
|
|
217
|
-
<
|
|
218
|
-
|
|
218
|
+
<div className='dashboard-filters__actions'>
|
|
219
|
+
<Button
|
|
220
|
+
variant='primary'
|
|
221
|
+
className='mb-1 me-2'
|
|
219
222
|
onClick={applyFilters}
|
|
220
223
|
disabled={show.some(filterIndex => {
|
|
221
224
|
const emptyFilterValues = [undefined, '', '- Select -']
|
|
@@ -226,13 +229,13 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
226
229
|
})}
|
|
227
230
|
>
|
|
228
231
|
{applyFiltersButtonText || 'GO!'}
|
|
229
|
-
</
|
|
232
|
+
</Button>
|
|
230
233
|
{handleReset && (
|
|
231
|
-
<
|
|
234
|
+
<Button variant='link' className='mb-1' onClick={handleReset}>
|
|
232
235
|
Clear Filters
|
|
233
|
-
</
|
|
236
|
+
</Button>
|
|
234
237
|
)}
|
|
235
|
-
|
|
238
|
+
</div>
|
|
236
239
|
)}
|
|
237
240
|
</form>
|
|
238
241
|
)
|
|
@@ -22,6 +22,7 @@ import { FILTER_STYLE } from '../../../types/FilterStyles'
|
|
|
22
22
|
import { handleSorting } from '@cdc/core/components/Filters'
|
|
23
23
|
import { removeDashboardFilter } from '../../../helpers/removeDashboardFilter'
|
|
24
24
|
import { DragDropContext, Droppable, Draggable, DropResult } from '@hello-pangea/dnd'
|
|
25
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
25
26
|
|
|
26
27
|
type DashboardFitlersEditorProps = {
|
|
27
28
|
vizConfig: DashboardFilters
|
|
@@ -314,9 +315,9 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
|
|
|
314
315
|
)}
|
|
315
316
|
</Droppable>
|
|
316
317
|
</DragDropContext>
|
|
317
|
-
<
|
|
318
|
+
<Button variant='primary' fullWidth onClick={addNewFilter}>
|
|
318
319
|
Add Filter
|
|
319
|
-
</
|
|
320
|
+
</Button>
|
|
320
321
|
{canAddExisting ? (
|
|
321
322
|
<label>
|
|
322
323
|
<span className='edit-label column-heading'>
|
|
@@ -347,9 +348,9 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
|
|
|
347
348
|
/>
|
|
348
349
|
</label>
|
|
349
350
|
) : (
|
|
350
|
-
<
|
|
351
|
+
<Button variant='primary' fullWidth className='mt-2' onClick={() => setCanAddExisting(true)}>
|
|
351
352
|
Add Existing Dashboard Filter
|
|
352
|
-
</
|
|
353
|
+
</Button>
|
|
353
354
|
)}
|
|
354
355
|
</AccordionItemPanel>
|
|
355
356
|
</AccordionItem>
|
|
@@ -2,6 +2,7 @@ import { useState } from 'react'
|
|
|
2
2
|
import { SharedFilter } from '../../../../types/SharedFilter'
|
|
3
3
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
4
4
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
5
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
5
6
|
|
|
6
7
|
type APIModalProps = {
|
|
7
8
|
filter: SharedFilter
|
|
@@ -107,8 +108,9 @@ const APIModal: React.FC<APIModalProps> = ({ filter, isNestedDropdown, updateAPI
|
|
|
107
108
|
)}
|
|
108
109
|
</div>
|
|
109
110
|
<div className='d-flex justify-content-end mt-2'>
|
|
110
|
-
<
|
|
111
|
-
|
|
111
|
+
<Button
|
|
112
|
+
variant='primary'
|
|
113
|
+
className='mt-2'
|
|
112
114
|
onClick={() =>
|
|
113
115
|
updateAPIFilter(
|
|
114
116
|
APIEndpoint,
|
|
@@ -120,7 +122,7 @@ const APIModal: React.FC<APIModalProps> = ({ filter, isNestedDropdown, updateAPI
|
|
|
120
122
|
}
|
|
121
123
|
>
|
|
122
124
|
Save
|
|
123
|
-
</
|
|
125
|
+
</Button>
|
|
124
126
|
</div>
|
|
125
127
|
</fieldset>
|
|
126
128
|
)
|
package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx
CHANGED
|
@@ -1,58 +1,59 @@
|
|
|
1
|
-
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
2
|
-
import Modal from '@cdc/core/components/ui/Modal'
|
|
3
|
-
import { DashboardContext } from '../../../../DashboardContext'
|
|
4
|
-
import { useContext } from 'react'
|
|
5
|
-
import { DashboardFilters } from '../../../../types/DashboardFilters'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
1
|
+
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
2
|
+
import Modal from '@cdc/core/components/ui/Modal'
|
|
3
|
+
import { DashboardContext } from '../../../../DashboardContext'
|
|
4
|
+
import { useContext } from 'react'
|
|
5
|
+
import { DashboardFilters } from '../../../../types/DashboardFilters'
|
|
6
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
7
|
+
|
|
8
|
+
type DeleteFilterProps = {
|
|
9
|
+
removeFilterCompletely: (number) => void
|
|
10
|
+
removeFilterFromViz: (number) => void
|
|
11
|
+
filterIndex: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DeleteFilterModal: React.FC<DeleteFilterProps> = ({
|
|
15
|
+
removeFilterCompletely,
|
|
16
|
+
removeFilterFromViz,
|
|
17
|
+
filterIndex
|
|
18
|
+
}) => {
|
|
19
|
+
const { overlay } = useGlobalContext()
|
|
20
|
+
const { config } = useContext(DashboardContext)
|
|
21
|
+
|
|
22
|
+
const filterUsedByMany =
|
|
23
|
+
Object.values(config.visualizations).filter(viz => {
|
|
24
|
+
return (viz as DashboardFilters).sharedFilterIndexes?.map(Number).includes(Number(filterIndex))
|
|
25
|
+
}).length > 1
|
|
26
|
+
|
|
27
|
+
const message = filterUsedByMany
|
|
28
|
+
? 'This filter is used by multiple visualizations. You can either delete the filter from this visualization only or you can delete the filter completely, which will also remove it from other visualizations.'
|
|
29
|
+
: 'Are you sure you want to delete this filter?'
|
|
30
|
+
return (
|
|
31
|
+
<Modal showClose={true}>
|
|
32
|
+
<Modal.Content>
|
|
33
|
+
<p>{message}</p>
|
|
34
|
+
{filterUsedByMany && (
|
|
35
|
+
<Button
|
|
36
|
+
variant='warning'
|
|
37
|
+
onClick={() => {
|
|
38
|
+
removeFilterFromViz(filterIndex)
|
|
39
|
+
overlay?.actions.toggleOverlay()
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
Delete from Visualization
|
|
43
|
+
</Button>
|
|
44
|
+
)}
|
|
45
|
+
<Button
|
|
46
|
+
variant='danger'
|
|
47
|
+
onClick={() => {
|
|
48
|
+
removeFilterCompletely(filterIndex)
|
|
49
|
+
overlay?.actions.toggleOverlay()
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
Delete{filterUsedByMany ? ' Completely' : ''}
|
|
53
|
+
</Button>
|
|
54
|
+
</Modal.Content>
|
|
55
|
+
</Modal>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default DeleteFilterModal
|
package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { fireEvent, render, screen } 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
|
+
visualizations: {}
|
|
23
|
+
} as any
|
|
24
|
+
|
|
25
|
+
const createNestedFilter = (type: 'datafilter' | 'urlfilter') =>
|
|
26
|
+
({
|
|
27
|
+
key: 'Year and Quarter',
|
|
28
|
+
type,
|
|
29
|
+
filterStyle: 'nested-dropdown',
|
|
30
|
+
showDropdown: true,
|
|
31
|
+
values: ['2023', '2024'],
|
|
32
|
+
columnName: 'year',
|
|
33
|
+
id: 0,
|
|
34
|
+
parents: [],
|
|
35
|
+
order: 'asc',
|
|
36
|
+
subGrouping: {
|
|
37
|
+
columnName: 'quarter',
|
|
38
|
+
valuesLookup: {
|
|
39
|
+
'2023': { values: ['Q1', 'Q2'] },
|
|
40
|
+
'2024': { values: ['Q3', 'Q4'] }
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
...(type === 'urlfilter'
|
|
44
|
+
? {
|
|
45
|
+
apiFilter: {
|
|
46
|
+
apiEndpoint: '/api/nested-options',
|
|
47
|
+
valueSelector: 'year',
|
|
48
|
+
subgroupValueSelector: 'quarter'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
: {})
|
|
52
|
+
} as any)
|
|
53
|
+
|
|
54
|
+
describe('FilterEditor nested dropdown display toggle', () => {
|
|
55
|
+
it.each([
|
|
56
|
+
['data-backed nested filters', createNestedFilter('datafilter')],
|
|
57
|
+
['api-backed nested filters', createNestedFilter('urlfilter')]
|
|
58
|
+
])('renders the checkbox below Create query parameters for %s', (_label, filter) => {
|
|
59
|
+
const updateFilterProp = vi.fn()
|
|
60
|
+
|
|
61
|
+
render(
|
|
62
|
+
<FilterEditor
|
|
63
|
+
config={{
|
|
64
|
+
...baseConfig,
|
|
65
|
+
dashboard: { sharedFilters: [filter] }
|
|
66
|
+
}}
|
|
67
|
+
filter={filter}
|
|
68
|
+
filterIndex={0}
|
|
69
|
+
onNestedDragAreaHover={vi.fn()}
|
|
70
|
+
toggleNestedQueryParameters={vi.fn()}
|
|
71
|
+
updateFilterProp={updateFilterProp}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const queryParameters = screen.getByLabelText('Create query parameters')
|
|
76
|
+
const displaySubgroupingOnly = screen.getByLabelText('Display subgrouping only')
|
|
77
|
+
|
|
78
|
+
expect(displaySubgroupingOnly).not.toBeChecked()
|
|
79
|
+
|
|
80
|
+
const queryParametersLabel = queryParameters.closest('label')
|
|
81
|
+
const displaySubgroupingOnlyLabel = displaySubgroupingOnly.closest('label')
|
|
82
|
+
const isBelowQueryParameters = !!(
|
|
83
|
+
queryParametersLabel &&
|
|
84
|
+
displaySubgroupingOnlyLabel &&
|
|
85
|
+
queryParametersLabel.compareDocumentPosition(displaySubgroupingOnlyLabel) & Node.DOCUMENT_POSITION_FOLLOWING
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
expect(isBelowQueryParameters).toBe(true)
|
|
89
|
+
|
|
90
|
+
fireEvent.click(displaySubgroupingOnly)
|
|
91
|
+
|
|
92
|
+
expect(updateFilterProp).toHaveBeenCalledWith('displaySubgroupingOnly', true)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it.each([
|
|
96
|
+
[
|
|
97
|
+
'data-backed non-nested filters',
|
|
98
|
+
{
|
|
99
|
+
...createNestedFilter('datafilter'),
|
|
100
|
+
filterStyle: 'dropdown'
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
[
|
|
104
|
+
'api-backed non-nested filters',
|
|
105
|
+
{
|
|
106
|
+
...createNestedFilter('urlfilter'),
|
|
107
|
+
filterStyle: 'dropdown'
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
])('does not render the checkbox for %s', (_label, filter) => {
|
|
111
|
+
render(
|
|
112
|
+
<FilterEditor
|
|
113
|
+
config={{
|
|
114
|
+
...baseConfig,
|
|
115
|
+
dashboard: { sharedFilters: [filter] }
|
|
116
|
+
}}
|
|
117
|
+
filter={filter}
|
|
118
|
+
filterIndex={0}
|
|
119
|
+
onNestedDragAreaHover={vi.fn()}
|
|
120
|
+
toggleNestedQueryParameters={vi.fn()}
|
|
121
|
+
updateFilterProp={vi.fn()}
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
expect(screen.queryByLabelText('Display subgrouping only')).not.toBeInTheDocument()
|
|
126
|
+
})
|
|
127
|
+
})
|
|
@@ -19,6 +19,7 @@ import { filterOrderOptions } from '@cdc/core/helpers/filterOrderOptions'
|
|
|
19
19
|
import FilterOrder from '@cdc/core/components/EditorPanel/VizFilterEditor/components/FilterOrder'
|
|
20
20
|
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
21
21
|
import Modal from '@cdc/core/components/ui/Modal'
|
|
22
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
22
23
|
|
|
23
24
|
type FilterEditorProps = {
|
|
24
25
|
config: DashboardConfig
|
|
@@ -56,7 +57,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
56
57
|
const getVizTitle = (viz, vizKey) => {
|
|
57
58
|
let vizName = viz.general?.title || viz.title || vizKey
|
|
58
59
|
if (viz.visualizationType === 'markup-include') {
|
|
59
|
-
vizName = viz.contentEditor
|
|
60
|
+
vizName = viz.contentEditor?.title || vizKey
|
|
60
61
|
}
|
|
61
62
|
return vizName
|
|
62
63
|
}
|
|
@@ -419,12 +420,13 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
419
420
|
</div>
|
|
420
421
|
)}
|
|
421
422
|
|
|
422
|
-
<
|
|
423
|
+
<Button
|
|
424
|
+
variant='primary'
|
|
425
|
+
className='mt-2'
|
|
423
426
|
onClick={() => handleEditAPIValues(filter, isNestedDropdown, updateAPIFilter)}
|
|
424
|
-
className='btn btn-primary mt-2'
|
|
425
427
|
>
|
|
426
428
|
Edit API Values
|
|
427
|
-
</
|
|
429
|
+
</Button>
|
|
428
430
|
</div>
|
|
429
431
|
|
|
430
432
|
<label>
|
|
@@ -451,6 +453,18 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
451
453
|
</span>
|
|
452
454
|
</label>
|
|
453
455
|
|
|
456
|
+
{isNestedDropdown && (
|
|
457
|
+
<label>
|
|
458
|
+
<input
|
|
459
|
+
type='checkbox'
|
|
460
|
+
checked={!!filter.displaySubgroupingOnly}
|
|
461
|
+
aria-label='Display subgrouping only'
|
|
462
|
+
onChange={e => updateFilterProp('displaySubgroupingOnly', e.target.checked)}
|
|
463
|
+
/>
|
|
464
|
+
<span> Display subgrouping only</span>
|
|
465
|
+
</label>
|
|
466
|
+
)}
|
|
467
|
+
|
|
454
468
|
{!!parentFilters.length && (
|
|
455
469
|
<label>
|
|
456
470
|
<span className='edit-label column-heading mt-1'>Parent Filter(s): </span>
|
|
@@ -596,6 +610,16 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
596
610
|
</Tooltip>
|
|
597
611
|
</span>
|
|
598
612
|
</label>
|
|
613
|
+
|
|
614
|
+
<label>
|
|
615
|
+
<input
|
|
616
|
+
type='checkbox'
|
|
617
|
+
checked={!!filter.displaySubgroupingOnly}
|
|
618
|
+
aria-label='Display subgrouping only'
|
|
619
|
+
onChange={e => updateFilterProp('displaySubgroupingOnly', e.target.checked)}
|
|
620
|
+
/>
|
|
621
|
+
<span> Display subgrouping only</span>
|
|
622
|
+
</label>
|
|
599
623
|
</>
|
|
600
624
|
)}
|
|
601
625
|
<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
|
)}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
2
|
+
|
|
1
3
|
type ExpandCollapseButtonsProps = {
|
|
2
4
|
setAllExpanded: Function
|
|
3
5
|
}
|
|
@@ -6,12 +8,12 @@ const ExpandCollapseButtons: React.FC<ExpandCollapseButtonsProps> = ({ setAllExp
|
|
|
6
8
|
return (
|
|
7
9
|
<div className='d-block '>
|
|
8
10
|
<div className='d-flex flex-row-reverse mb-2'>
|
|
9
|
-
<
|
|
11
|
+
<Button variant='light' onClick={() => setAllExpanded(false)}>
|
|
10
12
|
- Collapse All
|
|
11
|
-
</
|
|
12
|
-
<
|
|
13
|
+
</Button>
|
|
14
|
+
<Button variant='light' className='me-2' onClick={() => setAllExpanded(true)}>
|
|
13
15
|
+ Expand All
|
|
14
|
-
</
|
|
16
|
+
</Button>
|
|
15
17
|
</div>
|
|
16
18
|
</div>
|
|
17
19
|
)
|
package/src/components/Grid.tsx
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
import Row from './Row'
|
|
3
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
3
4
|
|
|
4
5
|
import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
|
|
5
6
|
import { ConfigRow } from '../types/ConfigRow'
|
|
6
7
|
|
|
7
8
|
const Grid = () => {
|
|
8
9
|
const { config } = useContext(DashboardContext)
|
|
10
|
+
const dispatch = useContext(DashboardDispatchContext)
|
|
9
11
|
if (!config) return null
|
|
10
12
|
const rows = config.rows
|
|
11
|
-
const dispatch = useContext(DashboardDispatchContext)
|
|
12
13
|
const updateConfig = config => dispatch({ type: 'UPDATE_CONFIG', payload: [config] })
|
|
13
14
|
const addRow = () => {
|
|
14
15
|
const blankRow: Partial<ConfigRow> = { columns: [{ width: 12 }] }
|
|
@@ -24,9 +25,9 @@ const Grid = () => {
|
|
|
24
25
|
{(rows || []).map((row, idx) => (
|
|
25
26
|
<Row row={row} idx={idx} uuid={row.uuid} key={idx} />
|
|
26
27
|
))}
|
|
27
|
-
<
|
|
28
|
+
<Button variant='primary' className='col' onClick={addRow}>
|
|
28
29
|
Add Row
|
|
29
|
-
</
|
|
30
|
+
</Button>
|
|
30
31
|
</div>
|
|
31
32
|
)
|
|
32
33
|
}
|