@cdc/dashboard 4.24.5 → 4.24.7
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 +122872 -112065
- package/examples/custom/css/respiratory.css +236 -0
- package/examples/custom/js/respiratory.js +242 -0
- package/examples/default-multi-dataset-shared-filter.json +1729 -0
- package/examples/ed-visits-county-file.json +618 -0
- package/examples/filtered-dash.json +6 -21
- package/index.html +10 -1
- package/package.json +12 -11
- package/src/CdcDashboard.tsx +5 -1
- package/src/CdcDashboardComponent.tsx +165 -306
- package/src/DashboardContext.tsx +9 -1
- package/src/_stories/Dashboard.stories.tsx +38 -34
- package/src/_stories/_mock/api-filter-chart.json +11 -35
- package/src/_stories/_mock/api-filter-map.json +17 -31
- package/src/_stories/_mock/multi-viz.json +2 -3
- package/src/_stories/_mock/pivot-filter.json +14 -12
- package/src/components/CollapsibleVisualizationRow.tsx +44 -0
- package/src/components/Column.tsx +1 -1
- package/src/components/DashboardFilters/DashboardFilters.tsx +80 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +218 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +48 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +367 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/index.ts +1 -0
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +143 -0
- package/src/components/DashboardFilters/index.ts +3 -0
- package/src/components/DataDesignerModal.tsx +9 -9
- package/src/components/ExpandCollapseButtons.tsx +20 -0
- package/src/components/Header/Header.tsx +1 -97
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +4 -4
- package/src/components/Row.tsx +52 -19
- package/src/components/Toggle/Toggle.tsx +2 -4
- package/src/components/VisualizationRow.tsx +82 -24
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +116 -0
- package/src/components/VisualizationsPanel/index.ts +1 -0
- package/src/components/VisualizationsPanel/visualizations-panel-styles.css +12 -0
- package/src/components/Widget.tsx +26 -90
- package/src/helpers/apiFilterHelpers.ts +51 -0
- package/src/helpers/changeFilterActive.ts +30 -0
- package/src/helpers/filterData.ts +10 -48
- package/src/helpers/generateValuesForFilter.ts +1 -1
- package/src/helpers/getAutoLoadVisualization.ts +11 -0
- package/src/helpers/getFilteredData.ts +4 -2
- package/src/helpers/getVizConfig.ts +23 -2
- package/src/helpers/getVizRowColumnLocator.ts +2 -1
- package/src/helpers/hasDashboardApplyBehavior.ts +5 -0
- package/src/helpers/iconHash.tsx +3 -3
- package/src/helpers/mapDataToConfig.ts +29 -0
- package/src/helpers/processData.ts +2 -3
- package/src/helpers/reloadURLHelpers.ts +68 -0
- package/src/helpers/tests/filterData.test.ts +1 -93
- package/src/scss/editor-panel.scss +1 -1
- package/src/scss/grid.scss +34 -27
- package/src/scss/main.scss +41 -3
- package/src/scss/variables.scss +4 -0
- package/src/store/dashboard.actions.ts +12 -4
- package/src/store/dashboard.reducer.ts +30 -4
- package/src/types/APIFilter.ts +1 -5
- package/src/types/ConfigRow.ts +2 -0
- package/src/types/Dashboard.ts +1 -1
- package/src/types/DashboardConfig.ts +2 -4
- package/src/types/DashboardFilters.ts +7 -0
- package/src/types/InitialState.ts +1 -1
- package/src/types/MultiDashboard.ts +2 -2
- package/src/types/SharedFilter.ts +2 -5
- package/src/types/Tab.ts +1 -1
- package/LICENSE +0 -201
- package/src/components/Filters.tsx +0 -88
- package/src/components/Header/FilterModal.tsx +0 -510
- package/src/components/VisualizationsPanel.tsx +0 -95
- package/src/helpers/getApiFilterKey.ts +0 -5
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { FilterBehavior } from '../components/Header/Header'
|
|
2
1
|
import { DataSet } from '../types/DataSet'
|
|
3
2
|
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
4
3
|
import { getFormattedData } from './getFormattedData'
|
|
5
4
|
|
|
6
|
-
export const processData = async (dataSet: DataSet,
|
|
7
|
-
if (dataSet.dataUrl &&
|
|
5
|
+
export const processData = async (dataSet: DataSet, hasFilterChangeBehavior: boolean) => {
|
|
6
|
+
if (dataSet.dataUrl && hasFilterChangeBehavior) {
|
|
8
7
|
const dataset = await fetchRemoteData(`${dataSet.dataUrl}`)
|
|
9
8
|
return getFormattedData(dataset, dataSet.dataDescription)
|
|
10
9
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { gatherQueryParams } from '@cdc/core/helpers/gatherQueryParams'
|
|
2
|
+
import { SharedFilter } from '../types/SharedFilter'
|
|
3
|
+
import { capitalizeSplitAndJoin } from '@cdc/core/helpers/cove/string'
|
|
4
|
+
import { Visualization } from '@cdc/core/types/Visualization'
|
|
5
|
+
import _ from 'lodash'
|
|
6
|
+
|
|
7
|
+
export const isUpdateNeeded = (filters: SharedFilter[], currentQueryParams: Record<string, string>, newQueryParams: Record<string, string>): boolean => {
|
|
8
|
+
let needsUpdate = false
|
|
9
|
+
filters.find(filter => {
|
|
10
|
+
if (filter.type === 'urlfilter' && !Array.isArray(filter.active) && filter.filterBy === 'File Name') {
|
|
11
|
+
needsUpdate = true
|
|
12
|
+
return true
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
Object.keys(newQueryParams).forEach(updatedParam => {
|
|
16
|
+
if (decodeURIComponent(newQueryParams[updatedParam]) !== currentQueryParams[updatedParam]) {
|
|
17
|
+
needsUpdate = true
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
return needsUpdate
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const getDataURL = (updatedQSParams: Record<string, string>, dataUrl: URL, newFileName: string) => {
|
|
24
|
+
const _params = Object.keys(updatedQSParams).map(key => ({ key, value: updatedQSParams[key] }))
|
|
25
|
+
const baseURL = dataUrl.origin + dataUrl.pathname
|
|
26
|
+
let dataUrlFinal = `${baseURL}${gatherQueryParams(baseURL, _params)}`
|
|
27
|
+
|
|
28
|
+
if (newFileName !== '') {
|
|
29
|
+
const fileExtension = dataUrl.pathname.split('.').pop()
|
|
30
|
+
const pathWithoutFilename = dataUrl.pathname.substring(0, dataUrl.pathname.lastIndexOf('/'))
|
|
31
|
+
dataUrlFinal = `${dataUrl.origin}${pathWithoutFilename}/${newFileName}.${fileExtension}${gatherQueryParams(baseURL, _params)}`
|
|
32
|
+
}
|
|
33
|
+
return dataUrlFinal
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const getNewFileName = (newFileName: string, filter: SharedFilter, datasetKey: string) => {
|
|
37
|
+
const replacements = {
|
|
38
|
+
'Remove Spaces': '',
|
|
39
|
+
'Keep Spaces': ' ',
|
|
40
|
+
'Replace With Underscore': '_'
|
|
41
|
+
}
|
|
42
|
+
let fileName = newFileName
|
|
43
|
+
if (filter.datasetKey === datasetKey) {
|
|
44
|
+
if (filter.fileName) {
|
|
45
|
+
// if a file name is found, ie, state_${query}, use that, ie. state_activeFilter.json
|
|
46
|
+
fileName = capitalizeSplitAndJoin.call(String(filter.fileName), ' ', replacements[filter.whitespaceReplacement ?? 'Keep Spaces'])
|
|
47
|
+
} else {
|
|
48
|
+
// if no file name is entered use the default active filter. ie. /activeFilter.json
|
|
49
|
+
fileName = filter.active as string
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (fileName?.includes('${query}')) {
|
|
54
|
+
fileName = fileName.replace('${query}', capitalizeSplitAndJoin.call(String(filter.active), ' ', replacements[filter.whitespaceReplacement ?? 'Keep Spaces']))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return fileName
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const getVisualizationsWithFormattedData = (visualizations: Record<string, Visualization>, newData: Object) => {
|
|
61
|
+
return Object.keys(visualizations).reduce((acc, vizKey) => {
|
|
62
|
+
const dataKey = visualizations[vizKey].dataKey
|
|
63
|
+
if (newData[dataKey]) {
|
|
64
|
+
acc[vizKey].formattedData = newData[dataKey]
|
|
65
|
+
}
|
|
66
|
+
return acc
|
|
67
|
+
}, _.cloneDeep(visualizations))
|
|
68
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
1
2
|
import { SharedFilter } from '../../types/SharedFilter'
|
|
2
3
|
import { filterData } from '../filterData'
|
|
3
4
|
|
|
@@ -36,48 +37,6 @@ describe('filterData', () => {
|
|
|
36
37
|
expect(result).toEqual([{ name: 'John', age: 30 }])
|
|
37
38
|
})
|
|
38
39
|
|
|
39
|
-
it('causes sideEffects to filters', () => {
|
|
40
|
-
// the side effect is not desired, but current functionality depends on the sideEffect.
|
|
41
|
-
// hopefully the side effect will be refactored in the future to be a returned value.
|
|
42
|
-
const filters = [
|
|
43
|
-
{ columnName: 'name', active: 'John', queuedActive: 'John', fileName: 'abc', key: 'name' },
|
|
44
|
-
{ columnName: 'age', fileName: 'abc', key: 'age' },
|
|
45
|
-
{ columnName: 'color', fileName: 'abc', key: 'color', parents: ['age'] }
|
|
46
|
-
] as SharedFilter[]
|
|
47
|
-
const data = [
|
|
48
|
-
{ name: 'John', age: 30, color: 'blue' },
|
|
49
|
-
{ name: 'Jane', age: 25, color: 'red' },
|
|
50
|
-
{ name: 'John', age: 35, color: 'yellow' },
|
|
51
|
-
{ name: 'Jane', age: 30, color: 'green' }
|
|
52
|
-
]
|
|
53
|
-
|
|
54
|
-
const result = filterData(filters, data)
|
|
55
|
-
|
|
56
|
-
expect(result).toEqual([{ name: 'John', age: 30, color: 'blue' }])
|
|
57
|
-
|
|
58
|
-
const sideEffectOfFiltering = [
|
|
59
|
-
{
|
|
60
|
-
columnName: 'name',
|
|
61
|
-
active: 'John',
|
|
62
|
-
queuedActive: 'John',
|
|
63
|
-
fileName: 'abc',
|
|
64
|
-
key: 'name',
|
|
65
|
-
tier: 1
|
|
66
|
-
},
|
|
67
|
-
{ columnName: 'age', fileName: 'abc', key: 'age', tier: 1 },
|
|
68
|
-
{
|
|
69
|
-
columnName: 'color',
|
|
70
|
-
fileName: 'abc',
|
|
71
|
-
key: 'color',
|
|
72
|
-
parents: ['age'],
|
|
73
|
-
tier: 2,
|
|
74
|
-
values: ['blue', 'yellow'],
|
|
75
|
-
active: 'blue'
|
|
76
|
-
}
|
|
77
|
-
]
|
|
78
|
-
expect(filters).toEqual(sideEffectOfFiltering)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
40
|
it('should not include data that does not meet the filter criteria', () => {
|
|
82
41
|
const filters = [
|
|
83
42
|
//{ columnName: 'apple', fileName: 'abc', key: 'banana' },
|
|
@@ -95,55 +54,4 @@ describe('filterData', () => {
|
|
|
95
54
|
const result = filterData(filters, data)
|
|
96
55
|
expect(result).toEqual([{ name: 'John', age: 25, color: 'red' }])
|
|
97
56
|
})
|
|
98
|
-
|
|
99
|
-
it('should pivot data based on the provided filters', () => {
|
|
100
|
-
const filters = [{ key: 'Race', type: 'datafilter', showDropdown: true, columnName: 'Race', pivot: 'Age-adjusted rate', usedBy: ['table1707935263149'] }] as SharedFilter[]
|
|
101
|
-
const data = [
|
|
102
|
-
{
|
|
103
|
-
Race: 'Hispanic or Latino',
|
|
104
|
-
'Age-adjusted rate': '644.2',
|
|
105
|
-
Year: '2016'
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
Race: 'Non-Hispanic American Indian',
|
|
109
|
-
'Age-adjusted rate': '636.1',
|
|
110
|
-
Year: '2016'
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
Race: 'Non-Hispanic Black',
|
|
114
|
-
'Age-adjusted rate': '563.7',
|
|
115
|
-
Year: '2016'
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
Race: 'Hispanic or Latino',
|
|
119
|
-
'Age-adjusted rate': '644.2',
|
|
120
|
-
Year: '2017'
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
Race: 'Non-Hispanic American Indian',
|
|
124
|
-
'Age-adjusted rate': '636.1',
|
|
125
|
-
Year: '2017'
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
Race: 'Non-Hispanic Black',
|
|
129
|
-
'Age-adjusted rate': '563.7',
|
|
130
|
-
Year: '2017'
|
|
131
|
-
}
|
|
132
|
-
]
|
|
133
|
-
|
|
134
|
-
expect(filterData(filters, data)).toEqual([
|
|
135
|
-
{
|
|
136
|
-
'Hispanic or Latino': '644.2',
|
|
137
|
-
'Non-Hispanic American Indian': '636.1',
|
|
138
|
-
'Non-Hispanic Black': '563.7',
|
|
139
|
-
Year: '2016'
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
'Hispanic or Latino': '644.2',
|
|
143
|
-
'Non-Hispanic American Indian': '636.1',
|
|
144
|
-
'Non-Hispanic Black': '563.7',
|
|
145
|
-
Year: '2017'
|
|
146
|
-
}
|
|
147
|
-
])
|
|
148
|
-
})
|
|
149
57
|
})
|
package/src/scss/grid.scss
CHANGED
|
@@ -7,15 +7,6 @@ $red: #f74242;
|
|
|
7
7
|
margin-left: calc($editorWidth + 1em);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
.visualizations-panel {
|
|
11
|
-
background-color: #fff;
|
|
12
|
-
padding: 1em;
|
|
13
|
-
width: $editorWidth;
|
|
14
|
-
border-right: #c7c7c7 1px solid;
|
|
15
|
-
z-index: 1;
|
|
16
|
-
overflow-y: scroll;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
10
|
.hidden.editor-panel + .builder-grid {
|
|
20
11
|
margin-left: 0 !important;
|
|
21
12
|
}
|
|
@@ -30,30 +21,16 @@ $red: #f74242;
|
|
|
30
21
|
padding: 5em 3em 3em;
|
|
31
22
|
}
|
|
32
23
|
|
|
33
|
-
.column-container {
|
|
34
|
-
display: flex;
|
|
35
|
-
flex-flow: row;
|
|
36
|
-
width: 100%;
|
|
37
|
-
position: relative;
|
|
38
|
-
padding: 2em 1em 1em;
|
|
39
|
-
border: 1px solid #c2c2c2;
|
|
40
|
-
transition: border 300ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
41
|
-
background-color: #f2f2f2;
|
|
42
|
-
user-select: none;
|
|
43
|
-
|
|
44
|
-
&.can-drop {
|
|
45
|
-
border-color: $blue-light;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
24
|
.row-menu {
|
|
50
25
|
display: flex;
|
|
51
26
|
align-items: flex-start;
|
|
52
27
|
transition: opacity 300ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
53
28
|
user-select: none;
|
|
54
|
-
position:
|
|
29
|
+
position: absolute;
|
|
55
30
|
z-index: 1;
|
|
56
|
-
|
|
31
|
+
top: -27px;
|
|
32
|
+
width: 100%;
|
|
33
|
+
left: 0;
|
|
57
34
|
|
|
58
35
|
> li:not(.spacer) + li:not(.spacer) {
|
|
59
36
|
margin-left: 0.3em;
|
|
@@ -337,6 +314,35 @@ $red: #f74242;
|
|
|
337
314
|
top: 0;
|
|
338
315
|
}
|
|
339
316
|
|
|
317
|
+
.footnotes {
|
|
318
|
+
margin: 0.5rem;
|
|
319
|
+
margin-bottom: 0;
|
|
320
|
+
width: calc(100% - 1rem);
|
|
321
|
+
border: 1px solid #c2c2c2;
|
|
322
|
+
transition: background-color 300ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
323
|
+
background-color: #c2c2c2;
|
|
324
|
+
&:hover {
|
|
325
|
+
border-color: $blue;
|
|
326
|
+
background-color: $blue;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
padding: 2em 1em 1em;
|
|
331
|
+
border: 1px solid #c2c2c2;
|
|
332
|
+
transition: border 300ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
333
|
+
background-color: #f2f2f2;
|
|
334
|
+
user-select: none;
|
|
335
|
+
|
|
336
|
+
&.can-drop {
|
|
337
|
+
border-color: $blue-light;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.column-container {
|
|
341
|
+
display: flex;
|
|
342
|
+
flex-flow: row;
|
|
343
|
+
width: 100%;
|
|
344
|
+
}
|
|
345
|
+
|
|
340
346
|
.widget__content {
|
|
341
347
|
padding: 0 2em;
|
|
342
348
|
|
|
@@ -369,5 +375,6 @@ $red: #f74242;
|
|
|
369
375
|
.column-container {
|
|
370
376
|
border-color: $blue;
|
|
371
377
|
}
|
|
378
|
+
border-color: $blue;
|
|
372
379
|
}
|
|
373
380
|
}
|
package/src/scss/main.scss
CHANGED
|
@@ -156,6 +156,13 @@
|
|
|
156
156
|
z-index: -1;
|
|
157
157
|
position: relative;
|
|
158
158
|
}
|
|
159
|
+
|
|
160
|
+
// Expand and Collapse Buttons for Multiviz Dashboard
|
|
161
|
+
&.expand-collapse-buttons {
|
|
162
|
+
background-color: var(--lightestGray);
|
|
163
|
+
border: 1px var(--lightGray) solid;
|
|
164
|
+
color: black;
|
|
165
|
+
}
|
|
159
166
|
}
|
|
160
167
|
|
|
161
168
|
.warning-icon {
|
|
@@ -181,6 +188,40 @@
|
|
|
181
188
|
}
|
|
182
189
|
}
|
|
183
190
|
|
|
191
|
+
.collapsable-multiviz-container {
|
|
192
|
+
position: relative;
|
|
193
|
+
border: $lightGray 1px solid;
|
|
194
|
+
clear: both;
|
|
195
|
+
margin-bottom: 20px;
|
|
196
|
+
.multi-visualiation-heading {
|
|
197
|
+
position: relative;
|
|
198
|
+
background: var(--lightestGray);
|
|
199
|
+
padding: 0.5em 0.7em;
|
|
200
|
+
cursor: pointer;
|
|
201
|
+
svg {
|
|
202
|
+
position: absolute;
|
|
203
|
+
height: 100%;
|
|
204
|
+
width: 15px;
|
|
205
|
+
top: 0;
|
|
206
|
+
right: 1em;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
&:focus {
|
|
210
|
+
z-index: 2;
|
|
211
|
+
position: relative;
|
|
212
|
+
}
|
|
213
|
+
@include breakpoint(xs) {
|
|
214
|
+
font-size: $font-small + 0.2em;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
.data-table-heading {
|
|
218
|
+
display: none;
|
|
219
|
+
}
|
|
220
|
+
.table-container {
|
|
221
|
+
margin: 0 1em;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
184
225
|
.dashboard-download-link {
|
|
185
226
|
font-size: 14px;
|
|
186
227
|
}
|
|
@@ -205,9 +246,6 @@
|
|
|
205
246
|
margin-left: 0;
|
|
206
247
|
margin-right: 0;
|
|
207
248
|
}
|
|
208
|
-
&.hidden-toggle {
|
|
209
|
-
display: none;
|
|
210
|
-
}
|
|
211
249
|
}
|
|
212
250
|
|
|
213
251
|
.dashboard-col-12 {
|
package/src/scss/variables.scss
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import type { DashboardConfig as Config } from '../types/DashboardConfig'
|
|
2
2
|
import { type Action } from '@cdc/core/types/Action'
|
|
3
3
|
import { Tab } from '../types/Tab'
|
|
4
|
-
import { ConfigureData } from '@cdc/core/types/ConfigureData'
|
|
5
4
|
import { ConfigRow } from '../types/ConfigRow'
|
|
5
|
+
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
6
|
+
import Footnotes from '@cdc/core/types/Footnotes'
|
|
7
|
+
import { SharedFilter } from '../types/SharedFilter'
|
|
6
8
|
|
|
7
|
-
type
|
|
9
|
+
type ADD_FOOTNOTE = Action<'ADD_FOOTNOTE', { id: string; rowIndex: number; config: Footnotes }>
|
|
10
|
+
type APPLY_CONFIG = Action<'APPLY_CONFIG', [Config, Object?]>
|
|
11
|
+
type SET_CONFIG = Action<'SET_CONFIG', Partial<Config>>
|
|
8
12
|
type UPDATE_CONFIG = Action<'UPDATE_CONFIG', [Config, Object?]>
|
|
9
|
-
type SET_DATA = Action<'SET_DATA',
|
|
13
|
+
type SET_DATA = Action<'SET_DATA', Record<string, any[]>>
|
|
10
14
|
type SET_LOADING = Action<'SET_LOADING', boolean>
|
|
11
15
|
type SET_PREVIEW = Action<'SET_PREVIEW', boolean>
|
|
12
16
|
type SET_FILTERED_DATA = Action<'SET_FILTERED_DATA', Object>
|
|
17
|
+
type SET_SHARED_FILTERS = Action<'SET_SHARED_FILTERS', SharedFilter[]>
|
|
13
18
|
type SET_TAB_SELECTED = Action<'SET_TAB_SELECTED', Tab>
|
|
14
19
|
type RENAME_DASHBOARD_TAB = Action<'RENAME_DASHBOARD_TAB', { current: string; new: string }>
|
|
15
20
|
type INITIALIZE_MULTIDASHBOARDS = Action<'INITIALIZE_MULTIDASHBOARDS', undefined>
|
|
@@ -19,10 +24,12 @@ type ADD_NEW_DASHBOARD = Action<'ADD_NEW_DASHBOARD', undefined>
|
|
|
19
24
|
type SAVE_CURRENT_CHANGES = Action<'SAVE_CURRENT_CHANGES', undefined>
|
|
20
25
|
type SWITCH_CONFIG = Action<'SWITCH_CONFIG', number>
|
|
21
26
|
type TOGGLE_ROW = Action<'TOGGLE_ROW', { rowIndex: number; colIndex: number }>
|
|
22
|
-
type UPDATE_VISUALIZATION = Action<'UPDATE_VISUALIZATION', { vizKey: string; configureData: Partial<
|
|
27
|
+
type UPDATE_VISUALIZATION = Action<'UPDATE_VISUALIZATION', { vizKey: string; configureData: Partial<AnyVisualization> }>
|
|
23
28
|
type UPDATE_ROW = Action<'UPDATE_ROW', { rowIndex: number; rowData: Partial<ConfigRow> }>
|
|
24
29
|
|
|
25
30
|
type DashboardActions =
|
|
31
|
+
| ADD_FOOTNOTE
|
|
32
|
+
| APPLY_CONFIG
|
|
26
33
|
| ADD_NEW_DASHBOARD
|
|
27
34
|
| SET_CONFIG
|
|
28
35
|
| UPDATE_CONFIG
|
|
@@ -34,6 +41,7 @@ type DashboardActions =
|
|
|
34
41
|
| SET_LOADING
|
|
35
42
|
| SET_PREVIEW
|
|
36
43
|
| SET_FILTERED_DATA
|
|
44
|
+
| SET_SHARED_FILTERS
|
|
37
45
|
| SET_TAB_SELECTED
|
|
38
46
|
| SWITCH_CONFIG
|
|
39
47
|
| INITIALIZE_MULTIDASHBOARDS
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
2
|
import { getUpdateConfig } from '../helpers/getUpdateConfig'
|
|
3
|
-
import {
|
|
3
|
+
import { MultiDashboardConfig } from '../types/MultiDashboard'
|
|
4
4
|
import DashboardActions from './dashboard.actions'
|
|
5
5
|
import { devToolsWrapper } from '@cdc/core/helpers/withDevTools'
|
|
6
6
|
import { Tab } from '../types/Tab'
|
|
7
7
|
import { DashboardConfig } from '../types/DashboardConfig'
|
|
8
8
|
import { ConfigRow } from '../types/ConfigRow'
|
|
9
|
+
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
10
|
+
import { initialState } from '../DashboardContext'
|
|
9
11
|
|
|
10
12
|
type BlankMultiConfig = {
|
|
11
13
|
dashboard: Partial<DashboardConfig>
|
|
@@ -28,7 +30,7 @@ const createBlankDashboard: () => BlankMultiConfig = () => ({
|
|
|
28
30
|
|
|
29
31
|
export type DashboardState = {
|
|
30
32
|
config: MultiDashboardConfig
|
|
31
|
-
data:
|
|
33
|
+
data: Record<string, any[]>
|
|
32
34
|
filteredData: Object
|
|
33
35
|
loading: boolean
|
|
34
36
|
preview: boolean
|
|
@@ -37,6 +39,11 @@ export type DashboardState = {
|
|
|
37
39
|
|
|
38
40
|
const reducer = (state: DashboardState, action: DashboardActions): DashboardState => {
|
|
39
41
|
switch (action.type) {
|
|
42
|
+
case 'ADD_FOOTNOTE': {
|
|
43
|
+
const { id, rowIndex, config } = action.payload
|
|
44
|
+
const newRows = state.config.rows.map((row, i) => (i === rowIndex ? { ...row, footnotesId: id } : row))
|
|
45
|
+
return { ...state, config: { ...state.config, rows: newRows, visualizations: { ...state.config.visualizations, [id]: config } } }
|
|
46
|
+
}
|
|
40
47
|
case 'ADD_NEW_DASHBOARD': {
|
|
41
48
|
const currentMultiDashboards = state.config.multiDashboards
|
|
42
49
|
const label = 'New Dashboard ' + (currentMultiDashboards.length + 1)
|
|
@@ -47,8 +54,21 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
47
54
|
const [config, filteredData] = getUpdateConfig(state)(...action.payload)
|
|
48
55
|
return { ...state, config, filteredData }
|
|
49
56
|
}
|
|
57
|
+
case 'APPLY_CONFIG': {
|
|
58
|
+
// using advanced editor. Wipe all existing data and apply new config
|
|
59
|
+
const [config, filteredData] = getUpdateConfig(state)(...action.payload)
|
|
60
|
+
// get the default data state
|
|
61
|
+
const data = [...Object.values(config.visualizations), ...config.rows]
|
|
62
|
+
.map(viz => viz.dataKey)
|
|
63
|
+
.reduce((acc, key) => {
|
|
64
|
+
const data = state.data[key] || state.config.datasets[key]?.data
|
|
65
|
+
if (data) acc[key] = data
|
|
66
|
+
return acc
|
|
67
|
+
}, {})
|
|
68
|
+
return { ...initialState, config, filteredData, data }
|
|
69
|
+
}
|
|
50
70
|
case 'SET_CONFIG': {
|
|
51
|
-
return { ...state, config: action.payload }
|
|
71
|
+
return { ...state, config: { ...state.config, ...action.payload } }
|
|
52
72
|
}
|
|
53
73
|
case 'SET_DATA': {
|
|
54
74
|
return { ...state, data: action.payload }
|
|
@@ -62,6 +82,11 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
62
82
|
case 'SET_PREVIEW': {
|
|
63
83
|
return { ...state, preview: action.payload }
|
|
64
84
|
}
|
|
85
|
+
case 'SET_SHARED_FILTERS': {
|
|
86
|
+
const newSharedFilters = action.payload
|
|
87
|
+
const newDashboardConfig = { ...state.config.dashboard, sharedFilters: newSharedFilters }
|
|
88
|
+
return { ...state, config: { ...state.config, dashboard: newDashboardConfig } }
|
|
89
|
+
}
|
|
65
90
|
case 'SET_TAB_SELECTED': {
|
|
66
91
|
return { ...state, tabSelected: action.payload }
|
|
67
92
|
}
|
|
@@ -124,7 +149,8 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
124
149
|
}
|
|
125
150
|
case 'UPDATE_VISUALIZATION': {
|
|
126
151
|
const { vizKey, configureData } = action.payload
|
|
127
|
-
|
|
152
|
+
const updatedViz = { ...state.config.visualizations[vizKey], ...configureData } as AnyVisualization
|
|
153
|
+
return { ...state, config: { ...state.config, visualizations: { ...state.config.visualizations, [vizKey]: updatedViz } } }
|
|
128
154
|
}
|
|
129
155
|
case 'UPDATE_ROW': {
|
|
130
156
|
const { rowIndex, rowData } = action.payload
|
package/src/types/APIFilter.ts
CHANGED
package/src/types/ConfigRow.ts
CHANGED
|
@@ -10,8 +10,10 @@ type Col = {
|
|
|
10
10
|
|
|
11
11
|
export type ConfigRow = {
|
|
12
12
|
columns: Col[]
|
|
13
|
+
expandCollapseAllButtons: boolean
|
|
13
14
|
uuid?: string | number
|
|
14
15
|
toggle?: boolean
|
|
15
16
|
equalHeight?: boolean
|
|
16
17
|
multiVizColumn?: string
|
|
18
|
+
footnotesId?: string // id for the footnotes in the vizConfig section
|
|
17
19
|
} & ConfigureData
|
package/src/types/Dashboard.ts
CHANGED
|
@@ -2,21 +2,19 @@ import { Series } from '@cdc/core/types/Series'
|
|
|
2
2
|
import { Runtime } from '@cdc/core/types/Runtime'
|
|
3
3
|
import { DataSet } from './DataSet'
|
|
4
4
|
import { ConfigRow } from './ConfigRow'
|
|
5
|
-
import {
|
|
5
|
+
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
6
6
|
import { Table } from '@cdc/core/types/Table'
|
|
7
|
-
import { FilterBehavior } from '@cdc/core/types/FilterBehavior'
|
|
8
7
|
import { Dashboard } from './Dashboard'
|
|
9
8
|
|
|
10
9
|
export type DashboardConfig = DataSet & {
|
|
11
10
|
dashboard: Dashboard
|
|
12
11
|
confidenceKeys: Record<string, any>
|
|
13
|
-
visualizations: Record<string,
|
|
12
|
+
visualizations: Record<string, AnyVisualization>
|
|
14
13
|
series: Series
|
|
15
14
|
datasets: Record<string, DataSet>
|
|
16
15
|
dataFileName: string
|
|
17
16
|
table: Table
|
|
18
17
|
rows: ConfigRow[]
|
|
19
|
-
filterBehavior: FilterBehavior
|
|
20
18
|
runtime: Runtime
|
|
21
19
|
downloadImageButton: boolean
|
|
22
20
|
downloadPdfButton: boolean
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
2
2
|
import { Dashboard } from './Dashboard'
|
|
3
3
|
import { DashboardConfig } from './DashboardConfig'
|
|
4
4
|
import { ConfigRow } from './ConfigRow'
|
|
5
5
|
|
|
6
|
-
export type MultiDashboard = { dashboard: Dashboard; rows: ConfigRow[]; visualizations: Record<string,
|
|
6
|
+
export type MultiDashboard = { dashboard: Dashboard; rows: ConfigRow[]; visualizations: Record<string, AnyVisualization>; label: string }
|
|
7
7
|
|
|
8
8
|
export type MultiDashboardConfig = DashboardConfig & {
|
|
9
9
|
multiDashboards?: MultiDashboard[]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { FilterBase } from '@cdc/core/types/VizFilter'
|
|
1
2
|
import { APIFilter } from './APIFilter'
|
|
2
|
-
export type SharedFilter = {
|
|
3
|
+
export type SharedFilter = FilterBase & {
|
|
3
4
|
type?: 'urlfilter' | 'datafilter' | ''
|
|
4
5
|
fileName?: string
|
|
5
6
|
filterBy?: 'Query String' | 'File Name'
|
|
@@ -9,15 +10,11 @@ export type SharedFilter = {
|
|
|
9
10
|
queuedActive?: string
|
|
10
11
|
usedBy?: (string | number)[] // if number used by whole row, else used by specific viz
|
|
11
12
|
parents?: string[]
|
|
12
|
-
pivot?: string
|
|
13
13
|
setBy?: string
|
|
14
14
|
selectLimit?: number
|
|
15
|
-
columnName?: string
|
|
16
15
|
resetLabel?: string
|
|
17
|
-
showDropdown?: boolean
|
|
18
16
|
labels?: Record<string, any>
|
|
19
17
|
key: string
|
|
20
|
-
values?: string[]
|
|
21
18
|
apiFilter?: APIFilter
|
|
22
19
|
datasetKey?: string
|
|
23
20
|
tier?: number
|
package/src/types/Tab.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type Tab = 'Dashboard Description' | '
|
|
1
|
+
export type Tab = 'Dashboard Description' | 'Data Table Settings' | 'Dashboard Preview'
|