@cdc/core 4.25.3-6 → 4.25.3
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 +11 -9
- package/components/DataTable/DataTable.tsx +34 -20
- package/components/DataTable/components/ChartHeader.tsx +1 -1
- package/components/DataTable/helpers/getChartCellValue.ts +11 -5
- package/components/DataTable/helpers/getDataSeriesColumns.ts +7 -3
- package/components/DataTable/helpers/mapCellMatrix.tsx +64 -33
- package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +80 -0
- package/components/EditorPanel/DataTableEditor.tsx +28 -18
- package/components/EditorPanel/Inputs.tsx +2 -1
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +23 -0
- package/components/Filters/Filters.tsx +20 -8
- package/components/Layout/components/Visualization/visualizations.scss +1 -1
- package/components/MediaControls.jsx +14 -7
- package/components/elements/Button.jsx +4 -2
- package/dist/cove-main.css +98 -151
- package/dist/cove-main.css.map +1 -1
- package/helpers/DataTransform.ts +2 -2
- package/helpers/addValuesToFilters.ts +1 -1
- package/helpers/coveUpdateWorker.ts +12 -7
- package/helpers/formatConfigBeforeSave.ts +30 -8
- package/helpers/isRightAlignedTableValue.js +5 -1
- package/helpers/isSolr.ts +13 -0
- package/helpers/labelHash.ts +21 -0
- package/helpers/pivotData.ts +14 -7
- package/helpers/tests/formatConfigBeforeSave.test.ts +68 -0
- package/helpers/tests/pivotData.test.ts +23 -19
- package/helpers/ver/4.25.3.ts +20 -0
- package/helpers/ver/tests/versionNeedsUpdate.test.ts +28 -0
- package/package.json +2 -2
- package/styles/_global-variables.scss +2 -1
- package/styles/_global.scss +18 -9
- package/styles/base.scss +42 -0
- package/styles/filters.scss +5 -11
- package/styles/v2/components/button.scss +48 -12
- package/styles/v2/themes/_color-definitions.scss +1 -4
- package/types/General.ts +0 -1
- package/types/Table.ts +2 -0
- package/helpers/isSolr.js +0 -13
|
@@ -35,20 +35,22 @@ export const AdvancedEditor = ({ loadConfig, config, convertStateToConfig, onExp
|
|
|
35
35
|
}, [config])
|
|
36
36
|
|
|
37
37
|
const typeLookup = {
|
|
38
|
-
chart: ['Charts', 'https://www.cdc.gov/
|
|
39
|
-
dashboard: ['Dashboard', 'https://www.cdc.gov/
|
|
40
|
-
map: ['Maps', 'https://www.cdc.gov/
|
|
41
|
-
'markup-include': [
|
|
42
|
-
'Markup Include',
|
|
43
|
-
'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/Markup-Include.html',
|
|
44
|
-
<MarkupIncludeIcon />
|
|
45
|
-
]
|
|
38
|
+
chart: ['Charts', 'https://www.cdc.gov/cove/index.html', <ChartIcon />],
|
|
39
|
+
dashboard: ['Dashboard', 'https://www.cdc.gov/cove/index.html', <ChartIcon />],
|
|
40
|
+
map: ['Maps', 'https://www.cdc.gov/cove/index.html', <MapIcon />],
|
|
41
|
+
'markup-include': ['Markup Include', 'https://www.cdc.gov/cove/index.html', <MarkupIncludeIcon />]
|
|
46
42
|
}
|
|
47
43
|
|
|
48
44
|
if (!config.type) return <></>
|
|
49
45
|
return (
|
|
50
46
|
<>
|
|
51
|
-
<a
|
|
47
|
+
<a
|
|
48
|
+
href={typeLookup[config.type][1]}
|
|
49
|
+
target='_blank'
|
|
50
|
+
rel='noopener noreferrer'
|
|
51
|
+
className='guidance-link'
|
|
52
|
+
style={{ cursor: 'pointer !important' }}
|
|
53
|
+
>
|
|
52
54
|
{typeLookup[config.type][2]}
|
|
53
55
|
<div>
|
|
54
56
|
<span className='heading-3'>Get Help with {typeLookup[config.type][0]}</span>
|
|
@@ -11,7 +11,7 @@ import BoxplotHeader from './components/BoxplotHeader'
|
|
|
11
11
|
import MapHeader from './components/MapHeader'
|
|
12
12
|
import SkipTo from '../elements/SkipTo'
|
|
13
13
|
import ExpandCollapse from './components/ExpandCollapse'
|
|
14
|
-
import mapCellMatrix from './helpers/mapCellMatrix'
|
|
14
|
+
import mapCellMatrix, { getMapRowData } from './helpers/mapCellMatrix'
|
|
15
15
|
import Table from '../Table'
|
|
16
16
|
import chartCellMatrix from './helpers/chartCellMatrix'
|
|
17
17
|
import regionCellMatrix from './helpers/regionCellMatrix'
|
|
@@ -24,6 +24,7 @@ import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
|
24
24
|
import isRightAlignedTableValue from '@cdc/core/helpers/isRightAlignedTableValue'
|
|
25
25
|
import './data-table.css'
|
|
26
26
|
import _ from 'lodash'
|
|
27
|
+
import { getDataSeriesColumns } from './helpers/getDataSeriesColumns'
|
|
27
28
|
|
|
28
29
|
export type DataTableProps = {
|
|
29
30
|
applyLegendToRow?: Function
|
|
@@ -32,9 +33,9 @@ export type DataTableProps = {
|
|
|
32
33
|
config: TableConfig
|
|
33
34
|
dataConfig?: Object
|
|
34
35
|
defaultSortBy?: string
|
|
35
|
-
displayGeoName?:
|
|
36
|
+
displayGeoName?: (row: string) => string
|
|
36
37
|
expandDataTable: boolean
|
|
37
|
-
formatLegendLocation?:
|
|
38
|
+
formatLegendLocation?: (row: string) => string
|
|
38
39
|
groupBy?: string
|
|
39
40
|
headerColor?: string
|
|
40
41
|
imageRef?: string
|
|
@@ -45,8 +46,7 @@ export type DataTableProps = {
|
|
|
45
46
|
outerContainerRef?: Function
|
|
46
47
|
rawData: Object[]
|
|
47
48
|
runtimeData: Object[] & Record<string, Object>
|
|
48
|
-
setFilteredCountryCode?:
|
|
49
|
-
showDownloadButton?: boolean
|
|
49
|
+
setFilteredCountryCode?: string // used for Maps only
|
|
50
50
|
tabbingId: string
|
|
51
51
|
tableTitle: string
|
|
52
52
|
viewport: 'lg' | 'md' | 'sm' | 'xs' | 'xxs'
|
|
@@ -61,6 +61,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
61
61
|
config,
|
|
62
62
|
dataConfig,
|
|
63
63
|
defaultSortBy,
|
|
64
|
+
displayGeoName,
|
|
64
65
|
tableTitle,
|
|
65
66
|
vizTitle,
|
|
66
67
|
rawData,
|
|
@@ -75,19 +76,15 @@ const DataTable = (props: DataTableProps) => {
|
|
|
75
76
|
} = props
|
|
76
77
|
const runtimeData = useMemo(() => {
|
|
77
78
|
const data = removeNullColumns(parentRuntimeData)
|
|
78
|
-
|
|
79
|
+
const { columnName, valueColumns } = config.table.pivot || {}
|
|
80
|
+
if (columnName && valueColumns) {
|
|
79
81
|
const excludeColumns = Object.values(config.columns || {})
|
|
80
82
|
.filter(column => column.dataTable === false)
|
|
81
83
|
.map(col => col.name)
|
|
82
|
-
|
|
83
|
-
if (columnName && valueColumns) {
|
|
84
|
-
// remove excluded columns so that they aren't included in the pivot calculation
|
|
85
|
-
const _data = data.map(row => _.omit(row, excludeColumns))
|
|
86
|
-
return pivotData(_data, columnName, valueColumns)
|
|
87
|
-
}
|
|
84
|
+
return pivotData(data, columnName, valueColumns, excludeColumns)
|
|
88
85
|
}
|
|
89
86
|
return data
|
|
90
|
-
}, [parentRuntimeData, config.table.pivot?.columnName, config.table.pivot?.
|
|
87
|
+
}, [parentRuntimeData, config.table.pivot?.columnName, config.table.pivot?.valueColumns])
|
|
91
88
|
|
|
92
89
|
const [expanded, setExpanded] = useState(expandDataTable)
|
|
93
90
|
const [sortBy, setSortBy] = useState<any>({
|
|
@@ -208,13 +205,10 @@ const DataTable = (props: DataTableProps) => {
|
|
|
208
205
|
[config.runtime?.seriesKeys]) // eslint-disable-line
|
|
209
206
|
|
|
210
207
|
const hasNoData = runtimeData.length === 0
|
|
211
|
-
|
|
212
208
|
const getClassNames = (): string => {
|
|
213
209
|
const classes = ['data-table-container']
|
|
214
210
|
|
|
215
|
-
const hasDownloadLinkAbove =
|
|
216
|
-
(config.table.download || config.general?.showDownloadButton) && !config.table.showDownloadLinkBelow
|
|
217
|
-
|
|
211
|
+
const hasDownloadLinkAbove = config.table.download && !config.table.showDownloadLinkBelow
|
|
218
212
|
const isStandaloneTable = config.type === 'table'
|
|
219
213
|
|
|
220
214
|
if (!hasDownloadLinkAbove && !isStandaloneTable) {
|
|
@@ -233,12 +227,32 @@ const DataTable = (props: DataTableProps) => {
|
|
|
233
227
|
|
|
234
228
|
if (config.visualizationType !== 'Box Plot') {
|
|
235
229
|
const getDownloadData = () => {
|
|
230
|
+
const dataSeriesColumns = getDataSeriesColumns(config, isVertical, runtimeData)
|
|
231
|
+
const sharedFilterColumns = config.table?.sharedFilterColumns || []
|
|
232
|
+
const vizFilterColumns = (config.filters || []).map(filter => filter.columnName)
|
|
233
|
+
const filterColumns = [...sharedFilterColumns, ...vizFilterColumns]
|
|
234
|
+
const visibleData =
|
|
235
|
+
config.type === 'map'
|
|
236
|
+
? getMapRowData(
|
|
237
|
+
rows,
|
|
238
|
+
columns,
|
|
239
|
+
config,
|
|
240
|
+
formatLegendLocation,
|
|
241
|
+
runtimeData as Record<string, Object>,
|
|
242
|
+
displayGeoName,
|
|
243
|
+
filterColumns
|
|
244
|
+
)
|
|
245
|
+
: runtimeData.map(d => {
|
|
246
|
+
return _.pick(d, [...filterColumns, ...dataSeriesColumns])
|
|
247
|
+
})
|
|
248
|
+
const csvData = config.table?.downloadVisibleDataOnly ? visibleData : rawData
|
|
249
|
+
|
|
236
250
|
// only use fullGeoName on County maps and no other
|
|
237
251
|
if (config.general?.geoType === 'us-county') {
|
|
238
252
|
// Add column for full Geo name along with State
|
|
239
|
-
return
|
|
253
|
+
return csvData.map(row => ({ FullGeoName: formatLegendLocation(row[config.columns.geo.name]), ...row }))
|
|
240
254
|
} else {
|
|
241
|
-
return
|
|
255
|
+
return csvData
|
|
242
256
|
}
|
|
243
257
|
}
|
|
244
258
|
|
|
@@ -276,7 +290,7 @@ const DataTable = (props: DataTableProps) => {
|
|
|
276
290
|
: {}
|
|
277
291
|
|
|
278
292
|
const TableMediaControls = ({ belowTable }) => {
|
|
279
|
-
const hasDownloadLink = config.table.download
|
|
293
|
+
const hasDownloadLink = config.table.download
|
|
280
294
|
return (
|
|
281
295
|
<MediaControls.Section classes={getMediaControlsClasses(belowTable, hasDownloadLink)}>
|
|
282
296
|
<MediaControls.Link config={config} dashboardDataConfig={dataConfig} />
|
|
@@ -54,7 +54,7 @@ const ChartHeader = ({
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const ColumnHeadingText = ({ column, text, config }) => {
|
|
57
|
-
if (text === '
|
|
57
|
+
if (text === '_pivotedFrom') return ''
|
|
58
58
|
let notApplicableText = 'Not Applicable'
|
|
59
59
|
if (text === '__series__' && config.table.indexLabel) return `${config.table.indexLabel} `
|
|
60
60
|
if (text === '__series__' && !config.table.indexLabel)
|
|
@@ -2,16 +2,22 @@ import { parseDate, formatDate } from '@cdc/core/helpers/cove/date'
|
|
|
2
2
|
import { formatNumber } from '../../../helpers/cove/number'
|
|
3
3
|
import { TableConfig } from '../types/TableConfig'
|
|
4
4
|
|
|
5
|
+
const isPivotColumn = (columnName, config) => {
|
|
6
|
+
const tableHasPivotColumnConfigured = config.table.pivot?.valueColumns?.length
|
|
7
|
+
const originalColumnNames = Object.keys(config.data?.[0] || {})
|
|
8
|
+
const columnIsPivot = originalColumnNames.length && !originalColumnNames.includes(columnName)
|
|
9
|
+
return tableHasPivotColumnConfigured && columnIsPivot
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
// if its additional column, return formatting params
|
|
6
|
-
const isAdditionalColumn = (column, config) => {
|
|
7
|
-
|
|
13
|
+
const isAdditionalColumn = (column: string, config, rowData) => {
|
|
14
|
+
const columnName = isPivotColumn(column, config) ? rowData._pivotedFrom : column
|
|
8
15
|
let formattingParams = {}
|
|
9
16
|
const { columns } = config
|
|
10
17
|
if (columns) {
|
|
11
18
|
Object.keys(columns).forEach(keycol => {
|
|
12
19
|
const col = columns[keycol]
|
|
13
|
-
if (col.name ===
|
|
14
|
-
inthere = true
|
|
20
|
+
if (col.name === columnName) {
|
|
15
21
|
formattingParams = {
|
|
16
22
|
addColPrefix: col.prefix,
|
|
17
23
|
addColSuffix: col.suffix,
|
|
@@ -54,7 +60,7 @@ export const getChartCellValue = (row: string, column: string, config: TableConf
|
|
|
54
60
|
if (rightSeriesItem.dataKey === column) resolvedAxis = 'right'
|
|
55
61
|
})
|
|
56
62
|
|
|
57
|
-
let addColParams = isAdditionalColumn(column, config)
|
|
63
|
+
let addColParams = isAdditionalColumn(column, config, rowObj)
|
|
58
64
|
if (addColParams) {
|
|
59
65
|
cellValue = config.dataFormat
|
|
60
66
|
? formatNumber(runtimeData[row][column], resolvedAxis, false, config, addColParams)
|
|
@@ -57,9 +57,13 @@ export const getDataSeriesColumns = (config: TableConfig, isVertical: boolean, r
|
|
|
57
57
|
})
|
|
58
58
|
|
|
59
59
|
tmpSeriesColumns.sort((a, b) => {
|
|
60
|
-
if (a === '
|
|
61
|
-
if (b === '
|
|
60
|
+
if (a === '_pivotedFrom') return -1
|
|
61
|
+
if (b === '_pivotedFrom') return 1
|
|
62
62
|
return columnOrderingHash[a] - columnOrderingHash[b]
|
|
63
63
|
})
|
|
64
|
-
|
|
64
|
+
|
|
65
|
+
return tmpSeriesColumns.filter(colName => {
|
|
66
|
+
// remove metadata added by pivotData function
|
|
67
|
+
return colName !== '_pivotedFrom'
|
|
68
|
+
})
|
|
65
69
|
}
|
|
@@ -3,11 +3,70 @@ import CellAnchor from '../components/CellAnchor'
|
|
|
3
3
|
import { DataTableProps } from '../DataTable'
|
|
4
4
|
import { ReactNode } from 'react'
|
|
5
5
|
import { displayDataAsText } from '@cdc/core/helpers/displayDataAsText'
|
|
6
|
+
import _ from 'lodash'
|
|
6
7
|
|
|
7
8
|
type MapRowsProps = DataTableProps & {
|
|
8
9
|
rows: string[]
|
|
9
10
|
}
|
|
10
11
|
|
|
12
|
+
const getGeoLabel = (config, row, formatLegendLocation, displayGeoName) => {
|
|
13
|
+
const { geoType, type } = config.general
|
|
14
|
+
let labelValue
|
|
15
|
+
if (!['single-state', 'us-county'].includes(geoType) || type === 'us-geocode') {
|
|
16
|
+
labelValue = displayGeoName(row)
|
|
17
|
+
labelValue = String(labelValue).startsWith('region') ? _.capitalize(labelValue) : labelValue
|
|
18
|
+
} else {
|
|
19
|
+
labelValue = formatLegendLocation(row)
|
|
20
|
+
}
|
|
21
|
+
return labelValue
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const getDataValue = (config, rowData, column) => {
|
|
25
|
+
// check for special classes
|
|
26
|
+
let specialValFound = ''
|
|
27
|
+
let columnName = config.columns[column]?.name
|
|
28
|
+
const { specialClasses } = config.legend
|
|
29
|
+
if (specialClasses && specialClasses.length && typeof specialClasses[0] === 'object') {
|
|
30
|
+
specialClasses.forEach(specialClass => {
|
|
31
|
+
if (specialClass.key === columnName) {
|
|
32
|
+
if (String(rowData[specialClass.key]) === specialClass.value) {
|
|
33
|
+
specialValFound = specialClass.label
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
return specialValFound || rowData[columnName]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const getMapRowData = (
|
|
42
|
+
rows: string[],
|
|
43
|
+
columns: Record<string, { label; name?; dataTable }>,
|
|
44
|
+
config: Record<string, Object>,
|
|
45
|
+
formatLegendLocation: (row: string) => string,
|
|
46
|
+
runtimeData: Record<string, Object>,
|
|
47
|
+
displayGeoName: (row: string) => string,
|
|
48
|
+
filterColumns: string[]
|
|
49
|
+
) => {
|
|
50
|
+
return rows.map((row: string) => {
|
|
51
|
+
const dataRow = {}
|
|
52
|
+
;[
|
|
53
|
+
...filterColumns,
|
|
54
|
+
...Object.keys(columns).filter(column => columns[column].dataTable === true && columns[column].name)
|
|
55
|
+
].map(column => {
|
|
56
|
+
const label = columns[column]?.label || columns[column]?.name || column
|
|
57
|
+
if (column === 'geo') {
|
|
58
|
+
dataRow[label] = getGeoLabel(config, row, formatLegendLocation, displayGeoName)
|
|
59
|
+
} else if (filterColumns.includes(column)) {
|
|
60
|
+
dataRow[label] = runtimeData[row][column]
|
|
61
|
+
} else {
|
|
62
|
+
const dataValue = getDataValue(config, runtimeData[row], column)
|
|
63
|
+
dataRow[label] = displayDataAsText(dataValue, column, config)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
return dataRow
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
11
70
|
const mapCellArray = ({
|
|
12
71
|
rows,
|
|
13
72
|
columns,
|
|
@@ -23,30 +82,15 @@ const mapCellArray = ({
|
|
|
23
82
|
Object.keys(columns)
|
|
24
83
|
.filter(column => columns[column].dataTable === true && columns[column].name)
|
|
25
84
|
.map(column => {
|
|
26
|
-
let cellValue
|
|
27
|
-
|
|
28
85
|
if (column === 'geo') {
|
|
29
86
|
const rowObj = runtimeData[row]
|
|
30
87
|
const legendColor = applyLegendToRow(rowObj)
|
|
31
|
-
|
|
32
|
-
let labelValue
|
|
88
|
+
const labelValue = getGeoLabel(config, row, formatLegendLocation, displayGeoName)
|
|
33
89
|
const mapZoomHandler =
|
|
34
90
|
config.general.type === 'bubble' && config.general.allowMapZoom && config.general.geoType === 'world'
|
|
35
91
|
? () => setFilteredCountryCode(row)
|
|
36
92
|
: undefined
|
|
37
|
-
|
|
38
|
-
(config.general.geoType !== 'single-state' && config.general.geoType !== 'us-county') ||
|
|
39
|
-
config.general.type === 'us-geocode'
|
|
40
|
-
) {
|
|
41
|
-
const capitalize = str => {
|
|
42
|
-
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
43
|
-
}
|
|
44
|
-
labelValue = displayGeoName(row)
|
|
45
|
-
labelValue = String(labelValue).startsWith('region') ? capitalize(labelValue) : labelValue
|
|
46
|
-
} else {
|
|
47
|
-
labelValue = formatLegendLocation(row)
|
|
48
|
-
}
|
|
49
|
-
cellValue = (
|
|
93
|
+
return (
|
|
50
94
|
<div className='col-12'>
|
|
51
95
|
<LegendShape fill={legendColor[0]} />
|
|
52
96
|
<CellAnchor
|
|
@@ -59,23 +103,10 @@ const mapCellArray = ({
|
|
|
59
103
|
</div>
|
|
60
104
|
)
|
|
61
105
|
} else {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const { specialClasses } = config.legend
|
|
66
|
-
if (specialClasses && specialClasses.length && typeof specialClasses[0] === 'object') {
|
|
67
|
-
specialClasses.forEach(specialClass => {
|
|
68
|
-
if (specialClass.key === columnName) {
|
|
69
|
-
if (String(runtimeData[row][specialClass.key]) === specialClass.value) {
|
|
70
|
-
specialValFound = specialClass.label
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
cellValue = displayDataAsText(specialValFound || runtimeData[row][columnName], column, config)
|
|
106
|
+
const rowData = runtimeData[row]
|
|
107
|
+
const dataValue = getDataValue(config, rowData, column)
|
|
108
|
+
return displayDataAsText(dataValue, column, config)
|
|
76
109
|
}
|
|
77
|
-
|
|
78
|
-
return cellValue
|
|
79
110
|
})
|
|
80
111
|
)
|
|
81
112
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { getMapRowData } from '../mapCellMatrix'
|
|
3
|
+
|
|
4
|
+
describe('getMapRowData', () => {
|
|
5
|
+
const columns = {
|
|
6
|
+
geo: { dataTable: true, name: 'geo', label: 'Geo' },
|
|
7
|
+
column1: { dataTable: true, name: 'column1', label: 'Column 1' },
|
|
8
|
+
column2: { dataTable: true, name: 'column2', label: 'Column 2' }
|
|
9
|
+
}
|
|
10
|
+
const config = {
|
|
11
|
+
general: { geoType: 'us-state', type: 'map' },
|
|
12
|
+
columns: {
|
|
13
|
+
geo: { name: 'geo' },
|
|
14
|
+
column1: { name: 'column1' },
|
|
15
|
+
column2: { name: 'column2' }
|
|
16
|
+
},
|
|
17
|
+
legend: { specialClasses: [] }
|
|
18
|
+
}
|
|
19
|
+
const formatLegendLocation = row => row
|
|
20
|
+
const rows = ['row2', 'row1']
|
|
21
|
+
const runtimeData = {
|
|
22
|
+
row1: { geo: 'region1', column1: 'data1', column2: 'data2', somethingElse: 'data5' },
|
|
23
|
+
row2: { geo: 'region2', column1: 'data3', column2: 'data4', somethingElse: 'data6' }
|
|
24
|
+
}
|
|
25
|
+
const displayGeoName = row => `displayGeoName -> ${row}`
|
|
26
|
+
const sharedFilterColumns = ['somethingElse']
|
|
27
|
+
|
|
28
|
+
it('should map rows to data rows correctly', () => {
|
|
29
|
+
const result = getMapRowData(
|
|
30
|
+
rows,
|
|
31
|
+
columns,
|
|
32
|
+
config,
|
|
33
|
+
formatLegendLocation,
|
|
34
|
+
runtimeData,
|
|
35
|
+
displayGeoName,
|
|
36
|
+
sharedFilterColumns
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
expect(result).toEqual([
|
|
40
|
+
{
|
|
41
|
+
Geo: 'displayGeoName -> row2',
|
|
42
|
+
'Column 1': 'data3',
|
|
43
|
+
'Column 2': 'data4',
|
|
44
|
+
somethingElse: 'data6'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
Geo: 'displayGeoName -> row1',
|
|
48
|
+
'Column 1': 'data1',
|
|
49
|
+
'Column 2': 'data2',
|
|
50
|
+
somethingElse: 'data5'
|
|
51
|
+
}
|
|
52
|
+
])
|
|
53
|
+
|
|
54
|
+
config.general.geoType = 'us-county'
|
|
55
|
+
const result2 = getMapRowData(
|
|
56
|
+
rows,
|
|
57
|
+
columns,
|
|
58
|
+
config,
|
|
59
|
+
formatLegendLocation,
|
|
60
|
+
runtimeData,
|
|
61
|
+
displayGeoName,
|
|
62
|
+
sharedFilterColumns
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
expect(result2).toEqual([
|
|
66
|
+
{
|
|
67
|
+
Geo: 'row2',
|
|
68
|
+
'Column 1': 'data3',
|
|
69
|
+
'Column 2': 'data4',
|
|
70
|
+
somethingElse: 'data6'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
Geo: 'row1',
|
|
74
|
+
'Column 1': 'data1',
|
|
75
|
+
'Column 2': 'data2',
|
|
76
|
+
somethingElse: 'data5'
|
|
77
|
+
}
|
|
78
|
+
])
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -83,7 +83,7 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
83
83
|
</Tooltip>
|
|
84
84
|
}
|
|
85
85
|
/>
|
|
86
|
-
{config.type !== 'table'
|
|
86
|
+
{config.type !== 'table' && (
|
|
87
87
|
<CheckBox
|
|
88
88
|
value={config.table.show}
|
|
89
89
|
fieldName='show'
|
|
@@ -108,15 +108,6 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
108
108
|
</Tooltip>
|
|
109
109
|
}
|
|
110
110
|
/>
|
|
111
|
-
) : (
|
|
112
|
-
<CheckBox
|
|
113
|
-
value={config.general?.showDownloadButton}
|
|
114
|
-
fieldName='showDownloadButton'
|
|
115
|
-
label='Show Download CSV link'
|
|
116
|
-
section='general'
|
|
117
|
-
updateField={updateField}
|
|
118
|
-
className='column-heading'
|
|
119
|
-
/>
|
|
120
111
|
)}
|
|
121
112
|
|
|
122
113
|
{config.visualizationType !== 'Box Plot' && config.type !== 'table' && (
|
|
@@ -234,6 +225,33 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
234
225
|
updateField={updateField}
|
|
235
226
|
/>
|
|
236
227
|
)}
|
|
228
|
+
<CheckBox
|
|
229
|
+
value={config.table.download}
|
|
230
|
+
fieldName='download'
|
|
231
|
+
label='Show Download CSV Link'
|
|
232
|
+
section='table'
|
|
233
|
+
updateField={updateField}
|
|
234
|
+
/>
|
|
235
|
+
{config.table.download && (
|
|
236
|
+
<>
|
|
237
|
+
<CheckBox
|
|
238
|
+
value={config.table.showDownloadLinkBelow}
|
|
239
|
+
fieldName='showDownloadLinkBelow'
|
|
240
|
+
className='ms-4'
|
|
241
|
+
label='Show Link Below Table'
|
|
242
|
+
section='table'
|
|
243
|
+
updateField={updateField}
|
|
244
|
+
/>
|
|
245
|
+
<CheckBox
|
|
246
|
+
value={config.table.downloadVisibleDataOnly}
|
|
247
|
+
fieldName='downloadVisibleDataOnly'
|
|
248
|
+
className='ms-4'
|
|
249
|
+
label='Download only visible data'
|
|
250
|
+
section='table'
|
|
251
|
+
updateField={updateField}
|
|
252
|
+
/>
|
|
253
|
+
</>
|
|
254
|
+
)}
|
|
237
255
|
{isDashboard && config.type !== 'table' && (
|
|
238
256
|
<CheckBox
|
|
239
257
|
value={config.table.showDataTableLink}
|
|
@@ -261,14 +279,6 @@ const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDash
|
|
|
261
279
|
updateField={updateField}
|
|
262
280
|
/>
|
|
263
281
|
)}
|
|
264
|
-
|
|
265
|
-
<CheckBox
|
|
266
|
-
value={config.table.showDownloadLinkBelow}
|
|
267
|
-
fieldName='showDownloadLinkBelow'
|
|
268
|
-
label='Show Download Link Below Table'
|
|
269
|
-
section='table'
|
|
270
|
-
updateField={updateField}
|
|
271
|
-
/>
|
|
272
282
|
<label>
|
|
273
283
|
<span className='edit-label column-heading'>Table Cell Min Width</span>
|
|
274
284
|
<input
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { memo, useEffect, useState } from 'react'
|
|
2
2
|
import { useDebounce } from 'use-debounce'
|
|
3
|
+
import { DROPDOWN_STYLES } from '../Filters/Filters'
|
|
3
4
|
|
|
4
5
|
export type Input = {
|
|
5
6
|
label: string
|
|
@@ -194,7 +195,7 @@ const Select = memo((props: SelectProps) => {
|
|
|
194
195
|
{tooltip}
|
|
195
196
|
</span>
|
|
196
197
|
<select
|
|
197
|
-
className={`cove-form-select ${required && !value ? 'warning' : ''}`}
|
|
198
|
+
className={`cove-form-select ${required && !value ? 'warning' : ''} ${DROPDOWN_STYLES}`}
|
|
198
199
|
name={fieldName}
|
|
199
200
|
value={value}
|
|
200
201
|
onChange={event => {
|
|
@@ -37,6 +37,15 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
37
37
|
updateField('filters', filterIndex, prop, value)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
const updateFilterDefaultValue = (index, value) => {
|
|
41
|
+
const filters = _.cloneDeep(config.filters)
|
|
42
|
+
const currentFilter = { ...filters[index], orderedValues: filters[index].values }
|
|
43
|
+
currentFilter.defaultValue = value
|
|
44
|
+
currentFilter.active = value
|
|
45
|
+
filters[index] = currentFilter
|
|
46
|
+
updateField(null, null, 'filters', filters)
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
const updateFilterStyle = (index, style: VizFilterStyle) => {
|
|
41
50
|
const filters = _.cloneDeep(config.filters)
|
|
42
51
|
const currentFilter = { ...filters[index], orderedValues: filters[index].values }
|
|
@@ -160,6 +169,20 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
160
169
|
options={filterStyleOptions}
|
|
161
170
|
/>
|
|
162
171
|
|
|
172
|
+
<Select
|
|
173
|
+
value={filter.defaultValue}
|
|
174
|
+
options={
|
|
175
|
+
filter.resetLabel
|
|
176
|
+
? [filter.resetLabel, ...config.filters?.[filterIndex].values]
|
|
177
|
+
: config.filters?.[filterIndex].values
|
|
178
|
+
}
|
|
179
|
+
updateField={(_section, _subSection, _key, value) => {
|
|
180
|
+
updateFilterDefaultValue(filterIndex, value)
|
|
181
|
+
}}
|
|
182
|
+
label='Filter Default Value'
|
|
183
|
+
initial='Select'
|
|
184
|
+
/>
|
|
185
|
+
|
|
163
186
|
{filter.filterStyle !== 'nested-dropdown' ? (
|
|
164
187
|
<>
|
|
165
188
|
<Select
|
|
@@ -26,6 +26,8 @@ export const VIZ_FILTER_STYLE = {
|
|
|
26
26
|
multiSelect: 'multi-select'
|
|
27
27
|
} as const
|
|
28
28
|
|
|
29
|
+
export const DROPDOWN_STYLES = 'py-2 ps-2 w-100 d-block'
|
|
30
|
+
|
|
29
31
|
export type VizFilterStyle = (typeof VIZ_FILTER_STYLE)[keyof typeof VIZ_FILTER_STYLE]
|
|
30
32
|
|
|
31
33
|
export const filterStyleOptions = Object.values(VIZ_FILTER_STYLE)
|
|
@@ -49,7 +51,7 @@ export const filterOrderOptions: { label: string; value: OrderBy }[] = [
|
|
|
49
51
|
export const useFilters = props => {
|
|
50
52
|
const [showApplyButton, setShowApplyButton] = useState(false)
|
|
51
53
|
|
|
52
|
-
//
|
|
54
|
+
// Deconstructing: notice, adding more descriptive visualizationConfig name over config
|
|
53
55
|
// visualizationConfig feels more robust for all vis types so that its not confused with config/state/etc.
|
|
54
56
|
const {
|
|
55
57
|
config: visualizationConfig,
|
|
@@ -250,8 +252,8 @@ export const useFilters = props => {
|
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
const filterConstants = {
|
|
253
|
-
buttonText: 'Apply
|
|
254
|
-
resetText: '
|
|
255
|
+
buttonText: 'Apply',
|
|
256
|
+
resetText: 'Clear Filters'
|
|
255
257
|
}
|
|
256
258
|
|
|
257
259
|
// prettier-ignore
|
|
@@ -285,6 +287,16 @@ const Filters = (props: FilterProps) => {
|
|
|
285
287
|
const [mobileFilterStyle, setMobileFilterStyle] = useState(false)
|
|
286
288
|
const [selectedFilter, setSelectedFilter] = useState<EventTarget>(null)
|
|
287
289
|
const [wrappingFilters, setWrappingFilters] = useState({})
|
|
290
|
+
const [initialActiveFilters, setInitialActiveFilters] = useState([])
|
|
291
|
+
|
|
292
|
+
useEffect(() => {
|
|
293
|
+
if (!filteredData) return
|
|
294
|
+
|
|
295
|
+
setInitialActiveFilters(filters.map(filter => filter.active))
|
|
296
|
+
}, [])
|
|
297
|
+
|
|
298
|
+
const activeFilters = filters.map(filter => filter.active)
|
|
299
|
+
const initialFiltersActive = initialActiveFilters.every((filter, i) => activeFilters.includes(filter))
|
|
288
300
|
const id = useId()
|
|
289
301
|
|
|
290
302
|
const wrappingFilterRefs = useRef({})
|
|
@@ -378,7 +390,7 @@ const Filters = (props: FilterProps) => {
|
|
|
378
390
|
id={`filter-${outerIndex}`}
|
|
379
391
|
name={label}
|
|
380
392
|
aria-label={`Filter by ${label}`}
|
|
381
|
-
className=
|
|
393
|
+
className={`cove-form-select ${DROPDOWN_STYLES}`}
|
|
382
394
|
data-index='0'
|
|
383
395
|
value={active}
|
|
384
396
|
onChange={e => {
|
|
@@ -547,7 +559,7 @@ const Filters = (props: FilterProps) => {
|
|
|
547
559
|
{visualizationConfig.filterIntro && (
|
|
548
560
|
<p className='filters-section__intro-text mb-3'>{visualizationConfig.filterIntro}</p>
|
|
549
561
|
)}
|
|
550
|
-
<div className='d-flex flex-wrap w-100
|
|
562
|
+
<div className='d-flex flex-wrap w-100 filters-section__wrapper align-items-end'>
|
|
551
563
|
{' '}
|
|
552
564
|
<>
|
|
553
565
|
<Style />
|
|
@@ -558,13 +570,13 @@ const Filters = (props: FilterProps) => {
|
|
|
558
570
|
handleApplyButton(filters)
|
|
559
571
|
}}
|
|
560
572
|
disabled={!showApplyButton}
|
|
561
|
-
className={[general?.headerColor ? general.headerColor : theme, 'apply'].join(' ')}
|
|
573
|
+
className={[general?.headerColor ? general.headerColor : theme, 'apply', 'me-2'].join(' ')}
|
|
562
574
|
>
|
|
563
575
|
{filterConstants.buttonText}
|
|
564
576
|
</Button>
|
|
565
|
-
<
|
|
577
|
+
<Button secondary disabled={initialFiltersActive} onClick={handleReset}>
|
|
566
578
|
{filterConstants.resetText}
|
|
567
|
-
</
|
|
579
|
+
</Button>
|
|
568
580
|
</div>
|
|
569
581
|
) : (
|
|
570
582
|
<></>
|