@cdc/dashboard 4.24.10 → 4.24.12-2
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/dist/cdcdashboard.js +51165 -49100
- package/examples/ed-visits-county-file.json +141 -357
- package/examples/private/DEV-10120.json +1294 -0
- package/examples/private/DEV-9199.json +606 -0
- package/examples/private/DEV-9644.json +20092 -0
- package/examples/private/DEV-9684.json +2135 -0
- package/examples/private/DEV-9989.json +229 -0
- package/examples/private/art-dashboard.json +18174 -0
- package/examples/private/art-scratch.json +2406 -0
- package/examples/private/dashboard-config-ehdi.json +29915 -0
- package/examples/private/dashboard-margins.js +15 -0
- package/examples/private/dataset.json +1452 -0
- package/examples/private/ehdi-data.json +29502 -0
- package/examples/private/gaza-issue.json +1214 -0
- package/examples/private/workforce.json +2041 -0
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +43 -29
- package/src/CdcDashboardComponent.tsx +91 -52
- package/src/DashboardContext.tsx +2 -0
- package/src/_stories/Dashboard.stories.tsx +8 -0
- package/src/_stories/_mock/api-filter-error.json +55 -0
- package/src/_stories/_mock/group-pivot-filter.json +10 -5
- package/src/components/CollapsibleVisualizationRow.tsx +8 -2
- package/src/components/DashboardFilters/DashboardFilters.tsx +121 -58
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +3 -1
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +54 -50
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +13 -7
- package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +21 -0
- package/src/components/DashboardFilters/dashboardfilter.styles.css +27 -0
- package/src/components/Grid.tsx +1 -1
- package/src/components/Header/Header.tsx +71 -10
- package/src/components/Header/index.scss +0 -5
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +28 -6
- package/src/components/MultiConfigTabs/MultiTabs.tsx +2 -0
- package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +4 -11
- package/src/components/Row.tsx +59 -13
- package/src/components/VisualizationRow.tsx +30 -22
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +0 -1
- package/src/components/Widget.tsx +23 -1
- package/src/data/initial-state.js +2 -1
- package/src/helpers/addValuesToDashboardFilters.ts +4 -2
- package/src/helpers/apiFilterHelpers.ts +55 -20
- package/src/helpers/changeFilterActive.ts +3 -0
- package/src/helpers/filterData.ts +1 -1
- package/src/helpers/getVizRowColumnLocator.ts +1 -0
- package/src/helpers/loadAPIFilters.ts +32 -10
- package/src/helpers/reloadURLHelpers.ts +9 -2
- package/src/helpers/shouldLoadAllFilters.ts +30 -0
- package/src/helpers/tests/apiFilterHelpers.test.ts +85 -4
- package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +10 -4
- package/src/helpers/tests/reloadURLHelpers.test.ts +11 -5
- package/src/helpers/tests/shouldLoadAllFilters.test.ts +117 -0
- package/src/scss/editor-panel.scss +0 -3
- package/src/scss/grid.scss +22 -23
- package/src/scss/main.scss +0 -27
- package/src/store/dashboard.reducer.ts +9 -2
- package/src/store/errorMessage/errorMessage.actions.ts +7 -0
- package/src/store/errorMessage/errorMessage.reducer.ts +24 -0
|
@@ -66,8 +66,16 @@ const Tab = ({ name, handleClick, tabs, index, active }) => {
|
|
|
66
66
|
|
|
67
67
|
return (
|
|
68
68
|
<li className='nav-item d-flex mt-0'>
|
|
69
|
-
{canMoveLeft && editing &&
|
|
70
|
-
|
|
69
|
+
{canMoveLeft && editing && (
|
|
70
|
+
<button className='border-0' onClick={() => handleReorder(index, -1)}>
|
|
71
|
+
{'<'}
|
|
72
|
+
</button>
|
|
73
|
+
)}
|
|
74
|
+
<div
|
|
75
|
+
className={`edit nav-link${active ? ' active' : ''}`}
|
|
76
|
+
aria-current={active ? 'page' : null}
|
|
77
|
+
onClick={onClick}
|
|
78
|
+
>
|
|
71
79
|
{editing ? (
|
|
72
80
|
<div className='d-flex'>
|
|
73
81
|
<input type='text' defaultValue={name} onBlur={saveName} ref={inputRef} />
|
|
@@ -78,13 +86,17 @@ const Tab = ({ name, handleClick, tabs, index, active }) => {
|
|
|
78
86
|
) : (
|
|
79
87
|
<>
|
|
80
88
|
{name}
|
|
81
|
-
<button className='
|
|
89
|
+
<button className='btn btn-danger border-0 ml-1' onClick={handleRemove}>
|
|
82
90
|
X
|
|
83
91
|
</button>
|
|
84
92
|
</>
|
|
85
93
|
)}
|
|
86
94
|
</div>
|
|
87
|
-
{canMoveRight && editing &&
|
|
95
|
+
{canMoveRight && editing && (
|
|
96
|
+
<button className='border-0' onClick={() => handleReorder(index, 1)}>
|
|
97
|
+
{'>'}
|
|
98
|
+
</button>
|
|
99
|
+
)}
|
|
88
100
|
</li>
|
|
89
101
|
)
|
|
90
102
|
}
|
|
@@ -92,7 +104,10 @@ const Tab = ({ name, handleClick, tabs, index, active }) => {
|
|
|
92
104
|
const MultiConfigTabs = () => {
|
|
93
105
|
const { config } = useContext(DashboardContext)
|
|
94
106
|
const dispatch = useContext(DashboardDispatchContext)
|
|
95
|
-
const tabs = useMemo<string[]>(
|
|
107
|
+
const tabs = useMemo<string[]>(
|
|
108
|
+
() => (config.multiDashboards || []).map(({ label }) => label),
|
|
109
|
+
[config.multiDashboards]
|
|
110
|
+
)
|
|
96
111
|
const activeTab = useMemo<number>(() => config.activeDashboard, [config.activeDashboard])
|
|
97
112
|
|
|
98
113
|
const saveAndLoad = (indexToSwitchTo: number) => {
|
|
@@ -104,7 +119,14 @@ const MultiConfigTabs = () => {
|
|
|
104
119
|
return (
|
|
105
120
|
<ul className='nav nav-tabs multi-config-tabs'>
|
|
106
121
|
{tabs.map((tab, index) => (
|
|
107
|
-
<Tab
|
|
122
|
+
<Tab
|
|
123
|
+
key={tab + index}
|
|
124
|
+
name={tab}
|
|
125
|
+
tabs={tabs}
|
|
126
|
+
index={index}
|
|
127
|
+
handleClick={() => saveAndLoad(index)}
|
|
128
|
+
active={index === activeTab}
|
|
129
|
+
/>
|
|
108
130
|
))}
|
|
109
131
|
<li className='nav-item'>
|
|
110
132
|
<button className='nav-link add' onClick={() => dispatch({ type: 'ADD_NEW_DASHBOARD' })}>
|
|
@@ -2,6 +2,7 @@ import { useContext, useMemo } from 'react'
|
|
|
2
2
|
import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
|
|
3
3
|
|
|
4
4
|
import './multiconfigtabs.styles.css'
|
|
5
|
+
import { updateQueryParam } from '@cdc/core/helpers/queryStringUtils'
|
|
5
6
|
|
|
6
7
|
const MultiTabs = () => {
|
|
7
8
|
const { config } = useContext(DashboardContext)
|
|
@@ -15,6 +16,7 @@ const MultiTabs = () => {
|
|
|
15
16
|
const load = (indexToSwitchTo: number, e) => {
|
|
16
17
|
e.preventDefault() // some form wrapper is causing this to act as a submit button
|
|
17
18
|
dispatch({ type: 'SWITCH_CONFIG', payload: indexToSwitchTo })
|
|
19
|
+
updateQueryParam('cove-tab', indexToSwitchTo)
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
if (!config.multiDashboards) return null
|
|
@@ -13,11 +13,6 @@
|
|
|
13
13
|
&:hover {
|
|
14
14
|
:is(button) {
|
|
15
15
|
display: inline-block;
|
|
16
|
-
color: var(--gray);
|
|
17
|
-
&:hover {
|
|
18
|
-
color: var(--black);
|
|
19
|
-
font-weight: bold;
|
|
20
|
-
}
|
|
21
16
|
}
|
|
22
17
|
|
|
23
18
|
background: var(--quaternary);
|
|
@@ -32,12 +27,10 @@
|
|
|
32
27
|
color: white;
|
|
33
28
|
font-weight: bold;
|
|
34
29
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
color: var(--black);
|
|
40
|
-
font-weight: bold;
|
|
30
|
+
.btn-danger {
|
|
31
|
+
text-decoration: none;
|
|
32
|
+
padding: 0px 5px;
|
|
33
|
+
font-size: inherit;
|
|
41
34
|
}
|
|
42
35
|
}
|
|
43
36
|
.add {
|
package/src/components/Row.tsx
CHANGED
|
@@ -122,39 +122,82 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
const layoutList = [
|
|
125
|
-
<li
|
|
125
|
+
<li
|
|
126
|
+
className={curr === '12' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
127
|
+
onClick={() => setRowLayout([12])}
|
|
128
|
+
key='12'
|
|
129
|
+
title='1 Column'
|
|
130
|
+
>
|
|
126
131
|
<OneColIcon />
|
|
127
132
|
</li>,
|
|
128
|
-
<li
|
|
133
|
+
<li
|
|
134
|
+
className={curr === '66' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
135
|
+
onClick={() => setRowLayout([6, 6])}
|
|
136
|
+
key='66'
|
|
137
|
+
title='2 Columns'
|
|
138
|
+
>
|
|
129
139
|
<TwoColIcon />
|
|
130
140
|
</li>,
|
|
131
|
-
<li
|
|
141
|
+
<li
|
|
142
|
+
className={curr === '444' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
143
|
+
onClick={() => setRowLayout([4, 4, 4])}
|
|
144
|
+
key='444'
|
|
145
|
+
title='3 Columns'
|
|
146
|
+
>
|
|
132
147
|
<ThreeColIcon />
|
|
133
148
|
</li>,
|
|
134
|
-
<li
|
|
149
|
+
<li
|
|
150
|
+
className={curr === '48' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
151
|
+
onClick={() => setRowLayout([4, 8])}
|
|
152
|
+
key='48'
|
|
153
|
+
title='2 Columns'
|
|
154
|
+
>
|
|
135
155
|
<FourEightColIcon />
|
|
136
156
|
</li>,
|
|
137
|
-
<li
|
|
157
|
+
<li
|
|
158
|
+
className={curr === '84' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
159
|
+
onClick={() => setRowLayout([8, 4])}
|
|
160
|
+
key='84'
|
|
161
|
+
title='2 Columns'
|
|
162
|
+
>
|
|
138
163
|
<EightFourColIcon />
|
|
139
164
|
</li>,
|
|
140
|
-
<li
|
|
165
|
+
<li
|
|
166
|
+
className={curr === 'toggle' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
167
|
+
onClick={() => setRowLayout([12, 12, 12], true)}
|
|
168
|
+
key='toggle'
|
|
169
|
+
title='Toggle between up to three visualizations'
|
|
170
|
+
>
|
|
141
171
|
<ToggleIcon />
|
|
142
172
|
</li>
|
|
143
173
|
]
|
|
144
174
|
|
|
145
175
|
return (
|
|
146
176
|
<nav className='row-menu'>
|
|
147
|
-
<
|
|
148
|
-
<ul className='row-menu__flyout'>{layoutList}</ul>
|
|
149
|
-
</div>
|
|
177
|
+
<ul className='row-menu__flyout'>{layoutList}</ul>
|
|
150
178
|
<div className='spacer'></div>
|
|
151
|
-
<button
|
|
179
|
+
<button
|
|
180
|
+
className={`btn btn-primary row-menu__btn border-0`}
|
|
181
|
+
title='Move Row Up'
|
|
182
|
+
onClick={() => moveRow('up')}
|
|
183
|
+
disabled={rowIdx === 0}
|
|
184
|
+
>
|
|
152
185
|
<Icon display='caretUp' color='#fff' size={25} />
|
|
153
186
|
</button>
|
|
154
|
-
<button
|
|
187
|
+
<button
|
|
188
|
+
className={'btn btn-primary row-menu__btn border-0'}
|
|
189
|
+
title='Move Row Down'
|
|
190
|
+
onClick={() => moveRow('down')}
|
|
191
|
+
disabled={rowIdx + 1 === rows.length}
|
|
192
|
+
>
|
|
155
193
|
<Icon display='caretDown' color='#fff' size={25} />
|
|
156
194
|
</button>
|
|
157
|
-
<button
|
|
195
|
+
<button
|
|
196
|
+
className={'btn btn-danger row-menu__btn row-menu__btn--remove border-0'}
|
|
197
|
+
title='Delete Row'
|
|
198
|
+
onClick={deleteRow}
|
|
199
|
+
disabled={rowIdx === 0 && rows.length === 1}
|
|
200
|
+
>
|
|
158
201
|
<Icon display='close' color='#fff' size={25} />
|
|
159
202
|
</button>
|
|
160
203
|
</nav>
|
|
@@ -177,7 +220,10 @@ const Row: React.FC<RowProps> = ({ row, idx: rowIdx, uuid }) => {
|
|
|
177
220
|
visualizationType: type,
|
|
178
221
|
editing: true
|
|
179
222
|
}
|
|
180
|
-
dispatch({
|
|
223
|
+
dispatch({
|
|
224
|
+
type: 'ADD_FOOTNOTE',
|
|
225
|
+
payload: { id: uid, rowIndex: rowIdx, config: newVisualizationConfig as Visualization }
|
|
226
|
+
})
|
|
181
227
|
} else {
|
|
182
228
|
dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey: row.footnotesId, configureData: { editing: true } } })
|
|
183
229
|
}
|
|
@@ -24,6 +24,7 @@ type VisualizationWrapperProps = {
|
|
|
24
24
|
children: React.ReactNode
|
|
25
25
|
currentViewport: ViewPort
|
|
26
26
|
groupName: string
|
|
27
|
+
hideVisualization: boolean
|
|
27
28
|
row: ConfigRow
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -31,10 +32,13 @@ const VisualizationWrapper: React.FC<VisualizationWrapperProps> = ({
|
|
|
31
32
|
allExpanded,
|
|
32
33
|
currentViewport,
|
|
33
34
|
groupName,
|
|
35
|
+
hideVisualization,
|
|
34
36
|
row,
|
|
35
37
|
children
|
|
36
38
|
}) => {
|
|
37
|
-
return
|
|
39
|
+
return hideVisualization ? (
|
|
40
|
+
<></>
|
|
41
|
+
) : row.expandCollapseAllButtons ? (
|
|
38
42
|
<div className='collapsable-multiviz-container'>
|
|
39
43
|
<CollapsibleVisualizationRow
|
|
40
44
|
allExpanded={allExpanded}
|
|
@@ -47,7 +51,7 @@ const VisualizationWrapper: React.FC<VisualizationWrapperProps> = ({
|
|
|
47
51
|
</div>
|
|
48
52
|
) : (
|
|
49
53
|
<>
|
|
50
|
-
<h3>{groupName}</h3>
|
|
54
|
+
{groupName !== '' ? <h3>{groupName}</h3> : <></>}
|
|
51
55
|
{children}
|
|
52
56
|
</>
|
|
53
57
|
)
|
|
@@ -59,6 +63,7 @@ type VizRowProps = {
|
|
|
59
63
|
groupName: string
|
|
60
64
|
row: ConfigRow
|
|
61
65
|
rowIndex: number
|
|
66
|
+
inNoDataState: boolean
|
|
62
67
|
setSharedFilter: Function
|
|
63
68
|
updateChildConfig: Function
|
|
64
69
|
apiFilterDropdowns: APIFilterDropdowns
|
|
@@ -71,6 +76,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
71
76
|
groupName,
|
|
72
77
|
row,
|
|
73
78
|
rowIndex: index,
|
|
79
|
+
inNoDataState,
|
|
74
80
|
setSharedFilter,
|
|
75
81
|
updateChildConfig,
|
|
76
82
|
apiFilterDropdowns,
|
|
@@ -81,11 +87,6 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
81
87
|
const setToggled = (colIndex: number) => {
|
|
82
88
|
setShow(show.map((_, i) => i === colIndex))
|
|
83
89
|
}
|
|
84
|
-
const inNoDataState = useMemo(() => {
|
|
85
|
-
const vals = Object.values(rawData).flatMap(val => val)
|
|
86
|
-
if (!vals.length) return true
|
|
87
|
-
return vals.some(val => val === undefined)
|
|
88
|
-
}, [rawData])
|
|
89
90
|
|
|
90
91
|
const footnotesConfig = useMemo(() => {
|
|
91
92
|
if (row.footnotesId) {
|
|
@@ -95,7 +96,10 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
95
96
|
// the multiViz filtering filtering is applied after the dashboard filters
|
|
96
97
|
const categoryFootnote = footnoteConfig.formattedData.filter(d => d[row.multiVizColumn] === vizCategory)
|
|
97
98
|
footnoteConfig.formattedData = categoryFootnote
|
|
99
|
+
} else {
|
|
100
|
+
footnoteConfig.formattedData = dashboardFilteredData[row.footnotesId]
|
|
98
101
|
}
|
|
102
|
+
|
|
99
103
|
return footnoteConfig
|
|
100
104
|
}
|
|
101
105
|
return null
|
|
@@ -120,13 +124,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
120
124
|
return false
|
|
121
125
|
}
|
|
122
126
|
return (
|
|
123
|
-
<div
|
|
124
|
-
className={`row mb-5 ${row.equalHeight ? 'equal-height' : ''} ${row.toggle ? 'toggle' : ''}`}
|
|
125
|
-
key={`row__${index}`}
|
|
126
|
-
>
|
|
127
|
-
{row.toggle && (
|
|
128
|
-
<Toggle row={row} visualizations={config.visualizations} active={show.indexOf(true)} setToggled={setToggled} />
|
|
129
|
-
)}
|
|
127
|
+
<div className={`row${row.equalHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`} key={`row__${index}`}>
|
|
130
128
|
{row.columns.map((col, colIndex) => {
|
|
131
129
|
if (col.width) {
|
|
132
130
|
if (!col.widget) return <div key={`row__${index}__col__${colIndex}`} className={`col col-${col.width}`}></div>
|
|
@@ -150,24 +148,34 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
150
148
|
{visualizationConfig.dataKey} (Go to Table)
|
|
151
149
|
</a>
|
|
152
150
|
)
|
|
153
|
-
|
|
151
|
+
|
|
152
|
+
const hideVisualization =
|
|
154
153
|
inNoDataState &&
|
|
155
|
-
visualizationConfig.
|
|
156
|
-
applyButtonNotClicked(visualizationConfig)
|
|
154
|
+
visualizationConfig.filterBehavior !== 'Apply Button' &&
|
|
155
|
+
(visualizationConfig.type !== 'dashboardFilters' || applyButtonNotClicked(visualizationConfig))
|
|
157
156
|
|
|
158
157
|
const shouldShow = row.toggle === undefined || (row.toggle && show[colIndex])
|
|
159
158
|
|
|
160
|
-
const body = <></>
|
|
161
|
-
|
|
162
159
|
return (
|
|
163
160
|
<div
|
|
164
161
|
key={`vis__${index}__${colIndex}`}
|
|
165
|
-
className={`
|
|
162
|
+
className={`col-12 col-md-${col.width}${!shouldShow ? ' d-none' : ''}${
|
|
163
|
+
hideVisualization ? ' hide-parent-visualization' : ' mt-5 p-1'
|
|
164
|
+
}`}
|
|
166
165
|
>
|
|
166
|
+
{row.toggle && !hideVisualization && (
|
|
167
|
+
<Toggle
|
|
168
|
+
row={row}
|
|
169
|
+
visualizations={config.visualizations}
|
|
170
|
+
active={show.indexOf(true)}
|
|
171
|
+
setToggled={setToggled}
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
167
174
|
<VisualizationWrapper
|
|
168
175
|
allExpanded={allExpanded}
|
|
169
176
|
currentViewport={currentViewport}
|
|
170
177
|
groupName={groupName}
|
|
178
|
+
hideVisualization={hideVisualization}
|
|
171
179
|
row={row}
|
|
172
180
|
>
|
|
173
181
|
{visualizationConfig.type === 'chart' && (
|
|
@@ -274,7 +282,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
274
282
|
configUrl={undefined}
|
|
275
283
|
/>
|
|
276
284
|
)}
|
|
277
|
-
{visualizationConfig.type === 'dashboardFilters' &&
|
|
285
|
+
{visualizationConfig.type === 'dashboardFilters' && (
|
|
278
286
|
<DashboardSharedFilters
|
|
279
287
|
setConfig={newConfig => {
|
|
280
288
|
updateChildConfig(col.widget, newConfig)
|
|
@@ -310,7 +318,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
|
|
|
310
318
|
}
|
|
311
319
|
return <React.Fragment key={`vis__${index}__${colIndex}`}></React.Fragment>
|
|
312
320
|
})}
|
|
313
|
-
{row.footnotesId ? (
|
|
321
|
+
{row.footnotesId && !inNoDataState ? (
|
|
314
322
|
<FootnotesStandAlone
|
|
315
323
|
isEditor={false}
|
|
316
324
|
visualizationKey={row.footnotesId}
|
|
@@ -119,7 +119,6 @@ const VisualizationsPanel = () => {
|
|
|
119
119
|
<Widget addVisualization={() => addVisualization('dashboardFilters', '')} type='dashboardFilters' />
|
|
120
120
|
<Widget addVisualization={() => addVisualization('table', '')} type='table' />
|
|
121
121
|
</div>
|
|
122
|
-
<span className='subheading-3'>Advanced</span>
|
|
123
122
|
<AdvancedEditor
|
|
124
123
|
loadConfig={loadConfig}
|
|
125
124
|
config={config}
|
|
@@ -5,6 +5,7 @@ import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
|
5
5
|
import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
|
|
6
6
|
|
|
7
7
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
8
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
8
9
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
9
10
|
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
10
11
|
import { iconHash } from '../helpers/iconHash'
|
|
@@ -39,7 +40,7 @@ type WidgetProps = {
|
|
|
39
40
|
|
|
40
41
|
const Widget = ({ widgetConfig, addVisualization, type }: WidgetProps) => {
|
|
41
42
|
const { overlay } = useGlobalContext()
|
|
42
|
-
const { config } = useContext(DashboardContext)
|
|
43
|
+
const { config, data } = useContext(DashboardContext)
|
|
43
44
|
const dispatch = useContext(DashboardDispatchContext)
|
|
44
45
|
|
|
45
46
|
const transform = new DataTransform()
|
|
@@ -79,9 +80,30 @@ const Widget = ({ widgetConfig, addVisualization, type }: WidgetProps) => {
|
|
|
79
80
|
})
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
const changeDataLimit = (dataUrl, limit) => {
|
|
84
|
+
const url = new URL(dataUrl)
|
|
85
|
+
url.searchParams.set('$limit', limit)
|
|
86
|
+
// Replace encoded $ with actual $ for the URL
|
|
87
|
+
return url.href.replace(/%24/g, '$')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const loadSampleData = () => {
|
|
91
|
+
const dataKey = config.rows[widgetConfig.rowIdx]?.dataKey || widgetConfig?.dataKey
|
|
92
|
+
const dataset = config.datasets[dataKey]
|
|
93
|
+
const _data = data[dataset?.dataUrl]
|
|
94
|
+
if (_data && !_data.length) {
|
|
95
|
+
const url = changeDataLimit(dataset.dataUrl, 100)
|
|
96
|
+
fetchRemoteData(url).then(responseData => {
|
|
97
|
+
responseData.sample = true
|
|
98
|
+
dispatch({ type: 'SET_DATA', payload: { ...data, [dataKey]: responseData } })
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
82
103
|
const editWidget = () => {
|
|
83
104
|
if (!widgetConfig) return
|
|
84
105
|
dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey: widgetConfig.uid, configureData: { editing: true } } })
|
|
106
|
+
loadSampleData()
|
|
85
107
|
}
|
|
86
108
|
|
|
87
109
|
let isConfigurationReady = false
|
|
@@ -30,9 +30,11 @@ const getSelector = (filter: SharedFilter) => {
|
|
|
30
30
|
|
|
31
31
|
export const addValuesToDashboardFilters = (
|
|
32
32
|
filters: SharedFilter[],
|
|
33
|
-
data: Record<string, any[]
|
|
33
|
+
data: Record<string, any[]>,
|
|
34
|
+
filtersToSkip: number[] = []
|
|
34
35
|
): Array<SharedFilter> => {
|
|
35
|
-
return filters?.map(filter => {
|
|
36
|
+
return filters?.map((filter, index) => {
|
|
37
|
+
if (filtersToSkip.includes(index)) return filter
|
|
36
38
|
if (filter.type === 'urlfilter') return filter
|
|
37
39
|
const filterCopy = _.cloneDeep(filter)
|
|
38
40
|
const filterValues = generateValuesForFilter(getSelector(filter), data)
|
|
@@ -3,7 +3,7 @@ import { APIFilterDropdowns, DropdownOptions } from '../components/DashboardFilt
|
|
|
3
3
|
import { APIFilter } from '../types/APIFilter'
|
|
4
4
|
import { SharedFilter } from '../types/SharedFilter'
|
|
5
5
|
import _ from 'lodash'
|
|
6
|
-
import {
|
|
6
|
+
import { getQueryParam } from '@cdc/core/helpers/queryStringUtils'
|
|
7
7
|
import { FILTER_STYLE } from '../types/FilterStyles'
|
|
8
8
|
|
|
9
9
|
/** key for the dropdowns object */
|
|
@@ -17,10 +17,10 @@ export const getLoadingFilterMemo = (
|
|
|
17
17
|
apiFiltersEndpoints.reduce((acc, endpoint, currIndex) => {
|
|
18
18
|
const _key: DropdownsKey = endpoint
|
|
19
19
|
const hasChanged = changedChildFilterIndexes.includes(currIndex)
|
|
20
|
-
if (apiFilterDropdowns[_key]
|
|
20
|
+
if (apiFilterDropdowns[_key] && !hasChanged) {
|
|
21
21
|
acc[_key] = apiFilterDropdowns[_key]
|
|
22
22
|
} else {
|
|
23
|
-
acc[_key] =
|
|
23
|
+
acc[_key] = undefined
|
|
24
24
|
}
|
|
25
25
|
return acc
|
|
26
26
|
}, {})
|
|
@@ -37,7 +37,7 @@ export const getParentParams = (
|
|
|
37
37
|
const key = filter.apiFilter.valueSelector || ''
|
|
38
38
|
const subKey = filter.apiFilter.subgroupValueSelector || ''
|
|
39
39
|
const val = filter.queuedActive ? filter.queuedActive[0] : (filter.active as string) || ''
|
|
40
|
-
const subVal = filter.queuedActive ? filter.queuedActive[1] : filter.subGrouping
|
|
40
|
+
const subVal = filter.queuedActive ? filter.queuedActive[1] : filter.subGrouping?.active || ''
|
|
41
41
|
return [
|
|
42
42
|
{ key, value: val },
|
|
43
43
|
{ key: subKey, value: subVal }
|
|
@@ -53,6 +53,8 @@ export const getParentParams = (
|
|
|
53
53
|
})
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
export const notAllParentsSelected = parentParams => parentParams?.some(({ value }) => value === '')
|
|
57
|
+
|
|
56
58
|
export const getFilterValues = (data: Array<Object>, apiFilter: APIFilter): DropdownOptions => {
|
|
57
59
|
const { textSelector, valueSelector, subgroupTextSelector, subgroupValueSelector } = apiFilter
|
|
58
60
|
if (subgroupValueSelector) {
|
|
@@ -83,9 +85,8 @@ export const getToFetch = (
|
|
|
83
85
|
const _key = baseEndpoint
|
|
84
86
|
if (apiFilterDropdowns[_key]) return // don't reload cached filter
|
|
85
87
|
const parentParams = getParentParams(filter, sharedFilters)
|
|
86
|
-
const notAllParentsSelected = parentParams?.some(({ value }) => value === '')
|
|
87
88
|
|
|
88
|
-
if (notAllParentsSelected) return // don't send request for dependent children filter options
|
|
89
|
+
if (notAllParentsSelected(parentParams)) return // don't send request for dependent children filter options
|
|
89
90
|
|
|
90
91
|
const endpoint = baseEndpoint + (parentParams ? gatherQueryParams(baseEndpoint, parentParams) : '')
|
|
91
92
|
toFetch[endpoint] = [_key, index]
|
|
@@ -93,6 +94,38 @@ export const getToFetch = (
|
|
|
93
94
|
return toFetch
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
export const setActiveNestedDropdown = (dropdownOptions, sharedFilter) => {
|
|
98
|
+
const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
|
|
99
|
+
const defaultValue = dropdownOptions[0]?.value
|
|
100
|
+
const subDefaultValue = dropdownOptions[0]?.subOptions[0].value
|
|
101
|
+
const subDefaultQueryParamValue = getQueryParam(sharedFilter?.subGrouping.setByQueryParameter)
|
|
102
|
+
if (!sharedFilter.active) {
|
|
103
|
+
sharedFilter.active = defaultQueryParamValue || defaultValue
|
|
104
|
+
sharedFilter.subGrouping.active = subDefaultQueryParamValue || subDefaultValue
|
|
105
|
+
} else {
|
|
106
|
+
const currentOption = dropdownOptions.find(option => option.value === sharedFilter.active)
|
|
107
|
+
sharedFilter.active = currentOption ? currentOption.value : defaultValue
|
|
108
|
+
if (currentOption) {
|
|
109
|
+
const currentSubOption = currentOption.subOptions.find(option => option.value === sharedFilter.subGrouping.active)
|
|
110
|
+
sharedFilter.subGrouping.active = currentSubOption?.value || subDefaultValue
|
|
111
|
+
} else {
|
|
112
|
+
sharedFilter.subGrouping.active = subDefaultValue
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const setActiveMultiDropdown = (dropdownOptions, sharedFilter) => {
|
|
118
|
+
const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
|
|
119
|
+
const multiDefaultQueryParamValue = Array.isArray(defaultQueryParamValue)
|
|
120
|
+
? defaultQueryParamValue
|
|
121
|
+
: defaultQueryParamValue?.split(',')
|
|
122
|
+
const multiDefaultValue = defaultQueryParamValue ? multiDefaultQueryParamValue : [dropdownOptions[0]?.value]
|
|
123
|
+
const currentOption = ((sharedFilter.active as string[]) || []).filter(activeVal =>
|
|
124
|
+
dropdownOptions.find(option => option.value === activeVal)
|
|
125
|
+
)
|
|
126
|
+
sharedFilter.active = currentOption.length ? currentOption : multiDefaultValue
|
|
127
|
+
}
|
|
128
|
+
|
|
96
129
|
export const setAutoLoadDefaultValue = (
|
|
97
130
|
sharedFilterIndex: number,
|
|
98
131
|
dropdownOptions: DropdownOptions,
|
|
@@ -102,24 +135,26 @@ export const setAutoLoadDefaultValue = (
|
|
|
102
135
|
const sharedFiltersCopy = _.cloneDeep(sharedFilters)
|
|
103
136
|
const sharedFilter = _.cloneDeep(sharedFiltersCopy[sharedFilterIndex])
|
|
104
137
|
if (!autoLoadFilterIndexes.length || !dropdownOptions?.length) return sharedFilter // no autoLoading happening
|
|
105
|
-
|
|
138
|
+
const hasQueryParameter = sharedFilter.setByQueryParameter
|
|
139
|
+
? Boolean(getQueryParam(sharedFilter.setByQueryParameter))
|
|
140
|
+
: false
|
|
141
|
+
if (autoLoadFilterIndexes.includes(sharedFilterIndex) || hasQueryParameter) {
|
|
106
142
|
const filterParents = sharedFiltersCopy.filter(f => sharedFilter.parents?.includes(f.key))
|
|
107
143
|
const notAllParentFiltersSelected = filterParents.some(p => !(p.active || p.queuedActive))
|
|
108
144
|
if (filterParents && notAllParentFiltersSelected) return sharedFilter
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
const defaultQueryParamValue = queryParams[sharedFilter?.setByQueryParameter]
|
|
114
|
-
sharedFilter.active = defaultQueryParamValue || defaultValue
|
|
115
|
-
} else if (sharedFilter.filterStyle === FILTER_STYLE.multiSelect) {
|
|
116
|
-
const currentOption = (sharedFilter.active as string[]).filter(activeVal =>
|
|
117
|
-
dropdownOptions.find(option => option.value === activeVal)
|
|
118
|
-
)
|
|
119
|
-
sharedFilter.active = currentOption.length ? currentOption : defaultValue
|
|
145
|
+
if (sharedFilter.filterStyle === FILTER_STYLE.multiSelect) {
|
|
146
|
+
setActiveMultiDropdown(dropdownOptions, sharedFilter)
|
|
147
|
+
} else if (sharedFilter.filterStyle === FILTER_STYLE.nestedDropdown) {
|
|
148
|
+
setActiveNestedDropdown(dropdownOptions, sharedFilter)
|
|
120
149
|
} else {
|
|
121
|
-
const
|
|
122
|
-
|
|
150
|
+
const defaultValue = dropdownOptions[0]?.value
|
|
151
|
+
const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
|
|
152
|
+
if (!sharedFilter.active) {
|
|
153
|
+
sharedFilter.active = defaultQueryParamValue || defaultValue
|
|
154
|
+
} else {
|
|
155
|
+
const currentOption = dropdownOptions.find(option => option.value === sharedFilter.active)
|
|
156
|
+
sharedFilter.active = currentOption ? currentOption.value : defaultValue
|
|
157
|
+
}
|
|
123
158
|
}
|
|
124
159
|
}
|
|
125
160
|
return sharedFilter
|
|
@@ -13,6 +13,9 @@ const handleChildren = (sharedFilters: SharedFilter[], parentIndex: number) => {
|
|
|
13
13
|
if (childFilterIndexes.length) {
|
|
14
14
|
childFilterIndexes.forEach(filterIndex => {
|
|
15
15
|
sharedFilters[filterIndex].active = ''
|
|
16
|
+
if (sharedFilters[filterIndex].subGrouping) {
|
|
17
|
+
sharedFilters[filterIndex].subGrouping.active = ''
|
|
18
|
+
}
|
|
16
19
|
})
|
|
17
20
|
}
|
|
18
21
|
return childFilterIndexes
|
|
@@ -24,7 +24,7 @@ function getMaxTierAndSetFilterTiers(filters: SharedFilter[]): number {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function filter(data = [], filters: SharedFilter[], condition) {
|
|
27
|
-
const activeFilters =
|
|
27
|
+
const activeFilters = _.filter(filters, f => (f.resetLabel === f.active ? f.values?.includes(f.resetLabel) : true))
|
|
28
28
|
return data.filter(row => {
|
|
29
29
|
const foundMatchingFilter = activeFilters.find(filter => {
|
|
30
30
|
const currentValue = row[filter.columnName]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ConfigRow } from '../types/ConfigRow'
|
|
2
2
|
|
|
3
|
+
// returns a dictionary of widget names and their corresponding row and column index
|
|
3
4
|
export const getVizRowColumnLocator = (rows: ConfigRow[]): Record<string, { row: number; column: number }> =>
|
|
4
5
|
rows.reduce((acc, curr, index) => {
|
|
5
6
|
curr.columns?.forEach((column, columnIndex) => {
|