@cdc/dashboard 4.25.3 → 4.25.6-1
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/Dynamic_Data.md +79 -0
- package/Override_Data.md +39 -0
- package/dist/cdcdashboard.js +76052 -77984
- package/examples/legend-issue-data.json +1874 -0
- package/examples/legend-issue.json +749 -0
- package/examples/map.json +628 -0
- package/examples/special-classes.json +54340 -0
- package/index.html +1 -26
- package/package.json +10 -15
- package/src/CdcDashboardComponent.tsx +65 -216
- package/src/_stories/Dashboard.stories.tsx +2 -0
- package/src/_stories/_mock/api-filter-map.json +43 -1
- package/src/components/CollapsibleVisualizationRow.tsx +4 -6
- package/src/components/DashboardEditors.tsx +143 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +205 -205
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +286 -287
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +129 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +680 -652
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +198 -198
- package/src/components/DataDesignerModal.tsx +33 -14
- package/src/components/Header/Header.tsx +7 -9
- package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +3 -0
- package/src/components/Row.tsx +2 -24
- package/src/components/VisualizationRow.tsx +191 -214
- package/src/helpers/getVizConfig.ts +93 -80
- package/src/helpers/getVizRowColumnLocator.ts +0 -1
- package/src/helpers/reloadURLHelpers.ts +11 -6
- package/src/helpers/shouldLoadAllFilters.ts +30 -30
- package/src/index.tsx +2 -1
- package/src/scss/main.scss +0 -5
- package/src/store/dashboard.actions.ts +61 -62
- package/src/store/dashboard.reducer.ts +15 -11
- package/src/types/ConfigRow.ts +0 -1
- package/src/types/Dashboard.ts +1 -1
- package/src/types/DashboardConfig.ts +1 -1
- package/src/types/SharedFilter.ts +2 -0
- package/examples/private/DEV-10120.json +0 -1294
- package/examples/private/DEV-10527.json +0 -845
- package/examples/private/DEV-10586.json +0 -54319
- package/examples/private/DEV-10856.json +0 -54319
- package/examples/private/DEV-9199.json +0 -606
- package/examples/private/DEV-9644.json +0 -20092
- package/examples/private/DEV-9684.json +0 -2135
- package/examples/private/DEV-9932.json +0 -95
- package/examples/private/DEV-9989.json +0 -229
- package/examples/private/art-dashboard.json +0 -18174
- package/examples/private/art-scratch.json +0 -2406
- package/examples/private/bird-flu-2.json +0 -440
- package/examples/private/bird-flu.json +0 -413
- package/examples/private/dashboard-config-ehdi.json +0 -29915
- package/examples/private/dashboard-map-filter.json +0 -815
- package/examples/private/dashboard-margins.js +0 -15
- package/examples/private/dataset.json +0 -1452
- package/examples/private/dev-10856-2.json +0 -1348
- package/examples/private/ehdi-data.json +0 -29502
- package/examples/private/exposure-source-h5-data.csv +0 -26
- package/examples/private/fatal-data.csv +0 -3159
- package/examples/private/feelings.json +0 -1
- package/examples/private/gaza-issue.json +0 -1214
- package/examples/private/markup.json +0 -115
- package/examples/private/nhis.json +0 -1792
- package/examples/private/workforce.json +0 -2041
- package/src/types/DataSet.ts +0 -11
|
@@ -1,198 +1,198 @@
|
|
|
1
|
-
import { useContext, useState } from 'react'
|
|
2
|
-
import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
|
|
3
|
-
import Filters from './DashboardFilters'
|
|
4
|
-
import { changeFilterActive } from '../../helpers/changeFilterActive'
|
|
5
|
-
import _ from 'lodash'
|
|
6
|
-
import { FilterBehavior } from '../../helpers/FilterBehavior'
|
|
7
|
-
import { getFilteredData } from '../../helpers/getFilteredData'
|
|
8
|
-
import { DashboardFilters } from '../../types/DashboardFilters'
|
|
9
|
-
import { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
|
|
10
|
-
import Layout from '@cdc/core/components/Layout'
|
|
11
|
-
import DashboardFiltersEditor from './DashboardFiltersEditor'
|
|
12
|
-
import { ViewPort } from '@cdc/core/types/ViewPort'
|
|
13
|
-
import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavior'
|
|
14
|
-
import * as apiFilterHelpers from '../../helpers/apiFilterHelpers'
|
|
15
|
-
import { applyQueuedActive } from '@cdc/core/components/Filters/helpers/applyQueuedActive'
|
|
16
|
-
import './dashboardfilter.styles.css'
|
|
17
|
-
import { updateChildFilters } from '../../helpers/updateChildFilters'
|
|
18
|
-
|
|
19
|
-
type SubOptions = { subOptions?: Record<'value' | 'text', string>[] }
|
|
20
|
-
|
|
21
|
-
export type DropdownOptions = (Record<'value' | 'text', string> & SubOptions)[]
|
|
22
|
-
|
|
23
|
-
/** the cached dropdown options for each filter */
|
|
24
|
-
export type APIFilterDropdowns = {
|
|
25
|
-
// null means still loading
|
|
26
|
-
[dropdownsKey: string]: null | DropdownOptions
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
type DashboardFiltersProps = {
|
|
30
|
-
apiFilterDropdowns: APIFilterDropdowns
|
|
31
|
-
visualizationConfig: DashboardFilters
|
|
32
|
-
isEditor?: boolean
|
|
33
|
-
setConfig: (config: DashboardFilters) => void
|
|
34
|
-
currentViewport?: ViewPort
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
38
|
-
apiFilterDropdowns,
|
|
39
|
-
visualizationConfig,
|
|
40
|
-
setConfig: updateConfig,
|
|
41
|
-
currentViewport,
|
|
42
|
-
isEditor = false
|
|
43
|
-
}) => {
|
|
44
|
-
const state = useContext(DashboardContext)
|
|
45
|
-
const { config: dashboardConfig, reloadURLData, loadAPIFilters, setAPIFilterDropdowns, setAPILoading } = state
|
|
46
|
-
const dispatch = useContext(DashboardDispatchContext)
|
|
47
|
-
|
|
48
|
-
const applyFilters = e => {
|
|
49
|
-
e.preventDefault() // prevent form submission
|
|
50
|
-
const dashboardConfig = _.cloneDeep(state.config.dashboard)
|
|
51
|
-
const nonAutoLoadFilterIndexes = Object.values(state.config.visualizations)
|
|
52
|
-
.filter(v => v.type === 'dashboardFilters')
|
|
53
|
-
.reduce((acc, viz: DashboardFilters) => (!viz.autoLoad ? [...acc, viz.sharedFilterIndexes] : acc), [])
|
|
54
|
-
const allRequiredFiltersSelected = !dashboardConfig.sharedFilters.some((filter, filterIndex) => {
|
|
55
|
-
if (nonAutoLoadFilterIndexes.includes(filterIndex)) {
|
|
56
|
-
return !filter.active && !filter.queuedActive
|
|
57
|
-
} else {
|
|
58
|
-
// autoload filters don't need to be selected to apply filters
|
|
59
|
-
return false
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
if (allRequiredFiltersSelected) {
|
|
63
|
-
if (hasDashboardApplyBehavior(state.config.visualizations)) {
|
|
64
|
-
const queryParams = getQueryParams()
|
|
65
|
-
let needsQueryUpdate = false
|
|
66
|
-
dashboardConfig.sharedFilters.forEach(sharedFilter => {
|
|
67
|
-
if (sharedFilter.queuedActive) applyQueuedActive(sharedFilter)
|
|
68
|
-
if (
|
|
69
|
-
sharedFilter.setByQueryParameter &&
|
|
70
|
-
queryParams[sharedFilter.setByQueryParameter] !== sharedFilter.active
|
|
71
|
-
) {
|
|
72
|
-
queryParams[sharedFilter.setByQueryParameter] = Array.isArray(sharedFilter.active)
|
|
73
|
-
? sharedFilter.active.join(',')
|
|
74
|
-
: sharedFilter.active
|
|
75
|
-
needsQueryUpdate = true
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
if (needsQueryUpdate) {
|
|
80
|
-
updateQueryString(queryParams)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
setAPILoading(true)
|
|
84
|
-
dispatch({ type: 'SET_SHARED_FILTERS', payload: dashboardConfig.sharedFilters })
|
|
85
|
-
dispatch({ type: 'SET_FILTERED_DATA', payload: getFilteredData(_.cloneDeep(state)) })
|
|
86
|
-
loadAPIFilters(dashboardConfig.sharedFilters, apiFilterDropdowns)
|
|
87
|
-
.then(newFilters => {
|
|
88
|
-
reloadURLData(newFilters)
|
|
89
|
-
})
|
|
90
|
-
.catch(e => {
|
|
91
|
-
console.error(e)
|
|
92
|
-
})
|
|
93
|
-
} else {
|
|
94
|
-
// TODO noftify of required fields
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const handleOnChange = (index: number, value: string | string[]) => {
|
|
99
|
-
const newConfig = _.cloneDeep(dashboardConfig)
|
|
100
|
-
let [newSharedFilters, changedFilterIndexes] = changeFilterActive(
|
|
101
|
-
index,
|
|
102
|
-
value,
|
|
103
|
-
newConfig.dashboard.sharedFilters,
|
|
104
|
-
visualizationConfig
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
// sets the active filter option that the user just selected.
|
|
108
|
-
dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
|
|
109
|
-
|
|
110
|
-
if (hasDashboardApplyBehavior(dashboardConfig.visualizations)) {
|
|
111
|
-
const isAutoSelectFilter = visualizationConfig.autoLoad
|
|
112
|
-
const missingFilterSelections = newConfig.dashboard.sharedFilters.some(f => !f.active)
|
|
113
|
-
const apiEndpoints = newSharedFilters.filter(f => f.apiFilter).map(f => f.apiFilter.apiEndpoint)
|
|
114
|
-
const loadingFilterMemo = apiFilterHelpers.getLoadingFilterMemo(
|
|
115
|
-
apiEndpoints,
|
|
116
|
-
apiFilterDropdowns,
|
|
117
|
-
changedFilterIndexes
|
|
118
|
-
)
|
|
119
|
-
if (isAutoSelectFilter && !missingFilterSelections) {
|
|
120
|
-
// a dropdown has been selected that doesn't
|
|
121
|
-
// require the Go Button
|
|
122
|
-
setAPIFilterDropdowns(loadingFilterMemo)
|
|
123
|
-
loadAPIFilters(newSharedFilters, loadingFilterMemo).then(filters => {
|
|
124
|
-
reloadURLData(filters)
|
|
125
|
-
})
|
|
126
|
-
} else {
|
|
127
|
-
newSharedFilters[index].queuedActive = value
|
|
128
|
-
// setData to empty object because we no longer have a data state.
|
|
129
|
-
dispatch({ type: 'SET_DATA', payload: {} })
|
|
130
|
-
dispatch({ type: 'SET_FILTERED_DATA', payload: {} })
|
|
131
|
-
setAPIFilterDropdowns(loadingFilterMemo)
|
|
132
|
-
loadAPIFilters(newSharedFilters, loadingFilterMemo)
|
|
133
|
-
}
|
|
134
|
-
} else {
|
|
135
|
-
if (newSharedFilters[index].type === 'urlfilter' && newSharedFilters[index].apiFilter) {
|
|
136
|
-
reloadURLData(newSharedFilters)
|
|
137
|
-
} else {
|
|
138
|
-
const clonedState = _.cloneDeep(state)
|
|
139
|
-
clonedState.config.dashboard.sharedFilters = newSharedFilters
|
|
140
|
-
const newFilteredData = getFilteredData(clonedState)
|
|
141
|
-
dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
|
|
142
|
-
dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
const [displayPanel, setDisplayPanel] = useState(true)
|
|
147
|
-
const onBackClick = () => {
|
|
148
|
-
setDisplayPanel(!displayPanel)
|
|
149
|
-
updateConfig({
|
|
150
|
-
...visualizationConfig,
|
|
151
|
-
showEditorPanel: !displayPanel
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// if all of the filters are hidden filters don't display the VisualizationWrapper
|
|
156
|
-
const filters = visualizationConfig?.sharedFilterIndexes
|
|
157
|
-
?.map(Number)
|
|
158
|
-
.map(filterIndex => dashboardConfig.dashboard.sharedFilters[filterIndex])
|
|
159
|
-
|
|
160
|
-
const displayNone = filters.length ? filters.every(filter => filter.showDropdown === false) : false
|
|
161
|
-
if (displayNone && !isEditor) return <></>
|
|
162
|
-
return (
|
|
163
|
-
<Layout.VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
|
|
164
|
-
{isEditor && (
|
|
165
|
-
<Layout.Sidebar
|
|
166
|
-
displayPanel={displayPanel}
|
|
167
|
-
isDashboard={true}
|
|
168
|
-
title={'Configure Dashboard Filters'}
|
|
169
|
-
onBackClick={onBackClick}
|
|
170
|
-
>
|
|
171
|
-
<DashboardFiltersEditor updateConfig={updateConfig} vizConfig={visualizationConfig} />
|
|
172
|
-
</Layout.Sidebar>
|
|
173
|
-
)}
|
|
174
|
-
|
|
175
|
-
{!displayNone && (
|
|
176
|
-
<Layout.Responsive isEditor={isEditor}>
|
|
177
|
-
<div
|
|
178
|
-
className={`${
|
|
179
|
-
isEditor ? ' is-editor' : ''
|
|
180
|
-
} cove-component__content col-12 cove-dashboard-filters-container`}
|
|
181
|
-
>
|
|
182
|
-
<Filters
|
|
183
|
-
show={visualizationConfig?.sharedFilterIndexes?.map(Number)}
|
|
184
|
-
filters={updateChildFilters(dashboardConfig.dashboard.sharedFilters, state.data) || []}
|
|
185
|
-
apiFilterDropdowns={apiFilterDropdowns}
|
|
186
|
-
handleOnChange={handleOnChange}
|
|
187
|
-
showSubmit={visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad}
|
|
188
|
-
applyFilters={applyFilters}
|
|
189
|
-
applyFiltersButtonText={visualizationConfig.applyFiltersButtonText}
|
|
190
|
-
/>
|
|
191
|
-
</div>
|
|
192
|
-
</Layout.Responsive>
|
|
193
|
-
)}
|
|
194
|
-
</Layout.VisualizationWrapper>
|
|
195
|
-
)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export default DashboardFiltersWrapper
|
|
1
|
+
import { useContext, useState } from 'react'
|
|
2
|
+
import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
|
|
3
|
+
import Filters from './DashboardFilters'
|
|
4
|
+
import { changeFilterActive } from '../../helpers/changeFilterActive'
|
|
5
|
+
import _ from 'lodash'
|
|
6
|
+
import { FilterBehavior } from '../../helpers/FilterBehavior'
|
|
7
|
+
import { getFilteredData } from '../../helpers/getFilteredData'
|
|
8
|
+
import { DashboardFilters } from '../../types/DashboardFilters'
|
|
9
|
+
import { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
|
|
10
|
+
import Layout from '@cdc/core/components/Layout'
|
|
11
|
+
import DashboardFiltersEditor from './DashboardFiltersEditor'
|
|
12
|
+
import { ViewPort } from '@cdc/core/types/ViewPort'
|
|
13
|
+
import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavior'
|
|
14
|
+
import * as apiFilterHelpers from '../../helpers/apiFilterHelpers'
|
|
15
|
+
import { applyQueuedActive } from '@cdc/core/components/Filters/helpers/applyQueuedActive'
|
|
16
|
+
import './dashboardfilter.styles.css'
|
|
17
|
+
import { updateChildFilters } from '../../helpers/updateChildFilters'
|
|
18
|
+
|
|
19
|
+
type SubOptions = { subOptions?: Record<'value' | 'text', string>[] }
|
|
20
|
+
|
|
21
|
+
export type DropdownOptions = (Record<'value' | 'text', string> & SubOptions)[]
|
|
22
|
+
|
|
23
|
+
/** the cached dropdown options for each filter */
|
|
24
|
+
export type APIFilterDropdowns = {
|
|
25
|
+
// null means still loading
|
|
26
|
+
[dropdownsKey: string]: null | DropdownOptions
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type DashboardFiltersProps = {
|
|
30
|
+
apiFilterDropdowns: APIFilterDropdowns
|
|
31
|
+
visualizationConfig: DashboardFilters
|
|
32
|
+
isEditor?: boolean
|
|
33
|
+
setConfig: (config: DashboardFilters) => void
|
|
34
|
+
currentViewport?: ViewPort
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
38
|
+
apiFilterDropdowns,
|
|
39
|
+
visualizationConfig,
|
|
40
|
+
setConfig: updateConfig,
|
|
41
|
+
currentViewport,
|
|
42
|
+
isEditor = false
|
|
43
|
+
}) => {
|
|
44
|
+
const state = useContext(DashboardContext)
|
|
45
|
+
const { config: dashboardConfig, reloadURLData, loadAPIFilters, setAPIFilterDropdowns, setAPILoading } = state
|
|
46
|
+
const dispatch = useContext(DashboardDispatchContext)
|
|
47
|
+
|
|
48
|
+
const applyFilters = e => {
|
|
49
|
+
e.preventDefault() // prevent form submission
|
|
50
|
+
const dashboardConfig = _.cloneDeep(state.config.dashboard)
|
|
51
|
+
const nonAutoLoadFilterIndexes = Object.values(state.config.visualizations)
|
|
52
|
+
.filter(v => v.type === 'dashboardFilters')
|
|
53
|
+
.reduce((acc, viz: DashboardFilters) => (!viz.autoLoad ? [...acc, viz.sharedFilterIndexes] : acc), [])
|
|
54
|
+
const allRequiredFiltersSelected = !dashboardConfig.sharedFilters.some((filter, filterIndex) => {
|
|
55
|
+
if (nonAutoLoadFilterIndexes.includes(filterIndex)) {
|
|
56
|
+
return !filter.active && !filter.queuedActive
|
|
57
|
+
} else {
|
|
58
|
+
// autoload filters don't need to be selected to apply filters
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
if (allRequiredFiltersSelected) {
|
|
63
|
+
if (hasDashboardApplyBehavior(state.config.visualizations)) {
|
|
64
|
+
const queryParams = getQueryParams()
|
|
65
|
+
let needsQueryUpdate = false
|
|
66
|
+
dashboardConfig.sharedFilters.forEach(sharedFilter => {
|
|
67
|
+
if (sharedFilter.queuedActive) applyQueuedActive(sharedFilter)
|
|
68
|
+
if (
|
|
69
|
+
sharedFilter.setByQueryParameter &&
|
|
70
|
+
queryParams[sharedFilter.setByQueryParameter] !== sharedFilter.active
|
|
71
|
+
) {
|
|
72
|
+
queryParams[sharedFilter.setByQueryParameter] = Array.isArray(sharedFilter.active)
|
|
73
|
+
? sharedFilter.active.join(',')
|
|
74
|
+
: sharedFilter.active
|
|
75
|
+
needsQueryUpdate = true
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
if (needsQueryUpdate) {
|
|
80
|
+
updateQueryString(queryParams)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
setAPILoading(true)
|
|
84
|
+
dispatch({ type: 'SET_SHARED_FILTERS', payload: dashboardConfig.sharedFilters })
|
|
85
|
+
dispatch({ type: 'SET_FILTERED_DATA', payload: getFilteredData(_.cloneDeep(state)) })
|
|
86
|
+
loadAPIFilters(dashboardConfig.sharedFilters, apiFilterDropdowns)
|
|
87
|
+
.then(newFilters => {
|
|
88
|
+
reloadURLData(newFilters)
|
|
89
|
+
})
|
|
90
|
+
.catch(e => {
|
|
91
|
+
console.error(e)
|
|
92
|
+
})
|
|
93
|
+
} else {
|
|
94
|
+
// TODO noftify of required fields
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const handleOnChange = (index: number, value: string | string[]) => {
|
|
99
|
+
const newConfig = _.cloneDeep(dashboardConfig)
|
|
100
|
+
let [newSharedFilters, changedFilterIndexes] = changeFilterActive(
|
|
101
|
+
index,
|
|
102
|
+
value,
|
|
103
|
+
newConfig.dashboard.sharedFilters,
|
|
104
|
+
visualizationConfig
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
// sets the active filter option that the user just selected.
|
|
108
|
+
dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
|
|
109
|
+
|
|
110
|
+
if (hasDashboardApplyBehavior(dashboardConfig.visualizations)) {
|
|
111
|
+
const isAutoSelectFilter = visualizationConfig.autoLoad
|
|
112
|
+
const missingFilterSelections = newConfig.dashboard.sharedFilters.some(f => !f.active)
|
|
113
|
+
const apiEndpoints = newSharedFilters.filter(f => f.apiFilter).map(f => f.apiFilter.apiEndpoint)
|
|
114
|
+
const loadingFilterMemo = apiFilterHelpers.getLoadingFilterMemo(
|
|
115
|
+
apiEndpoints,
|
|
116
|
+
apiFilterDropdowns,
|
|
117
|
+
changedFilterIndexes
|
|
118
|
+
)
|
|
119
|
+
if (isAutoSelectFilter && !missingFilterSelections) {
|
|
120
|
+
// a dropdown has been selected that doesn't
|
|
121
|
+
// require the Go Button
|
|
122
|
+
setAPIFilterDropdowns(loadingFilterMemo)
|
|
123
|
+
loadAPIFilters(newSharedFilters, loadingFilterMemo).then(filters => {
|
|
124
|
+
reloadURLData(filters)
|
|
125
|
+
})
|
|
126
|
+
} else {
|
|
127
|
+
newSharedFilters[index].queuedActive = value
|
|
128
|
+
// setData to empty object because we no longer have a data state.
|
|
129
|
+
dispatch({ type: 'SET_DATA', payload: {} })
|
|
130
|
+
dispatch({ type: 'SET_FILTERED_DATA', payload: {} })
|
|
131
|
+
setAPIFilterDropdowns(loadingFilterMemo)
|
|
132
|
+
loadAPIFilters(newSharedFilters, loadingFilterMemo)
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
if (newSharedFilters[index].type === 'urlfilter' && newSharedFilters[index].apiFilter) {
|
|
136
|
+
reloadURLData(newSharedFilters)
|
|
137
|
+
} else {
|
|
138
|
+
const clonedState = _.cloneDeep(state)
|
|
139
|
+
clonedState.config.dashboard.sharedFilters = newSharedFilters
|
|
140
|
+
const newFilteredData = getFilteredData(clonedState)
|
|
141
|
+
dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
|
|
142
|
+
dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const [displayPanel, setDisplayPanel] = useState(true)
|
|
147
|
+
const onBackClick = () => {
|
|
148
|
+
setDisplayPanel(!displayPanel)
|
|
149
|
+
updateConfig({
|
|
150
|
+
...visualizationConfig,
|
|
151
|
+
showEditorPanel: !displayPanel
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// if all of the filters are hidden filters don't display the VisualizationWrapper
|
|
156
|
+
const filters = visualizationConfig?.sharedFilterIndexes
|
|
157
|
+
?.map(Number)
|
|
158
|
+
.map(filterIndex => dashboardConfig.dashboard.sharedFilters[filterIndex])
|
|
159
|
+
|
|
160
|
+
const displayNone = filters.length ? filters.every(filter => filter.showDropdown === false) : false
|
|
161
|
+
if (displayNone && !isEditor) return <></>
|
|
162
|
+
return (
|
|
163
|
+
<Layout.VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
|
|
164
|
+
{isEditor && (
|
|
165
|
+
<Layout.Sidebar
|
|
166
|
+
displayPanel={displayPanel}
|
|
167
|
+
isDashboard={true}
|
|
168
|
+
title={'Configure Dashboard Filters'}
|
|
169
|
+
onBackClick={onBackClick}
|
|
170
|
+
>
|
|
171
|
+
<DashboardFiltersEditor updateConfig={updateConfig} vizConfig={visualizationConfig} />
|
|
172
|
+
</Layout.Sidebar>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
{!displayNone && (
|
|
176
|
+
<Layout.Responsive isEditor={isEditor}>
|
|
177
|
+
<div
|
|
178
|
+
className={`${
|
|
179
|
+
isEditor ? ' is-editor' : ''
|
|
180
|
+
} cove-component__content col-12 cove-dashboard-filters-container`}
|
|
181
|
+
>
|
|
182
|
+
<Filters
|
|
183
|
+
show={visualizationConfig?.sharedFilterIndexes?.map(Number)}
|
|
184
|
+
filters={updateChildFilters(dashboardConfig.dashboard.sharedFilters, state.data) || []}
|
|
185
|
+
apiFilterDropdowns={apiFilterDropdowns}
|
|
186
|
+
handleOnChange={handleOnChange}
|
|
187
|
+
showSubmit={visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad}
|
|
188
|
+
applyFilters={applyFilters}
|
|
189
|
+
applyFiltersButtonText={visualizationConfig.applyFiltersButtonText}
|
|
190
|
+
/>
|
|
191
|
+
</div>
|
|
192
|
+
</Layout.Responsive>
|
|
193
|
+
)}
|
|
194
|
+
</Layout.VisualizationWrapper>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export default DashboardFiltersWrapper
|
|
@@ -29,20 +29,16 @@ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, ro
|
|
|
29
29
|
const [errorMessage, setErrorMessage] = useState('')
|
|
30
30
|
const [loadingAPIData, setLoadingAPIData] = useState(false)
|
|
31
31
|
|
|
32
|
+
const isUpdatingVisualization = useMemo(() => {
|
|
33
|
+
return !!vizKey && !useRow
|
|
34
|
+
}, [vizKey, useRow])
|
|
35
|
+
|
|
32
36
|
const configureData = useMemo(() => {
|
|
33
|
-
if (
|
|
37
|
+
if (isUpdatingVisualization) {
|
|
34
38
|
return config.visualizations[vizKey]
|
|
35
39
|
}
|
|
36
40
|
return config.rows[rowIndex]
|
|
37
|
-
}, [config.visualizations, config.rows,
|
|
38
|
-
|
|
39
|
-
const updateConfigureData = (newConfigureData: Partial<ConfigureData>) => {
|
|
40
|
-
if (vizKey && !useRow) {
|
|
41
|
-
dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey, configureData: newConfigureData } })
|
|
42
|
-
} else {
|
|
43
|
-
dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: newConfigureData } })
|
|
44
|
-
}
|
|
45
|
-
}
|
|
41
|
+
}, [config.visualizations, config.rows, rowIndex, isUpdatingVisualization])
|
|
46
42
|
|
|
47
43
|
const fetchData = async datasetKey => {
|
|
48
44
|
const { data, dataUrl } = config.datasets[datasetKey]
|
|
@@ -65,10 +61,32 @@ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, ro
|
|
|
65
61
|
return newData
|
|
66
62
|
}
|
|
67
63
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
const updateConfigureData = (newConfigureData: Partial<ConfigureData>) => {
|
|
65
|
+
if (isUpdatingVisualization) {
|
|
66
|
+
dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey, configureData: newConfigureData } })
|
|
67
|
+
} else {
|
|
68
|
+
dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: newConfigureData } })
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
71
|
|
|
72
|
+
const removeDatasetsFromVisualizations = () => {
|
|
73
|
+
const columnVisualizations = config.rows[rowIndex].columns.map(column => column.widget).filter(Boolean)
|
|
74
|
+
columnVisualizations.forEach(currentVisualizationKey => {
|
|
75
|
+
dispatch({ type: 'RESET_VISUALIZATION', payload: { vizKey: currentVisualizationKey } })
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const changeDataset = async ({ target: { value } }) => {
|
|
80
|
+
if (!isUpdatingVisualization) removeDatasetsFromVisualizations()
|
|
81
|
+
const newData = value === '' ? {} : await fetchData(value)
|
|
82
|
+
const newConfigureData = {
|
|
83
|
+
dataDescription: {
|
|
84
|
+
horizontal: false
|
|
85
|
+
},
|
|
86
|
+
formattedData: undefined,
|
|
87
|
+
dataKey: value,
|
|
88
|
+
data: newData
|
|
89
|
+
} as ConfigureData
|
|
72
90
|
updateConfigureData(newConfigureData)
|
|
73
91
|
}
|
|
74
92
|
|
|
@@ -112,12 +130,13 @@ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, ro
|
|
|
112
130
|
Object.keys(config.datasets).map(datasetKey => <option key={datasetKey}>{datasetKey}</option>)}
|
|
113
131
|
</select>
|
|
114
132
|
{vizKey && (
|
|
133
|
+
// only shows for visualizations
|
|
115
134
|
<CheckBox
|
|
116
135
|
label='Apply To Row'
|
|
117
136
|
value={useRow}
|
|
118
137
|
updateField={(section, subsection, fieldName, value) => {
|
|
119
138
|
setUseRow(value)
|
|
120
|
-
changeDataset({ target: { value:
|
|
139
|
+
changeDataset({ target: { value: configureData.dataKey } })
|
|
121
140
|
}}
|
|
122
141
|
/>
|
|
123
142
|
)}
|
|
@@ -9,7 +9,7 @@ import _ from 'lodash'
|
|
|
9
9
|
|
|
10
10
|
type HeaderProps = {
|
|
11
11
|
back?: any
|
|
12
|
-
subEditor?:
|
|
12
|
+
subEditor?: boolean
|
|
13
13
|
visualizationKey?: string
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -96,14 +96,12 @@ const Header = (props: HeaderProps) => {
|
|
|
96
96
|
multidashboard
|
|
97
97
|
</span>
|
|
98
98
|
<br />
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
/>
|
|
106
|
-
}
|
|
99
|
+
<input
|
|
100
|
+
type='text'
|
|
101
|
+
placeholder='Enter Dashboard Name Here'
|
|
102
|
+
defaultValue={config.dashboard?.title}
|
|
103
|
+
onChange={e => changeConfigValue('dashboard', 'title', e.target.value)}
|
|
104
|
+
/>
|
|
107
105
|
</div>
|
|
108
106
|
)}
|
|
109
107
|
{!subEditor && (
|
package/src/components/Row.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext, useMemo
|
|
1
|
+
import React, { useContext, useMemo } from 'react'
|
|
2
2
|
|
|
3
3
|
import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
|
|
4
4
|
|
|
@@ -215,30 +215,11 @@ type RowProps = { row: ConfigRow; idx: number; uuid: number | string }
|
|
|
215
215
|
|
|
216
216
|
const Row: React.FC<RowProps> = ({ row, idx: rowIdx, uuid }) => {
|
|
217
217
|
const { overlay } = useGlobalContext()
|
|
218
|
-
const dispatch = useContext(DashboardDispatchContext)
|
|
219
|
-
|
|
220
|
-
const configureFootnotes = () => {
|
|
221
|
-
if (!row.footnotesId) {
|
|
222
|
-
const type = 'footnotes'
|
|
223
|
-
const uid = type + Date.now()
|
|
224
|
-
const newVisualizationConfig = {
|
|
225
|
-
uid,
|
|
226
|
-
type,
|
|
227
|
-
visualizationType: type,
|
|
228
|
-
editing: true
|
|
229
|
-
}
|
|
230
|
-
dispatch({
|
|
231
|
-
type: 'ADD_FOOTNOTE',
|
|
232
|
-
payload: { id: uid, rowIndex: rowIdx, config: newVisualizationConfig as Visualization }
|
|
233
|
-
})
|
|
234
|
-
} else {
|
|
235
|
-
dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey: row.footnotesId, configureData: { editing: true } } })
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
218
|
return (
|
|
239
219
|
<>
|
|
240
220
|
<div className='builder-row' data-row-id={rowIdx}>
|
|
241
221
|
<RowMenu rowIdx={rowIdx} />
|
|
222
|
+
<span className='ms-2 mt-n3'>Row - {rowIdx + 1}</span>
|
|
242
223
|
<button
|
|
243
224
|
title='Configure Data'
|
|
244
225
|
className='btn btn-configure-row'
|
|
@@ -261,9 +242,6 @@ const Row: React.FC<RowProps> = ({ row, idx: rowIdx, uuid }) => {
|
|
|
261
242
|
/>
|
|
262
243
|
))}
|
|
263
244
|
</div>
|
|
264
|
-
<button className='btn btn-primary footnotes' onClick={configureFootnotes}>
|
|
265
|
-
{row.footnotesId ? 'Edit' : 'Add'} Footnotes
|
|
266
|
-
</button>
|
|
267
245
|
</div>
|
|
268
246
|
</>
|
|
269
247
|
)
|