@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.
Files changed (109) hide show
  1. package/CONFIG.md +172 -0
  2. package/README.md +60 -20
  3. package/dist/cdcdashboard-CY9IcPSi.es.js +6 -0
  4. package/dist/cdcdashboard-DlpiY3fQ.es.js +4 -0
  5. package/dist/cdcdashboard.js +56686 -50281
  6. package/examples/__data__/data-2.json +6 -0
  7. package/examples/__data__/data-with-metadata.json +18 -0
  8. package/examples/__data__/data.json +6 -0
  9. package/examples/default.json +7 -36
  10. package/examples/legend-issue.json +1 -1
  11. package/examples/minimal-example.json +34 -0
  12. package/examples/private/dengue.json +4640 -0
  13. package/examples/private/inline-markup.json +775 -0
  14. package/examples/private/link_to_file.json +16662 -0
  15. package/examples/private/recent-update.json +1456 -0
  16. package/examples/private/toggle.json +10137 -0
  17. package/examples/sankey.json +3 -3
  18. package/examples/test-api-filter-reset.json +4 -4
  19. package/examples/tp5-test.json +86 -4
  20. package/package.json +9 -9
  21. package/src/CdcDashboard.tsx +2 -1
  22. package/src/CdcDashboardComponent.tsx +48 -28
  23. package/src/_stories/Dashboard.DataSetup.stories.tsx +6 -1
  24. package/src/_stories/Dashboard.Pages.smoke.stories.tsx +22 -0
  25. package/src/_stories/Dashboard.smoke.stories.tsx +33 -0
  26. package/src/_stories/Dashboard.stories.tsx +4523 -83
  27. package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
  28. package/src/_stories/_mock/tab-simple-filter.json +153 -0
  29. package/src/_stories/_mock/tp5-test.json +86 -5
  30. package/src/components/DashboardEditors.tsx +15 -0
  31. package/src/components/DashboardFilters/DashboardFilters.test.tsx +129 -0
  32. package/src/components/DashboardFilters/DashboardFilters.tsx +29 -10
  33. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +12 -8
  34. package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +6 -4
  35. package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +59 -58
  36. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +127 -0
  37. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +29 -6
  38. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +10 -9
  39. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +8 -8
  40. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +1 -1
  41. package/src/components/DashboardFilters/dashboardfilter.styles.css +3 -3
  42. package/src/components/DataDesignerModal.tsx +2 -2
  43. package/src/components/ExpandCollapseButtons.tsx +6 -4
  44. package/src/components/Grid.tsx +4 -3
  45. package/src/components/Header/Header.tsx +27 -5
  46. package/src/components/Header/index.scss +1 -1
  47. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
  48. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +6 -6
  49. package/src/components/Row.tsx +30 -8
  50. package/src/components/Toggle/toggle-style.css +7 -7
  51. package/src/components/VisualizationRow.tsx +81 -22
  52. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +2 -55
  53. package/src/components/VisualizationsPanel/visualizations-panel-styles.css +2 -2
  54. package/src/components/Widget/Widget.tsx +7 -6
  55. package/src/components/Widget/widget.styles.css +48 -17
  56. package/src/data/initial-state.js +2 -1
  57. package/src/helpers/addVisualization.ts +73 -0
  58. package/src/helpers/formatConfigBeforeSave.ts +1 -1
  59. package/src/helpers/getVizConfig.ts +13 -3
  60. package/src/helpers/iconHash.tsx +45 -36
  61. package/src/helpers/processDataLegacy.ts +19 -14
  62. package/src/helpers/tests/addVisualization.test.ts +52 -0
  63. package/src/helpers/tests/formatConfigBeforeSave.test.ts +81 -1
  64. package/src/scss/editor-panel.scss +1 -1
  65. package/src/scss/grid.scss +38 -8
  66. package/src/scss/main.scss +237 -40
  67. package/src/store/dashboard.reducer.ts +2 -1
  68. package/src/test/CdcDashboard.test.jsx +26 -2
  69. package/src/test/CdcDashboardComponent.test.tsx +74 -0
  70. package/src/types/FilterStyles.ts +2 -1
  71. package/src/types/SharedFilter.ts +1 -0
  72. package/tests/fixtures/dashboard-config-with-metadata.json +89 -0
  73. package/vite.config.js +2 -2
  74. package/dist/cdcdashboard-Cf9_fbQf.es.js +0 -6
  75. package/examples/DEV-6574.json +0 -2224
  76. package/examples/api-dashboard-data.json +0 -272
  77. package/examples/api-dashboard-years.json +0 -11
  78. package/examples/api-geographies-data.json +0 -11
  79. package/examples/chart-data.json +0 -5409
  80. package/examples/custom/css/respiratory.css +0 -236
  81. package/examples/custom/js/respiratory.js +0 -242
  82. package/examples/default-data.json +0 -368
  83. package/examples/default-filter-control.json +0 -209
  84. package/examples/default-multi-dataset-shared-filter.json +0 -1729
  85. package/examples/default-multi-dataset.json +0 -506
  86. package/examples/ed-visits-county-file.json +0 -402
  87. package/examples/filters/Alabama.json +0 -72
  88. package/examples/filters/Alaska.json +0 -1737
  89. package/examples/filters/Arkansas.json +0 -4713
  90. package/examples/filters/California.json +0 -212
  91. package/examples/filters/Colorado.json +0 -1500
  92. package/examples/filters/Connecticut.json +0 -559
  93. package/examples/filters/Delaware.json +0 -63
  94. package/examples/filters/DistrictofColumbia.json +0 -63
  95. package/examples/filters/Florida.json +0 -4217
  96. package/examples/filters/States.json +0 -146
  97. package/examples/state-level.json +0 -90136
  98. package/examples/state-points.json +0 -10474
  99. package/examples/temp-example-data.json +0 -130
  100. package/examples/test-dashboard-simple.json +0 -503
  101. package/examples/test-example.json +0 -752
  102. package/examples/test-file.json +0 -147
  103. package/examples/test.json +0 -752
  104. package/examples/testing.json +0 -94456
  105. /package/examples/{legend-issue-data.json → __data__/legend-issue-data.json} +0 -0
  106. /package/examples/api-test/{categories.json → __data__/categories.json} +0 -0
  107. /package/examples/api-test/{chart-data.json → __data__/chart-data.json} +0 -0
  108. /package/examples/api-test/{topics.json → __data__/topics.json} +0 -0
  109. /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 (!urlFilterType && !filter.showDropdown && filter.filterStyle !== FILTER_STYLE.nestedDropdown)
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 formGroupClass = `form-group me-4 mb-1${loading ? ' loading-filter' : ''}`
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.multiSelect ? (
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
- <button
202
- className='btn btn-primary mb-1 me-2'
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
- </button>
232
+ </Button>
214
233
  {handleReset && (
215
- <button className='btn btn-link mb-1' onClick={handleReset}>
234
+ <Button variant='link' className='mb-1' onClick={handleReset}>
216
235
  Clear Filters
217
- </button>
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 _ from 'lodash'
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 = _.cloneDeep(sharedFilters)
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 = _.cloneDeep(sharedFilters)
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 = _.cloneDeep(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
- <button onClick={addNewFilter} className='btn btn-primary full-width'>
318
+ <Button variant='primary' fullWidth onClick={addNewFilter}>
315
319
  Add Filter
316
- </button>
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
- <button onClick={() => setCanAddExisting(true)} className='btn btn-primary full-width mt-2'>
351
+ <Button variant='primary' fullWidth className='mt-2' onClick={() => setCanAddExisting(true)}>
348
352
  Add Existing Dashboard Filter
349
- </button>
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 cdc-open-viz-module'>
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
- <button
111
- className='btn btn-primary mt-2'
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
- </button>
125
+ </Button>
124
126
  </div>
125
127
  </fieldset>
126
128
  )
@@ -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
- type DeleteFilterProps = {
8
- removeFilterCompletely: (number) => void
9
- removeFilterFromViz: (number) => void
10
- filterIndex: number
11
- }
12
-
13
- const DeleteFilterModal: React.FC<DeleteFilterProps> = ({
14
- removeFilterCompletely,
15
- removeFilterFromViz,
16
- filterIndex
17
- }) => {
18
- const { overlay } = useGlobalContext()
19
- const { config } = useContext(DashboardContext)
20
-
21
- const filterUsedByMany =
22
- Object.values(config.visualizations).filter(viz => {
23
- return (viz as DashboardFilters).sharedFilterIndexes?.map(Number).includes(Number(filterIndex))
24
- }).length > 1
25
-
26
- const message = filterUsedByMany
27
- ? '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.'
28
- : 'Are you sure you want to delete this filter?'
29
- return (
30
- <Modal showClose={true}>
31
- <Modal.Content>
32
- <p>{message}</p>
33
- {filterUsedByMany && (
34
- <button
35
- className='btn btn-warning'
36
- onClick={() => {
37
- removeFilterFromViz(filterIndex)
38
- overlay?.actions.toggleOverlay()
39
- }}
40
- >
41
- Delete from Visualization
42
- </button>
43
- )}
44
- <button
45
- className='btn btn-danger'
46
- onClick={() => {
47
- removeFilterCompletely(filterIndex)
48
- overlay?.actions.toggleOverlay()
49
- }}
50
- >
51
- Delete{filterUsedByMany ? ' Completely' : ''}
52
- </button>
53
- </Modal.Content>
54
- </Modal>
55
- )
56
- }
57
-
58
- export default DeleteFilterModal
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
@@ -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.title || vizKey
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
- <button
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
- </button>
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 _ from 'lodash'
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[] = _.uniq(
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: _.cloneDeep(filter.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 = _.cloneDeep(filter.orderedValues || filter.values)
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: _.cloneDeep(subGroup.values), order })
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 = _.cloneDeep(currentOrderedValues)
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 = _.cloneDeep(subGrouping)
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 Sub Group */}
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={'Sub Group Default Value'}
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 Layout from '@cdc/core/components/Layout'
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
- <Layout.VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
305
+ <VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
306
306
  {isEditor && (
307
- <Layout.Sidebar
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
- </Layout.Sidebar>
314
+ </Sidebar>
315
315
  )}
316
316
 
317
317
  {!displayNone && (
318
- <Layout.Responsive isEditor={isEditor}>
318
+ <Responsive isEditor={isEditor}>
319
319
  <div
320
320
  className={`${
321
321
  isEditor ? ' is-editor' : ''
322
- } cove-component__content col-12 cove-dashboard-filters-container`}
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
- </Layout.Responsive>
340
+ </Responsive>
341
341
  )}
342
- </Layout.VisualizationWrapper>
342
+ </VisualizationWrapper>
343
343
  )
344
344
  }
345
345