@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.
Files changed (85) 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 +51744 -49003
  6. package/examples/__data__/data-2.json +6 -0
  7. package/examples/__data__/data.json +6 -0
  8. package/examples/legend-issue.json +1 -1
  9. package/examples/minimal-example.json +34 -0
  10. package/examples/private/dengue.json +4640 -0
  11. package/examples/private/link_to_file.json +16662 -0
  12. package/examples/sankey.json +3 -3
  13. package/examples/test-api-filter-reset.json +4 -4
  14. package/examples/tp5-test.json +86 -4
  15. package/package.json +9 -9
  16. package/src/CdcDashboardComponent.tsx +1 -1
  17. package/src/_stories/Dashboard.smoke.stories.tsx +33 -0
  18. package/src/_stories/Dashboard.stories.tsx +43 -2
  19. package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
  20. package/src/_stories/_mock/tp5-test.json +86 -5
  21. package/src/components/DashboardEditors.tsx +15 -0
  22. package/src/components/DashboardFilters/DashboardFilters.test.tsx +129 -0
  23. package/src/components/DashboardFilters/DashboardFilters.tsx +10 -7
  24. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +5 -4
  25. package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +5 -3
  26. package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +59 -58
  27. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +127 -0
  28. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +28 -4
  29. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +2 -2
  30. package/src/components/ExpandCollapseButtons.tsx +6 -4
  31. package/src/components/Grid.tsx +4 -3
  32. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
  33. package/src/components/Row.tsx +11 -10
  34. package/src/components/VisualizationRow.tsx +71 -20
  35. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +1 -1
  36. package/src/components/Widget/Widget.tsx +5 -4
  37. package/src/components/Widget/widget.styles.css +41 -10
  38. package/src/data/initial-state.js +1 -0
  39. package/src/helpers/addVisualization.ts +3 -1
  40. package/src/helpers/tests/addVisualization.test.ts +1 -1
  41. package/src/scss/grid.scss +38 -8
  42. package/src/scss/main.scss +110 -38
  43. package/src/store/dashboard.reducer.ts +1 -0
  44. package/src/test/CdcDashboard.test.jsx +24 -0
  45. package/src/types/SharedFilter.ts +1 -0
  46. package/tests/fixtures/dashboard-config-with-metadata.json +1 -1
  47. package/LICENSE +0 -201
  48. package/dist/cdcdashboard-vr9HZwRt.es.js +0 -6
  49. package/examples/DEV-6574.json +0 -2224
  50. package/examples/api-dashboard-data.json +0 -272
  51. package/examples/api-dashboard-years.json +0 -11
  52. package/examples/api-geographies-data.json +0 -11
  53. package/examples/chart-data.json +0 -5409
  54. package/examples/custom/css/respiratory.css +0 -236
  55. package/examples/custom/js/respiratory.js +0 -242
  56. package/examples/default-data.json +0 -368
  57. package/examples/default-filter-control.json +0 -209
  58. package/examples/default-multi-dataset-shared-filter.json +0 -1729
  59. package/examples/default-multi-dataset.json +0 -506
  60. package/examples/ed-visits-county-file.json +0 -402
  61. package/examples/filters/Alabama.json +0 -72
  62. package/examples/filters/Alaska.json +0 -1737
  63. package/examples/filters/Arkansas.json +0 -4713
  64. package/examples/filters/California.json +0 -212
  65. package/examples/filters/Colorado.json +0 -1500
  66. package/examples/filters/Connecticut.json +0 -559
  67. package/examples/filters/Delaware.json +0 -63
  68. package/examples/filters/DistrictofColumbia.json +0 -63
  69. package/examples/filters/Florida.json +0 -4217
  70. package/examples/filters/States.json +0 -146
  71. package/examples/state-level.json +0 -90136
  72. package/examples/state-points.json +0 -10474
  73. package/examples/temp-example-data.json +0 -130
  74. package/examples/test-dashboard-simple.json +0 -503
  75. package/examples/test-example.json +0 -752
  76. package/examples/test-file.json +0 -147
  77. package/examples/test.json +0 -752
  78. package/examples/testing.json +0 -94456
  79. /package/examples/{data → __data__}/data-with-metadata.json +0 -0
  80. /package/examples/{legend-issue-data.json → __data__/legend-issue-data.json} +0 -0
  81. /package/examples/api-test/{categories.json → __data__/categories.json} +0 -0
  82. /package/examples/api-test/{chart-data.json → __data__/chart-data.json} +0 -0
  83. /package/examples/api-test/{topics.json → __data__/topics.json} +0 -0
  84. /package/examples/api-test/{years.json → __data__/years.json} +0 -0
  85. /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
- <button
218
- 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'
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
- </button>
232
+ </Button>
230
233
  {handleReset && (
231
- <button className='btn btn-link mb-1' onClick={handleReset}>
234
+ <Button variant='link' className='mb-1' onClick={handleReset}>
232
235
  Clear Filters
233
- </button>
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
- <button onClick={addNewFilter} className='btn btn-primary full-width'>
318
+ <Button variant='primary' fullWidth onClick={addNewFilter}>
318
319
  Add Filter
319
- </button>
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
- <button onClick={() => setCanAddExisting(true)} className='btn btn-primary full-width mt-2'>
351
+ <Button variant='primary' fullWidth className='mt-2' onClick={() => setCanAddExisting(true)}>
351
352
  Add Existing Dashboard Filter
352
- </button>
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
- <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
+ })
@@ -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.title || vizKey
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
- <button
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
- </button>
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 Sub Group */}
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={'Sub Group Default Value'}
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
- <button className='btn expand-collapse-buttons' onClick={() => setAllExpanded(false)}>
11
+ <Button variant='light' onClick={() => setAllExpanded(false)}>
10
12
  - Collapse All
11
- </button>
12
- <button className='btn expand-collapse-buttons me-2' onClick={() => setAllExpanded(true)}>
13
+ </Button>
14
+ <Button variant='light' className='me-2' onClick={() => setAllExpanded(true)}>
13
15
  + Expand All
14
- </button>
16
+ </Button>
15
17
  </div>
16
18
  </div>
17
19
  )
@@ -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
- <button className='btn btn-primary col' onClick={addRow}>
28
+ <Button variant='primary' className='col' onClick={addRow}>
28
29
  Add Row
29
- </button>
30
+ </Button>
30
31
  </div>
31
32
  )
32
33
  }