@cdc/dashboard 4.26.2 → 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 +56686 -50281
- package/examples/__data__/data-2.json +6 -0
- package/examples/__data__/data-with-metadata.json +18 -0
- package/examples/__data__/data.json +6 -0
- package/examples/default.json +7 -36
- package/examples/legend-issue.json +1 -1
- package/examples/minimal-example.json +34 -0
- package/examples/private/dengue.json +4640 -0
- package/examples/private/inline-markup.json +775 -0
- package/examples/private/link_to_file.json +16662 -0
- package/examples/private/recent-update.json +1456 -0
- package/examples/private/toggle.json +10137 -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/CdcDashboard.tsx +2 -1
- package/src/CdcDashboardComponent.tsx +48 -28
- package/src/_stories/Dashboard.DataSetup.stories.tsx +6 -1
- package/src/_stories/Dashboard.Pages.smoke.stories.tsx +22 -0
- package/src/_stories/Dashboard.smoke.stories.tsx +33 -0
- package/src/_stories/Dashboard.stories.tsx +4523 -83
- package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
- package/src/_stories/_mock/tab-simple-filter.json +153 -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 +29 -10
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +12 -8
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +6 -4
- 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 +29 -6
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +10 -9
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +8 -8
- package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +1 -1
- package/src/components/DashboardFilters/dashboardfilter.styles.css +3 -3
- package/src/components/DataDesignerModal.tsx +2 -2
- package/src/components/ExpandCollapseButtons.tsx +6 -4
- package/src/components/Grid.tsx +4 -3
- package/src/components/Header/Header.tsx +27 -5
- package/src/components/Header/index.scss +1 -1
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
- package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +6 -6
- package/src/components/Row.tsx +30 -8
- package/src/components/Toggle/toggle-style.css +7 -7
- package/src/components/VisualizationRow.tsx +81 -22
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +2 -55
- package/src/components/VisualizationsPanel/visualizations-panel-styles.css +2 -2
- package/src/components/Widget/Widget.tsx +7 -6
- package/src/components/Widget/widget.styles.css +48 -17
- package/src/data/initial-state.js +2 -1
- package/src/helpers/addVisualization.ts +73 -0
- package/src/helpers/formatConfigBeforeSave.ts +1 -1
- package/src/helpers/getVizConfig.ts +13 -3
- package/src/helpers/iconHash.tsx +45 -36
- package/src/helpers/processDataLegacy.ts +19 -14
- package/src/helpers/tests/addVisualization.test.ts +52 -0
- package/src/helpers/tests/formatConfigBeforeSave.test.ts +81 -1
- package/src/scss/editor-panel.scss +1 -1
- package/src/scss/grid.scss +38 -8
- package/src/scss/main.scss +237 -40
- package/src/store/dashboard.reducer.ts +2 -1
- package/src/test/CdcDashboard.test.jsx +26 -2
- package/src/test/CdcDashboardComponent.test.tsx +74 -0
- package/src/types/FilterStyles.ts +2 -1
- package/src/types/SharedFilter.ts +1 -0
- package/tests/fixtures/dashboard-config-with-metadata.json +89 -0
- package/vite.config.js +2 -2
- package/dist/cdcdashboard-Cf9_fbQf.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/{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
|
@@ -9,8 +9,10 @@ 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'
|
|
15
|
+
import Tabs from '@cdc/core/components/Filters/components/Tabs'
|
|
14
16
|
|
|
15
17
|
type DashboardFilterProps = {
|
|
16
18
|
show: number[]
|
|
@@ -63,7 +65,12 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
63
65
|
const urlFilterType = filter.type === 'urlfilter'
|
|
64
66
|
const label = stripDuplicateLabelIncrement(filter.key || '')
|
|
65
67
|
|
|
66
|
-
if (
|
|
68
|
+
if (
|
|
69
|
+
!urlFilterType &&
|
|
70
|
+
!filter.showDropdown &&
|
|
71
|
+
filter.filterStyle !== FILTER_STYLE.nestedDropdown &&
|
|
72
|
+
filter.filterStyle !== FILTER_STYLE.tabSimple
|
|
73
|
+
)
|
|
67
74
|
return <React.Fragment key={`${filter.key}-filtersection-${filterIndex}-option`} />
|
|
68
75
|
const values: JSX.Element[] = []
|
|
69
76
|
|
|
@@ -125,7 +132,10 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
125
132
|
)
|
|
126
133
|
}
|
|
127
134
|
|
|
128
|
-
const
|
|
135
|
+
const isTabSimple = filter.filterStyle === FILTER_STYLE.tabSimple
|
|
136
|
+
const formGroupClass = `form-group${isTabSimple ? '' : ' me-4'} mb-1${loading ? ' loading-filter' : ''}${
|
|
137
|
+
isTabSimple ? ' w-100' : ''
|
|
138
|
+
}`
|
|
129
139
|
return (
|
|
130
140
|
<div className={formGroupClass} key={`${filter.key}-filtersection-${filterIndex}`}>
|
|
131
141
|
{label && (
|
|
@@ -133,7 +143,14 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
133
143
|
{label}
|
|
134
144
|
</label>
|
|
135
145
|
)}
|
|
136
|
-
{filter.filterStyle === FILTER_STYLE.
|
|
146
|
+
{filter.filterStyle === FILTER_STYLE.tabSimple ? (
|
|
147
|
+
<Tabs
|
|
148
|
+
filter={filter}
|
|
149
|
+
index={filterIndex}
|
|
150
|
+
changeFilterActive={(index, value) => handleOnChange(index, value)}
|
|
151
|
+
loading={loading}
|
|
152
|
+
/>
|
|
153
|
+
) : filter.filterStyle === FILTER_STYLE.multiSelect ? (
|
|
137
154
|
<MultiSelect
|
|
138
155
|
label={label}
|
|
139
156
|
options={multiValues}
|
|
@@ -147,6 +164,7 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
147
164
|
<NestedDropdown
|
|
148
165
|
activeGroup={(filter.queuedActive?.[0] || filter.active) as string}
|
|
149
166
|
activeSubGroup={(filter.queuedActive?.[1] || filter.subGrouping?.active) as string}
|
|
167
|
+
displaySubgroupingOnly={filter.displaySubgroupingOnly}
|
|
150
168
|
filterIndex={filterIndex}
|
|
151
169
|
options={_key ? getNestedDropdownOptions(apiFilterDropdowns[_key]) : nestedOptions}
|
|
152
170
|
listLabel={label}
|
|
@@ -197,9 +215,10 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
197
215
|
)
|
|
198
216
|
})}
|
|
199
217
|
{showSubmit && (
|
|
200
|
-
|
|
201
|
-
<
|
|
202
|
-
|
|
218
|
+
<div className='dashboard-filters__actions'>
|
|
219
|
+
<Button
|
|
220
|
+
variant='primary'
|
|
221
|
+
className='mb-1 me-2'
|
|
203
222
|
onClick={applyFilters}
|
|
204
223
|
disabled={show.some(filterIndex => {
|
|
205
224
|
const emptyFilterValues = [undefined, '', '- Select -']
|
|
@@ -210,13 +229,13 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
210
229
|
})}
|
|
211
230
|
>
|
|
212
231
|
{applyFiltersButtonText || 'GO!'}
|
|
213
|
-
</
|
|
232
|
+
</Button>
|
|
214
233
|
{handleReset && (
|
|
215
|
-
<
|
|
234
|
+
<Button variant='link' className='mb-1' onClick={handleReset}>
|
|
216
235
|
Clear Filters
|
|
217
|
-
</
|
|
236
|
+
</Button>
|
|
218
237
|
)}
|
|
219
|
-
|
|
238
|
+
</div>
|
|
220
239
|
)}
|
|
221
240
|
</form>
|
|
222
241
|
)
|
|
@@ -12,7 +12,7 @@ import Icon from '@cdc/core/components/ui/Icon'
|
|
|
12
12
|
import FieldSetWrapper from '@cdc/core/components/EditorPanel/FieldSetWrapper'
|
|
13
13
|
import FilterEditor from './components/FilterEditor'
|
|
14
14
|
import { DashboardContext, DashboardDispatchContext } from '../../../DashboardContext'
|
|
15
|
-
import
|
|
15
|
+
import cloneDeep from 'lodash/cloneDeep'
|
|
16
16
|
import { DashboardFilters } from '../../../types/DashboardFilters'
|
|
17
17
|
import { SharedFilter } from '../../../types/SharedFilter'
|
|
18
18
|
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
@@ -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
|
|
@@ -55,7 +56,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
|
|
|
55
56
|
const [isNestedDragHovered, setIsNestedDragHovered] = useState(false)
|
|
56
57
|
|
|
57
58
|
const updateFilterProp = (prop: string, index: number, value) => {
|
|
58
|
-
const newSharedFilters =
|
|
59
|
+
const newSharedFilters = cloneDeep(sharedFilters)
|
|
59
60
|
const {
|
|
60
61
|
apiEndpoint: oldEndpoint,
|
|
61
62
|
valueSelector: oldValueSelector,
|
|
@@ -102,6 +103,9 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
|
|
|
102
103
|
// changing a api filter and want to load the api data into the preview.
|
|
103
104
|
// automatically dispatches SET_SHARED_FILTERS
|
|
104
105
|
loadAPIFilters(newSharedFilters, {})
|
|
106
|
+
} else if (prop === 'defaultValue') {
|
|
107
|
+
newSharedFilters[index].active = value
|
|
108
|
+
dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
|
|
105
109
|
} else {
|
|
106
110
|
handleSorting(newSharedFilters[index])
|
|
107
111
|
dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
|
|
@@ -109,7 +113,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
|
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
const toggleNestedQueryParameters = (index, checked: boolean) => {
|
|
112
|
-
const newSharedFilters =
|
|
116
|
+
const newSharedFilters = cloneDeep(sharedFilters)
|
|
113
117
|
const filter = newSharedFilters[index]
|
|
114
118
|
const isUrlFilter = filter.type === 'urlfilter'
|
|
115
119
|
const groupColumnName = isUrlFilter ? filter.apiFilter.valueSelector : filter.columnName
|
|
@@ -149,7 +153,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
|
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
const addNewFilter = () => {
|
|
152
|
-
const _sharedFilters =
|
|
156
|
+
const _sharedFilters = cloneDeep(sharedFilters) || []
|
|
153
157
|
const columnName = 'New Dashboard Filter ' + (_sharedFilters.length + 1)
|
|
154
158
|
const newFilter = { key: columnName, showDropdown: true, values: [] } as SharedFilter
|
|
155
159
|
dispatch({ type: 'SET_SHARED_FILTERS', payload: [..._sharedFilters, newFilter] })
|
|
@@ -311,9 +315,9 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
|
|
|
311
315
|
)}
|
|
312
316
|
</Droppable>
|
|
313
317
|
</DragDropContext>
|
|
314
|
-
<
|
|
318
|
+
<Button variant='primary' fullWidth onClick={addNewFilter}>
|
|
315
319
|
Add Filter
|
|
316
|
-
</
|
|
320
|
+
</Button>
|
|
317
321
|
{canAddExisting ? (
|
|
318
322
|
<label>
|
|
319
323
|
<span className='edit-label column-heading'>
|
|
@@ -344,9 +348,9 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
|
|
|
344
348
|
/>
|
|
345
349
|
</label>
|
|
346
350
|
) : (
|
|
347
|
-
<
|
|
351
|
+
<Button variant='primary' fullWidth className='mt-2' onClick={() => setCanAddExisting(true)}>
|
|
348
352
|
Add Existing Dashboard Filter
|
|
349
|
-
</
|
|
353
|
+
</Button>
|
|
350
354
|
)}
|
|
351
355
|
</AccordionItemPanel>
|
|
352
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
|
|
@@ -16,7 +17,7 @@ const APIModal: React.FC<APIModalProps> = ({ filter, isNestedDropdown, updateAPI
|
|
|
16
17
|
const [APISubGroupValueSelector, setAPISubGroupValueSelector] = useState(filter.apiFilter?.subgroupValueSelector)
|
|
17
18
|
const [APISubGroupTextSelector, setAPISubGroupTextSelector] = useState(filter.apiFilter?.subgroupTextSelector)
|
|
18
19
|
return (
|
|
19
|
-
<fieldset className='mb-1 px-3
|
|
20
|
+
<fieldset className='mb-1 px-3 cove-visualization'>
|
|
20
21
|
<label className='d-block'>
|
|
21
22
|
<span>API Endpoint: </span>
|
|
22
23
|
<textarea
|
|
@@ -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
|
+
})
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from 'lodash'
|
|
2
1
|
import { getVizRowColumnLocator } from '../../../../helpers/getVizRowColumnLocator'
|
|
3
2
|
import { Select, TextField } from '@cdc/core/components/EditorPanel/Inputs'
|
|
4
3
|
import DataTransform from '@cdc/core/helpers/DataTransform'
|
|
@@ -20,6 +19,7 @@ import { filterOrderOptions } from '@cdc/core/helpers/filterOrderOptions'
|
|
|
20
19
|
import FilterOrder from '@cdc/core/components/EditorPanel/VizFilterEditor/components/FilterOrder'
|
|
21
20
|
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
22
21
|
import Modal from '@cdc/core/components/ui/Modal'
|
|
22
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
23
23
|
|
|
24
24
|
type FilterEditorProps = {
|
|
25
25
|
config: DashboardConfig
|
|
@@ -57,7 +57,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
57
57
|
const getVizTitle = (viz, vizKey) => {
|
|
58
58
|
let vizName = viz.general?.title || viz.title || vizKey
|
|
59
59
|
if (viz.visualizationType === 'markup-include') {
|
|
60
|
-
vizName = viz.contentEditor
|
|
60
|
+
vizName = viz.contentEditor?.title || vizKey
|
|
61
61
|
}
|
|
62
62
|
return vizName
|
|
63
63
|
}
|
|
@@ -105,7 +105,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
105
105
|
let _dataSet = config.datasets[dataKey]
|
|
106
106
|
if (!_dataSet.data && _dataSet.dataUrl) {
|
|
107
107
|
setDataFiltersLoading(true)
|
|
108
|
-
let data = await fetchRemoteData(_dataSet.dataUrl)
|
|
108
|
+
let data = (await fetchRemoteData(_dataSet.dataUrl)).data
|
|
109
109
|
if (_dataSet.dataDescription && data && data.length > 0) {
|
|
110
110
|
try {
|
|
111
111
|
data = transform.autoStandardize(data)
|
|
@@ -420,12 +420,13 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
420
420
|
</div>
|
|
421
421
|
)}
|
|
422
422
|
|
|
423
|
-
<
|
|
423
|
+
<Button
|
|
424
|
+
variant='primary'
|
|
425
|
+
className='mt-2'
|
|
424
426
|
onClick={() => handleEditAPIValues(filter, isNestedDropdown, updateAPIFilter)}
|
|
425
|
-
className='btn btn-primary mt-2'
|
|
426
427
|
>
|
|
427
428
|
Edit API Values
|
|
428
|
-
</
|
|
429
|
+
</Button>
|
|
429
430
|
</div>
|
|
430
431
|
|
|
431
432
|
<label>
|
|
@@ -452,6 +453,18 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
452
453
|
</span>
|
|
453
454
|
</label>
|
|
454
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
|
+
|
|
455
468
|
{!!parentFilters.length && (
|
|
456
469
|
<label>
|
|
457
470
|
<span className='edit-label column-heading mt-1'>Parent Filter(s): </span>
|
|
@@ -597,6 +610,16 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
|
|
|
597
610
|
</Tooltip>
|
|
598
611
|
</span>
|
|
599
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>
|
|
600
623
|
</>
|
|
601
624
|
)}
|
|
602
625
|
<Select
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DashboardConfig } from '../../../../types/DashboardConfig'
|
|
2
2
|
import { SharedFilter } from '../../../../types/SharedFilter'
|
|
3
|
-
import
|
|
3
|
+
import cloneDeep from 'lodash/cloneDeep'
|
|
4
|
+
import uniq from 'lodash/uniq'
|
|
4
5
|
import { SubGrouping, OrderBy } from '@cdc/core/types/VizFilter'
|
|
5
6
|
import { TextField, Select } from '@cdc/core/components/EditorPanel/Inputs'
|
|
6
7
|
import { handleSorting } from '@cdc/core/components/Filters/helpers/handleSorting'
|
|
@@ -72,7 +73,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
|
|
|
72
73
|
const order = subGrouping?.order || 'asc'
|
|
73
74
|
|
|
74
75
|
const valuesLookup = filter.values.reduce((acc, groupName) => {
|
|
75
|
-
const rawValues: string[] =
|
|
76
|
+
const rawValues: string[] = uniq(
|
|
76
77
|
config.datasets[selectedOptionDatasetName].data
|
|
77
78
|
.map(d => {
|
|
78
79
|
return d[filter.columnName] === groupName ? d[newColumnName] : ''
|
|
@@ -104,7 +105,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
|
|
|
104
105
|
// Handle group order change (asc/desc/cust)
|
|
105
106
|
const handleGroupingOrderBy = (order: OrderBy) => {
|
|
106
107
|
const groupSortObject = {
|
|
107
|
-
values:
|
|
108
|
+
values: cloneDeep(filter.values),
|
|
108
109
|
order
|
|
109
110
|
}
|
|
110
111
|
const { values: newOrderedValues } = handleSorting(groupSortObject)
|
|
@@ -128,7 +129,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
|
|
|
128
129
|
const handleGroupingCustomOrder = (sourceIndex: number, destinationIndex: number) => {
|
|
129
130
|
if (sourceIndex === undefined || destinationIndex === undefined || sourceIndex === destinationIndex) return
|
|
130
131
|
|
|
131
|
-
const orderedValues =
|
|
132
|
+
const orderedValues = cloneDeep(filter.orderedValues || filter.values)
|
|
132
133
|
const [movedItem] = orderedValues.splice(sourceIndex, 1)
|
|
133
134
|
orderedValues.splice(destinationIndex, 0, movedItem)
|
|
134
135
|
|
|
@@ -143,7 +144,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
|
|
|
143
144
|
const handleSubGroupingOrderBy = (order: OrderBy) => {
|
|
144
145
|
const newValuesLookup = Object.keys(subGrouping.valuesLookup).reduce((acc, groupName) => {
|
|
145
146
|
const subGroup = subGrouping.valuesLookup[groupName]
|
|
146
|
-
const { values: sortedValues } = handleSorting({ values:
|
|
147
|
+
const { values: sortedValues } = handleSorting({ values: cloneDeep(subGroup.values), order })
|
|
147
148
|
|
|
148
149
|
acc[groupName] = {
|
|
149
150
|
values: sortedValues,
|
|
@@ -170,11 +171,11 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
|
|
|
170
171
|
) => {
|
|
171
172
|
if (sourceIndex === undefined || destinationIndex === undefined || sourceIndex === destinationIndex) return
|
|
172
173
|
|
|
173
|
-
const updatedGroupOrderedValues =
|
|
174
|
+
const updatedGroupOrderedValues = cloneDeep(currentOrderedValues)
|
|
174
175
|
const [movedItem] = updatedGroupOrderedValues.splice(sourceIndex, 1)
|
|
175
176
|
updatedGroupOrderedValues.splice(destinationIndex, 0, movedItem)
|
|
176
177
|
|
|
177
|
-
const newSubGrouping =
|
|
178
|
+
const newSubGrouping = cloneDeep(subGrouping)
|
|
178
179
|
newSubGrouping.valuesLookup[groupName].values = updatedGroupOrderedValues
|
|
179
180
|
newSubGrouping.valuesLookup[groupName].orderedValues = updatedGroupOrderedValues
|
|
180
181
|
newSubGrouping.order = 'cust'
|
|
@@ -241,7 +242,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
|
|
|
241
242
|
/>
|
|
242
243
|
)}
|
|
243
244
|
|
|
244
|
-
{/* Default Value for
|
|
245
|
+
{/* Default Value for Subgroup */}
|
|
245
246
|
{subGrouping?.columnName && (filter.defaultValue || filter.active) && subGrouping.valuesLookup && (
|
|
246
247
|
<Select
|
|
247
248
|
value={subGrouping.defaultValue}
|
|
@@ -254,7 +255,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
|
|
|
254
255
|
const newSubGrouping = { ...subGrouping, defaultValue: value }
|
|
255
256
|
updateFilterProp('subGrouping', newSubGrouping)
|
|
256
257
|
}}
|
|
257
|
-
label={'
|
|
258
|
+
label={'Subgroup Default Value'}
|
|
258
259
|
initial={'Select'}
|
|
259
260
|
/>
|
|
260
261
|
)}
|
|
@@ -7,7 +7,7 @@ import { FilterBehavior } from '../../helpers/FilterBehavior'
|
|
|
7
7
|
import { getFilteredData } from '../../helpers/getFilteredData'
|
|
8
8
|
import { DashboardFilters } from '../../types/DashboardFilters'
|
|
9
9
|
import { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
|
|
10
|
-
import
|
|
10
|
+
import { VisualizationWrapper, Sidebar, Responsive } from '@cdc/core/components/Layout'
|
|
11
11
|
import DashboardFiltersEditor from './DashboardFiltersEditor'
|
|
12
12
|
import { ViewPort } from '@cdc/core/types/ViewPort'
|
|
13
13
|
import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavior'
|
|
@@ -302,24 +302,24 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
302
302
|
const displayNone = filters?.length ? filters.every(filter => filter.showDropdown === false) : false
|
|
303
303
|
if (displayNone && !isEditor) return <></>
|
|
304
304
|
return (
|
|
305
|
-
<
|
|
305
|
+
<VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
|
|
306
306
|
{isEditor && (
|
|
307
|
-
<
|
|
307
|
+
<Sidebar
|
|
308
308
|
displayPanel={displayPanel}
|
|
309
309
|
isDashboard={true}
|
|
310
310
|
title={'Configure Dashboard Filters'}
|
|
311
311
|
onBackClick={onBackClick}
|
|
312
312
|
>
|
|
313
313
|
<DashboardFiltersEditor updateConfig={updateConfig} vizConfig={visualizationConfig} />
|
|
314
|
-
</
|
|
314
|
+
</Sidebar>
|
|
315
315
|
)}
|
|
316
316
|
|
|
317
317
|
{!displayNone && (
|
|
318
|
-
<
|
|
318
|
+
<Responsive isEditor={isEditor}>
|
|
319
319
|
<div
|
|
320
320
|
className={`${
|
|
321
321
|
isEditor ? ' is-editor' : ''
|
|
322
|
-
} cove-
|
|
322
|
+
} cove-visualization__inner cove-visualization__body col-12 cove-dashboard-filters-container`}
|
|
323
323
|
>
|
|
324
324
|
<Filters
|
|
325
325
|
show={visualizationConfig?.sharedFilterIndexes?.map(Number)}
|
|
@@ -337,9 +337,9 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
337
337
|
}
|
|
338
338
|
/>
|
|
339
339
|
</div>
|
|
340
|
-
</
|
|
340
|
+
</Responsive>
|
|
341
341
|
)}
|
|
342
|
-
</
|
|
342
|
+
</VisualizationWrapper>
|
|
343
343
|
)
|
|
344
344
|
}
|
|
345
345
|
|