@cdc/core 4.24.4 → 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/components/AdvancedEditor/AdvancedEditor.tsx +93 -0
- package/components/AdvancedEditor/advanced-editor-styles.css +3 -0
- package/components/AdvancedEditor/index.ts +1 -0
- package/components/DataTable/DataTable.tsx +33 -15
- package/components/DataTable/DataTableStandAlone.tsx +30 -4
- package/components/DataTable/components/ChartHeader.tsx +3 -2
- package/components/DataTable/components/DataTableEditorPanel.tsx +23 -6
- package/components/DataTable/components/ExpandCollapse.tsx +1 -1
- package/components/DataTable/helpers/chartCellMatrix.tsx +5 -10
- package/components/DataTable/helpers/getChartCellValue.ts +26 -2
- package/components/DataTable/helpers/getDataSeriesColumns.ts +40 -16
- package/components/DataTable/helpers/getRowType.ts +6 -0
- package/components/DataTable/helpers/getSeriesName.ts +2 -1
- package/components/DataTable/helpers/{customColumns.ts → removeNullColumns.ts} +3 -3
- package/components/DataTable/types/TableConfig.ts +12 -22
- package/components/EditorPanel/ColumnsEditor.tsx +278 -262
- package/components/EditorPanel/DataTableEditor.tsx +159 -60
- package/components/EditorPanel/FieldSetWrapper.tsx +51 -0
- package/components/EditorPanel/FootnotesEditor.tsx +77 -0
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +227 -0
- package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +54 -0
- package/components/EditorPanel/VizFilterEditor/index.ts +1 -0
- package/components/EditorWrapper/EditorWrapper.tsx +47 -0
- package/components/EditorWrapper/editor-wrapper.style.css +14 -0
- package/components/EditorWrapper/index.ts +1 -0
- package/components/{Filters.jsx → Filters.tsx} +102 -70
- package/components/Footnotes/Footnotes.tsx +25 -0
- package/components/Footnotes/FootnotesStandAlone.tsx +45 -0
- package/components/Footnotes/footnotes.css +5 -0
- package/components/Footnotes/index.ts +1 -0
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +8 -4
- package/components/Layout/components/Visualization/index.tsx +14 -5
- package/components/MediaControls.jsx +1 -1
- package/components/MultiSelect/MultiSelect.tsx +36 -9
- package/components/MultiSelect/multiselect.styles.css +0 -3
- package/components/_stories/DataTable.stories.tsx +1 -2
- package/components/_stories/EditorPanel.stories.tsx +1 -0
- package/components/_stories/Footnotes.stories.tsx +17 -0
- package/components/inputs/InputSelect.tsx +17 -6
- package/components/ui/Icon.tsx +1 -2
- package/helpers/DataTransform.ts +9 -32
- package/helpers/addValuesToFilters.ts +56 -0
- package/helpers/cove/accessibility.ts +1 -0
- package/helpers/cove/fontSettings.ts +2 -0
- package/helpers/coveUpdateWorker.ts +11 -2
- package/helpers/filterVizData.ts +30 -0
- package/helpers/footnoteSymbols.ts +11 -0
- package/helpers/formatConfigBeforeSave.ts +90 -0
- package/helpers/gatherQueryParams.ts +14 -7
- package/helpers/lineChartHelpers.js +2 -1
- package/helpers/pivotData.ts +18 -0
- package/helpers/queryStringUtils.ts +29 -0
- package/helpers/tests/updateFieldFactory.test.ts +1 -0
- package/helpers/updateFieldFactory.ts +1 -1
- package/helpers/useDataVizClasses.js +0 -4
- package/helpers/ver/4.23.4.ts +27 -0
- package/helpers/ver/4.24.5.ts +32 -0
- package/helpers/ver/4.24.7.ts +92 -0
- package/package.json +6 -4
- package/styles/_button-section.scss +6 -1
- package/styles/_data-table.scss +0 -1
- package/styles/_reset.scss +7 -6
- package/styles/base.scss +4 -0
- package/styles/v2/themes/_color-definitions.scss +1 -0
- package/types/Annotation.ts +46 -0
- package/types/Column.ts +1 -0
- package/types/ConfigureData.ts +1 -1
- package/types/Footnotes.ts +17 -0
- package/types/General.ts +5 -0
- package/types/Legend.ts +1 -0
- package/types/MarkupInclude.ts +26 -0
- package/types/Runtime.ts +3 -7
- package/types/Series.ts +1 -1
- package/types/Table.ts +21 -14
- package/types/Visualization.ts +40 -11
- package/types/VizFilter.ts +24 -0
- package/LICENSE +0 -201
- package/components/AdvancedEditor.jsx +0 -74
- package/helpers/queryStringUtils.js +0 -26
- package/types/BaseVisualizationType.ts +0 -1
|
@@ -51,13 +51,12 @@ export const CityState: Story = {
|
|
|
51
51
|
|
|
52
52
|
export const Grouped: Story = {
|
|
53
53
|
args: {
|
|
54
|
-
config: Example_1,
|
|
54
|
+
config: { ...Example_1, table: { ...Example_1.table, groupBy: 'TimeZone' } },
|
|
55
55
|
dataConfig: Example_1.datasets[datasetKey],
|
|
56
56
|
rawData: Example_1.datasets[datasetKey].data,
|
|
57
57
|
runtimeData: Example_1.datasets[datasetKey].data,
|
|
58
58
|
expandDataTable: true,
|
|
59
59
|
tableTitle: 'COVE DataTable',
|
|
60
|
-
groupBy: 'TimeZone',
|
|
61
60
|
viewport: 'lg',
|
|
62
61
|
tabbingId: datasetKey
|
|
63
62
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import Footnotes from '../Footnotes'
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Footnotes> = {
|
|
5
|
+
title: 'Components/Organisms/Footnotes',
|
|
6
|
+
component: Footnotes
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default meta
|
|
10
|
+
|
|
11
|
+
type Story = StoryObj<typeof Footnotes>
|
|
12
|
+
|
|
13
|
+
export const Primary: Story = {
|
|
14
|
+
args: {
|
|
15
|
+
footnotes: [{ symbol: '*', text: 'This is a footnote' }, { symbol: '†', text: 'This is another footnote' }, { text: 'This is a third footnote' }]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -3,7 +3,7 @@ import '../../styles/v2/components/input/index.scss'
|
|
|
3
3
|
interface InputProps {
|
|
4
4
|
label?
|
|
5
5
|
value?
|
|
6
|
-
options: string[] |
|
|
6
|
+
options: string[] | Record<string, any> | [any, string][]
|
|
7
7
|
fieldName
|
|
8
8
|
section?
|
|
9
9
|
subsection?
|
|
@@ -17,11 +17,22 @@ const InputSelect = ({ label, value, options, fieldName, section = null, subsect
|
|
|
17
17
|
|
|
18
18
|
if (Array.isArray(options)) {
|
|
19
19
|
//Handle basic array
|
|
20
|
-
optionsJsx = options.map(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
optionsJsx = options.map(option => {
|
|
21
|
+
if (typeof option === 'string') {
|
|
22
|
+
return (
|
|
23
|
+
<option value={option} key={option}>
|
|
24
|
+
{option}
|
|
25
|
+
</option>
|
|
26
|
+
)
|
|
27
|
+
} else {
|
|
28
|
+
const [value, name] = option
|
|
29
|
+
return (
|
|
30
|
+
<option value={value} key={name}>
|
|
31
|
+
{name}
|
|
32
|
+
</option>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
25
36
|
} else {
|
|
26
37
|
//Handle object with value/name pairs
|
|
27
38
|
optionsJsx = []
|
package/components/ui/Icon.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
1
|
import PropTypes from 'prop-types'
|
|
3
2
|
|
|
4
3
|
import iconCaretUp from '../../assets/icon-caret-up.svg'
|
|
@@ -68,7 +67,7 @@ const iconHash = {
|
|
|
68
67
|
plus: iconPlus,
|
|
69
68
|
minus: iconMinus,
|
|
70
69
|
'filtered-text': iconText,
|
|
71
|
-
|
|
70
|
+
dashboardFilters: iconDropdowns,
|
|
72
71
|
table: iconTable,
|
|
73
72
|
sankey: iconSankey,
|
|
74
73
|
rotateLeft: iconRotateLeft,
|
package/helpers/DataTransform.ts
CHANGED
|
@@ -134,27 +134,27 @@ export class DataTransform {
|
|
|
134
134
|
let standardized: string[] = []
|
|
135
135
|
|
|
136
136
|
data.forEach(row => {
|
|
137
|
-
let uniqueKey = row[description.xKey]
|
|
137
|
+
let uniqueKey = row[description.xKey]
|
|
138
138
|
Object.keys(row).forEach(key => {
|
|
139
|
-
if(key !== description.xKey && key !== description.seriesKey && description.valueKeysTallSupport.indexOf(key) === -1 && (!description.ignoredKeys || description.ignoredKeys.indexOf(key) === -1)){
|
|
140
|
-
uniqueKey +=
|
|
139
|
+
if (key !== description.xKey && key !== description.seriesKey && description.valueKeysTallSupport.indexOf(key) === -1 && (!description.ignoredKeys || description.ignoredKeys.indexOf(key) === -1)) {
|
|
140
|
+
uniqueKey += '|' + row[key]
|
|
141
141
|
}
|
|
142
142
|
})
|
|
143
143
|
|
|
144
|
-
if(!standardizedMapped[uniqueKey]){
|
|
145
|
-
standardizedMapped[uniqueKey] = {[description.xKey]: row[description.xKey]}
|
|
144
|
+
if (!standardizedMapped[uniqueKey]) {
|
|
145
|
+
standardizedMapped[uniqueKey] = { [description.xKey]: row[description.xKey] }
|
|
146
146
|
}
|
|
147
147
|
Object.keys(row).forEach(key => {
|
|
148
|
-
if(key !== description.xKey && key !== description.seriesKey && description.valueKeysTallSupport.indexOf(key) === -1 && (!description.ignoredKeys || description.ignoredKeys.indexOf(key) === -1)){
|
|
149
|
-
standardizedMapped[uniqueKey][key] = row[key]
|
|
148
|
+
if (key !== description.xKey && key !== description.seriesKey && description.valueKeysTallSupport.indexOf(key) === -1 && (!description.ignoredKeys || description.ignoredKeys.indexOf(key) === -1)) {
|
|
149
|
+
standardizedMapped[uniqueKey][key] = row[key]
|
|
150
150
|
}
|
|
151
151
|
})
|
|
152
152
|
description.valueKeysTallSupport.forEach(valueKey => {
|
|
153
|
-
standardizedMapped[uniqueKey][row[description.seriesKey] + '-' + valueKey] = row[valueKey]
|
|
153
|
+
standardizedMapped[uniqueKey][row[description.seriesKey] + '-' + valueKey] = row[valueKey]
|
|
154
154
|
})
|
|
155
155
|
})
|
|
156
156
|
|
|
157
|
-
standardized = Object.keys(standardizedMapped).map(key => standardizedMapped[key])
|
|
157
|
+
standardized = Object.keys(standardizedMapped).map(key => standardizedMapped[key])
|
|
158
158
|
|
|
159
159
|
return standardized
|
|
160
160
|
} else if (description.valueKeys !== undefined) {
|
|
@@ -291,29 +291,6 @@ export class DataTransform {
|
|
|
291
291
|
return cleanedupData
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
applySuppression = (data, suppressedData) => {
|
|
295
|
-
if (!suppressedData || suppressedData.length === 0) return data
|
|
296
|
-
|
|
297
|
-
// Create a new array to store the result without modifying the original data
|
|
298
|
-
const result = data.map(item => {
|
|
299
|
-
// Create a new object to store the updated item without modifying the original item
|
|
300
|
-
const newItem = { ...item }
|
|
301
|
-
|
|
302
|
-
// For each suppressedData item
|
|
303
|
-
for (let i = 0; i < suppressedData.length; i++) {
|
|
304
|
-
// If the object contains the column and its value matches the suppressed one
|
|
305
|
-
if (newItem[suppressedData[i].column] && newItem[suppressedData[i].column] === suppressedData[i].value) {
|
|
306
|
-
// Replace the value with the label in the new object
|
|
307
|
-
newItem[suppressedData[i].column] = suppressedData[i].label
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return newItem
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
return result
|
|
315
|
-
}
|
|
316
|
-
|
|
317
294
|
// clean out %, $, commas from numbers when needing to do sorting!
|
|
318
295
|
cleanDataPoint(data, testing = false) {
|
|
319
296
|
if (testing) console.log('clean', data)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
|
|
3
|
+
|
|
4
|
+
type Filter = {
|
|
5
|
+
columnName: string
|
|
6
|
+
values: string[]
|
|
7
|
+
filterStyle?: string
|
|
8
|
+
active?: string | string[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Gets filter values from dataset
|
|
12
|
+
const generateValuesForFilter = (columnName, data: any[] | Record<string, any[]>) => {
|
|
13
|
+
const values: string[] = []
|
|
14
|
+
|
|
15
|
+
if (Array.isArray(data)) {
|
|
16
|
+
data.forEach(row => {
|
|
17
|
+
const value = row[columnName]
|
|
18
|
+
if (!values.includes(value)) {
|
|
19
|
+
values.push(value)
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
} else {
|
|
23
|
+
// data is a dataset this loops through ALL datasets to find matching values
|
|
24
|
+
// not sure if this is desired behavior
|
|
25
|
+
Object.values(data).forEach((rows: any[]) => {
|
|
26
|
+
rows.forEach(row => {
|
|
27
|
+
const value = row[columnName]
|
|
28
|
+
if (!values.includes(value)) {
|
|
29
|
+
values.push(value)
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return values
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const addValuesToFilters = <T>(filters: Filter[], data: any[] | Record<string, any[]>): Array<T> => {
|
|
39
|
+
return filters?.map(filter => {
|
|
40
|
+
const filterCopy = _.cloneDeep(filter)
|
|
41
|
+
|
|
42
|
+
const filterValues = generateValuesForFilter(filter.columnName, data)
|
|
43
|
+
filterCopy.values = filterValues
|
|
44
|
+
if (filterValues.length > 0) {
|
|
45
|
+
const defaultValues = filterCopy.filterStyle === 'multi-select' ? filterCopy.values : filterCopy.values[0]
|
|
46
|
+
|
|
47
|
+
const queryStringFilterValue = getQueryStringFilterValue(filterCopy)
|
|
48
|
+
if (queryStringFilterValue) {
|
|
49
|
+
filterCopy.active = queryStringFilterValue
|
|
50
|
+
} else {
|
|
51
|
+
filterCopy.active = filterCopy.active || defaultValues
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return filterCopy
|
|
55
|
+
}) as Array<T>
|
|
56
|
+
}
|
|
@@ -9,6 +9,7 @@ import chroma from 'chroma-js'
|
|
|
9
9
|
export const WCAG_CONTRAST_RATIO = 4.5
|
|
10
10
|
|
|
11
11
|
export const getContrastColor = (textColor: string, bgColor: string) => {
|
|
12
|
+
if (!bgColor) return
|
|
12
13
|
if (chroma.contrast(textColor, bgColor) < WCAG_CONTRAST_RATIO) {
|
|
13
14
|
switch (textColor) {
|
|
14
15
|
case '#FFF':
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
// If config key names or position in the config have been changed with a version change,
|
|
2
2
|
// process those config entries and format old values into new
|
|
3
|
+
import update_4_24_4 from './ver/4.23.4'
|
|
3
4
|
import update_4_24_3 from './ver/4.24.3'
|
|
5
|
+
import update_4_24_5 from './ver/4.24.5'
|
|
6
|
+
import update_4_24_7 from './ver/4.24.7'
|
|
4
7
|
|
|
5
|
-
// 4.23.6 ------------------------------------------------------
|
|
6
8
|
export const coveUpdateWorker = config => {
|
|
9
|
+
if (config.multiDashboards) {
|
|
10
|
+
config.multiDashboards.forEach((dashboard, index) => {
|
|
11
|
+
config.multiDashboards[index] = coveUpdateWorker(dashboard)
|
|
12
|
+
})
|
|
13
|
+
}
|
|
7
14
|
let genConfig = config
|
|
8
15
|
|
|
9
|
-
// v4.24.3
|
|
10
16
|
genConfig = update_4_24_3(genConfig)
|
|
17
|
+
genConfig = update_4_24_4(genConfig)
|
|
18
|
+
genConfig = update_4_24_5(genConfig)
|
|
19
|
+
genConfig = update_4_24_7(genConfig)
|
|
11
20
|
|
|
12
21
|
return genConfig
|
|
13
22
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const filterVizData = (filters, data) => {
|
|
2
|
+
if (!data) {
|
|
3
|
+
console.warn('COVE: No data to filter')
|
|
4
|
+
return []
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
if (!filters) return data
|
|
8
|
+
const filteredData: any[] = []
|
|
9
|
+
|
|
10
|
+
data?.forEach(row => {
|
|
11
|
+
let add = true
|
|
12
|
+
filters
|
|
13
|
+
.filter(filter => filter.type !== 'url')
|
|
14
|
+
.forEach(filter => {
|
|
15
|
+
const value = row[filter.columnName]
|
|
16
|
+
if (filter.active === undefined) return
|
|
17
|
+
if (Array.isArray(filter.active)) {
|
|
18
|
+
if (!filter.active.includes(value)) {
|
|
19
|
+
add = false
|
|
20
|
+
}
|
|
21
|
+
} else if (value != filter.active) {
|
|
22
|
+
add = false
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
if (add) filteredData.push(row)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return filteredData
|
|
30
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
|
|
3
|
+
const symbols = [
|
|
4
|
+
['*', 'Asterisk'],
|
|
5
|
+
['†', 'Dagger'],
|
|
6
|
+
['§', 'Section Symbol'],
|
|
7
|
+
['¶', 'Paragraph Symbol']
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
export const footnotesSymbols = symbols.concat(symbols.map(([symbol, name]) => [symbol + symbol, 'Double ' + name]))
|
|
11
|
+
export const adjustedSymbols = _.fromPairs(_.map(footnotesSymbols, ([symbol, name]) => [name, symbol]))
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import Footnotes from '@cdc/core/types/Footnotes'
|
|
2
|
+
import { Visualization } from '@cdc/core/types/Visualization'
|
|
3
|
+
import { DashboardConfig } from '@cdc/dashboard/src/types/DashboardConfig'
|
|
4
|
+
import _ from 'lodash'
|
|
5
|
+
|
|
6
|
+
const cleanDashboardFootnotes = (config: DashboardConfig) => {
|
|
7
|
+
// strip any blank footnote visualizations
|
|
8
|
+
const footnoteIds: string[] = []
|
|
9
|
+
|
|
10
|
+
if (config.rows) {
|
|
11
|
+
config.rows.forEach(row => {
|
|
12
|
+
if (row.footnotesId) {
|
|
13
|
+
const { dataKey, staticFootnotes } = config.visualizations[row.footnotesId] as Footnotes
|
|
14
|
+
if (!dataKey && !staticFootnotes?.length) {
|
|
15
|
+
delete config.visualizations[row.footnotesId]
|
|
16
|
+
} else {
|
|
17
|
+
footnoteIds.push(row.footnotesId)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (config.visualizations) {
|
|
24
|
+
Object.keys(config.visualizations).forEach(vizKey => {
|
|
25
|
+
const viz: Visualization = config.visualizations[vizKey]
|
|
26
|
+
if (viz.type === 'footnotes' && !footnoteIds.includes(vizKey)) {
|
|
27
|
+
// if footnote isn't being used by any rows, remove it
|
|
28
|
+
delete config.visualizations[vizKey]
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cleanDashboardData = (config: DashboardConfig) => {
|
|
35
|
+
if (config.datasets) {
|
|
36
|
+
Object.keys(config.datasets).forEach(datasetKey => {
|
|
37
|
+
delete config.datasets[datasetKey].formattedData
|
|
38
|
+
if (config.datasets[datasetKey].dataUrl) {
|
|
39
|
+
delete config.datasets[datasetKey].data
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
if (config.visualizations) {
|
|
44
|
+
Object.keys(config.visualizations).forEach(vizKey => {
|
|
45
|
+
config.visualizations[vizKey] = _.omit(config.visualizations[vizKey], ['runtime', 'formattedData', 'data'])
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
if (config.rows) {
|
|
49
|
+
config.rows.forEach((row, i) => {
|
|
50
|
+
if (row.dataKey) {
|
|
51
|
+
config.rows[i] = _.omit(row, ['data', 'formattedData'])
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const cleanSharedFilters = (config: DashboardConfig) => {
|
|
58
|
+
if (config.dashboard?.sharedFilters) {
|
|
59
|
+
config.dashboard.sharedFilters.forEach((filter, index) => {
|
|
60
|
+
delete config.dashboard.sharedFilters[index].active
|
|
61
|
+
if (filter.type === 'urlfilter') {
|
|
62
|
+
delete config.dashboard.sharedFilters[index].values
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const formatConfigBeforeSave = configToStrip => {
|
|
69
|
+
let strippedConfig = _.cloneDeep(configToStrip)
|
|
70
|
+
if (strippedConfig.type === 'dashboard') {
|
|
71
|
+
if (strippedConfig.multiDashboards) {
|
|
72
|
+
strippedConfig.multiDashboards.forEach((multiDashboard, i) => {
|
|
73
|
+
cleanDashboardData(strippedConfig.multiDashboards[i])
|
|
74
|
+
cleanSharedFilters(strippedConfig.multiDashboards[i])
|
|
75
|
+
cleanDashboardFootnotes(strippedConfig.multiDashboards[i])
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
cleanDashboardData(strippedConfig)
|
|
79
|
+
cleanSharedFilters(strippedConfig)
|
|
80
|
+
cleanDashboardFootnotes(strippedConfig)
|
|
81
|
+
} else {
|
|
82
|
+
delete strippedConfig.runtime
|
|
83
|
+
delete strippedConfig.formattedData
|
|
84
|
+
if (strippedConfig.dataUrl) {
|
|
85
|
+
delete strippedConfig.data
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return strippedConfig
|
|
90
|
+
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
|
|
3
|
+
export const gatherQueryParams = (baseEndpoint: string, params: { key: string; value: string }[]) => {
|
|
4
|
+
const baseEndpointHasQueryParams = baseEndpoint.includes('?')
|
|
5
|
+
return params
|
|
6
|
+
.filter(({ value }) => value !== '')
|
|
7
|
+
.map(({ key, value }, i) => {
|
|
8
|
+
const leadingCharacter = i === 0 && !baseEndpointHasQueryParams ? '?' : '&'
|
|
9
|
+
const isStatementParam = key.match(/\$.*/)
|
|
10
|
+
if (!_.isNaN(parseInt(value)) || isStatementParam) return leadingCharacter + key + '=' + value
|
|
11
|
+
return leadingCharacter + key + '=' + `"${value}"`
|
|
12
|
+
})
|
|
13
|
+
.join('')
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
|
|
3
|
+
/** columnName is the column you'd like to select data values from to show as column headers.
|
|
4
|
+
* Pivot is the value column who's data you'd like to show under those respective columns*/
|
|
5
|
+
export const pivotData = (data: Record<string, any>[], columnName: string, pivot: string) => {
|
|
6
|
+
const grouped = _.groupBy(data, val => val[columnName])
|
|
7
|
+
const newData = []
|
|
8
|
+
for (const key in grouped) {
|
|
9
|
+
const group = grouped[key]
|
|
10
|
+
group.forEach((val, index) => {
|
|
11
|
+
const row = newData[index] || {}
|
|
12
|
+
row[key] = val[pivot]
|
|
13
|
+
const toAdd = _.omit(val, [columnName, pivot])
|
|
14
|
+
newData[index] = { ...toAdd, ...row }
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
return newData
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function getQueryStringFilterValue(filter) {
|
|
2
|
+
const urlParams = new URLSearchParams(window.location.search)
|
|
3
|
+
if (filter.setByQueryParameter) {
|
|
4
|
+
// Only check the query string if the filter is supposed to be set by QS param
|
|
5
|
+
const filterValue = urlParams.get(filter.setByQueryParameter)
|
|
6
|
+
if (filterValue && filter.values) {
|
|
7
|
+
for (let i = 0; i < filter.values.length; i++) {
|
|
8
|
+
if (filter.values[i] && filter.values[i].toLowerCase() === filterValue.toLowerCase()) {
|
|
9
|
+
return filter.values[i]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getQueryParams() {
|
|
17
|
+
const queryParams = {}
|
|
18
|
+
for (const [key, value] of Array.from(new URLSearchParams(window.location.search).entries())) {
|
|
19
|
+
queryParams[key] = value
|
|
20
|
+
}
|
|
21
|
+
return queryParams
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function updateQueryString(queryParams) {
|
|
25
|
+
const updateUrl = `${window.location.origin}${window.location.pathname}?${Object.keys(queryParams)
|
|
26
|
+
.map(queryParam => `${queryParam}=${encodeURIComponent(queryParams[queryParam])}`)
|
|
27
|
+
.join('&')}`
|
|
28
|
+
window.history.pushState({ path: updateUrl }, '', updateUrl)
|
|
29
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UpdateFieldFunc } from '../types/UpdateFieldFunc'
|
|
2
2
|
|
|
3
3
|
export const updateFieldFactory =
|
|
4
|
-
(config, updateConfig, legacy = false): UpdateFieldFunc<
|
|
4
|
+
<T>(config, updateConfig, legacy = false): UpdateFieldFunc<T> =>
|
|
5
5
|
(section, subsection, fieldName, newValue) => {
|
|
6
6
|
// Top level
|
|
7
7
|
if (null === section && null === subsection) {
|
|
@@ -18,10 +18,6 @@ export default function useDataVizClasses(config, viewport = null) {
|
|
|
18
18
|
if (title && showTitle) contentClasses.push('component--has-title')
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
if (config.type === 'markup-include') {
|
|
22
|
-
contentClasses = contentClasses.filter(item => item !== 'cove-component__content')
|
|
23
|
-
}
|
|
24
|
-
|
|
25
21
|
config.showTitle && contentClasses.push('component--has-title')
|
|
26
22
|
config.title && config.visualizationType !== 'chart' && config.visualizationType !== 'Spark Line' && contentClasses.push('component--has-title')
|
|
27
23
|
config.subtext && innerContainerClasses.push('component--has-subtext')
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
|
|
3
|
+
const addFiltersToTables = config => {
|
|
4
|
+
if (config.type === 'dashboard') {
|
|
5
|
+
Object.keys(config.visualizations).forEach(vizKey => {
|
|
6
|
+
const viz = config.visualizations[vizKey]
|
|
7
|
+
if (viz.type === 'table') {
|
|
8
|
+
if (!viz.filters) {
|
|
9
|
+
viz.filters = []
|
|
10
|
+
viz.filterBehavior = 'Filter Change'
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const update_4_24_4 = config => {
|
|
18
|
+
const ver = '4.24.4'
|
|
19
|
+
|
|
20
|
+
const newConfig = _.cloneDeep(config)
|
|
21
|
+
addFiltersToTables(newConfig)
|
|
22
|
+
|
|
23
|
+
newConfig.version = ver
|
|
24
|
+
return newConfig
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default update_4_24_4
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
|
|
3
|
+
const migrateMarkupInclude = newConfig => {
|
|
4
|
+
if (newConfig.type === 'markup-include') {
|
|
5
|
+
if (!newConfig.contentEditor) {
|
|
6
|
+
newConfig.contentEditor = {
|
|
7
|
+
title: newConfig.title,
|
|
8
|
+
showHeader: newConfig.showHeader,
|
|
9
|
+
srcUrl: newConfig.srcUrl
|
|
10
|
+
}
|
|
11
|
+
delete newConfig.title
|
|
12
|
+
delete newConfig.showHeader
|
|
13
|
+
delete newConfig.srcUrl
|
|
14
|
+
}
|
|
15
|
+
if (!newConfig.visual) {
|
|
16
|
+
newConfig.visual = {}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const update_4_24_4 = config => {
|
|
22
|
+
const ver = '4.24.4'
|
|
23
|
+
|
|
24
|
+
const newConfig = _.cloneDeep(config)
|
|
25
|
+
|
|
26
|
+
migrateMarkupInclude(newConfig)
|
|
27
|
+
|
|
28
|
+
newConfig.version = ver
|
|
29
|
+
return newConfig
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default update_4_24_4
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import { DashboardFilters } from '@cdc/dashboard/src/types/DashboardFilters'
|
|
3
|
+
import { MultiDashboardConfig } from '@cdc/dashboard/src/types/MultiDashboard'
|
|
4
|
+
import { AnyVisualization } from '../../types/Visualization'
|
|
5
|
+
|
|
6
|
+
export const dashboardFiltersMigrate = config => {
|
|
7
|
+
if (!config.dashboard) return config
|
|
8
|
+
const dashboardConfig = config as MultiDashboardConfig
|
|
9
|
+
const newVisualizations = {}
|
|
10
|
+
// autoload was removed from APIFilter type
|
|
11
|
+
const newSharedFilters = (dashboardConfig.dashboard.sharedFilters || []).map(sf => {
|
|
12
|
+
if (sf.apiFilter?.autoLoad !== undefined) {
|
|
13
|
+
delete sf.apiFilter.autoLoad
|
|
14
|
+
}
|
|
15
|
+
if (sf.apiFilter?.defaultValue !== undefined) {
|
|
16
|
+
delete sf.apiFilter.defaultValue
|
|
17
|
+
}
|
|
18
|
+
return sf
|
|
19
|
+
})
|
|
20
|
+
config.dashboard.sharedFilters = newSharedFilters
|
|
21
|
+
|
|
22
|
+
Object.keys(dashboardConfig.visualizations).forEach(vizKey => {
|
|
23
|
+
const viz = dashboardConfig.visualizations[vizKey] as DashboardFilters
|
|
24
|
+
// hide was removed from visualizations
|
|
25
|
+
if (viz.hide !== undefined) {
|
|
26
|
+
viz.sharedFilterIndexes = newSharedFilters.map((_sf, i) => i).filter(i => !viz.hide.includes(i))
|
|
27
|
+
viz.type = 'dashboardFilters'
|
|
28
|
+
if (viz.autoLoad) {
|
|
29
|
+
viz.filterBehavior = 'Filter Change'
|
|
30
|
+
} else {
|
|
31
|
+
viz.filterBehavior = 'Apply Button'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
delete viz.hide
|
|
35
|
+
}
|
|
36
|
+
// 'filter-dropdowns' was renamed to 'dashboardFilters' for clarity
|
|
37
|
+
if (viz.type === 'filter-dropdowns') viz.type = 'dashboardFilters'
|
|
38
|
+
if (viz.visualizationType === 'filter-dropdowns') viz.visualizationType = 'dashboardFilters'
|
|
39
|
+
newVisualizations[vizKey] = viz
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
if (config.dashboard.sharedFilters.length && !Object.values(newVisualizations).find((v: AnyVisualization) => v.type === 'dashboardFilters')) {
|
|
43
|
+
const newViz = {
|
|
44
|
+
type: 'dashboardFilters',
|
|
45
|
+
visualizationType: 'dashboardFilters',
|
|
46
|
+
sharedFilterIndexes: config.dashboard.sharedFilters.map((_sf, i) => i),
|
|
47
|
+
filterBehavior: config.filterBehavior || 'Filter Change'
|
|
48
|
+
}
|
|
49
|
+
const key = 'legacySharedFilters'
|
|
50
|
+
newVisualizations[key] = newViz
|
|
51
|
+
const newRow = {
|
|
52
|
+
columns: [
|
|
53
|
+
{
|
|
54
|
+
width: 12,
|
|
55
|
+
widget: key
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
config.rows = [newRow, ...config.rows]
|
|
60
|
+
}
|
|
61
|
+
// if there's no dashboardFilters visualization but there are sharedFilters create a visualization and update rows.
|
|
62
|
+
|
|
63
|
+
config.visualizations = newVisualizations
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const mapUpdates = newConfig => {
|
|
67
|
+
// When switching between old version of equal number, and the revised equal number opt in, roundToPlace needs to be set.
|
|
68
|
+
// There wasn't an initial value set for this, and legends would return NaN if it wasn't set. ie. 0 - NAN instead of 0 - 1
|
|
69
|
+
const equalNumberRoundingPatch = newConfig => {
|
|
70
|
+
if (newConfig.type === 'map') {
|
|
71
|
+
if (newConfig.columns.primary.roundToPlace === undefined) {
|
|
72
|
+
newConfig.columns.primary.roundToPlace = 0
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
equalNumberRoundingPatch(newConfig)
|
|
78
|
+
|
|
79
|
+
return newConfig
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const update_4_24_7 = config => {
|
|
83
|
+
const ver = '4.24.7'
|
|
84
|
+
|
|
85
|
+
const newConfig = _.cloneDeep(config)
|
|
86
|
+
|
|
87
|
+
mapUpdates(newConfig)
|
|
88
|
+
dashboardFiltersMigrate(newConfig)
|
|
89
|
+
newConfig.version = ver
|
|
90
|
+
return newConfig
|
|
91
|
+
}
|
|
92
|
+
export default update_4_24_7
|