@cdc/core 4.26.2 → 4.26.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/LICENSE +201 -0
- package/_stories/Gallery.Charts.stories.tsx +1 -1
- package/_stories/Gallery.DataBite.stories.tsx +1 -1
- package/_stories/Gallery.Maps.stories.tsx +1 -1
- package/_stories/PageART.stories.tsx +1 -1
- package/_stories/PageBRFSS.stories.tsx +1 -1
- package/_stories/PageCancerRegistries.stories.tsx +1 -1
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +3 -3
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +1 -1
- package/_stories/PageMaternalMortality.stories.tsx +1 -1
- package/_stories/PageOralHealth.stories.tsx +1 -1
- package/_stories/PageRespiratory.stories.tsx +4 -4
- package/_stories/PageSmokingTobacco.stories.tsx +1 -1
- package/_stories/PageStateDiabetesProfiles.stories.tsx +1 -1
- package/_stories/PageWastewater.stories.tsx +4 -4
- package/_stories/VegaImport.stories.tsx +3 -3
- package/assets/callout-flag.svg +7 -0
- package/components/AdvancedEditor/EmbedEditor.tsx +1 -1
- package/components/Alert/components/Alert.styles.css +2 -2
- package/components/ComboBox/combobox.styles.css +48 -48
- package/components/CustomColorsEditor/CustomColorsEditor.css +53 -53
- package/components/DataTable/DataTable.tsx +46 -18
- package/components/DataTable/DataTableStandAlone.tsx +1 -0
- package/components/DataTable/components/ChartHeader.tsx +21 -12
- package/components/DataTable/components/MapHeader.tsx +34 -28
- package/components/DataTable/components/SortIcon/sort-icon.css +5 -5
- package/components/DataTable/data-table.css +50 -52
- package/components/DataTable/helpers/applyCustomOrder.ts +17 -0
- package/components/DataTable/helpers/getChartCellValue.ts +10 -7
- package/components/DataTable/helpers/getMapDataTableColumnKeys.ts +22 -0
- package/components/DataTable/helpers/mapCellMatrix.tsx +33 -23
- package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +33 -0
- package/components/DownloadButton.tsx +14 -6
- package/components/EditorPanel/ColumnsEditor.tsx +38 -31
- package/components/EditorPanel/CustomSortOrder.tsx +94 -0
- package/components/EditorPanel/DataTableEditor.tsx +139 -23
- package/components/EditorPanel/EditorPanel.styles.css +71 -71
- package/components/EditorPanel/EditorPanel.tsx +3 -8
- package/components/EditorPanel/EditorPanelDispatch.tsx +4 -4
- package/components/EditorPanel/FootnotesEditor.tsx +2 -2
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +7 -6
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +12 -10
- package/components/EditorPanel/components/MarkupVariablesEditor.tsx +160 -106
- package/components/EditorPanel/components/PanelMarkup.tsx +5 -1
- package/{styles/v2/components → components/EditorPanel}/editor.scss +67 -13
- package/components/EditorPanel/sections/StyleTreatmentSection.tsx +99 -0
- package/components/EditorPanel/sections/VisualSection.tsx +11 -0
- package/components/EditorWrapper/editor-wrapper.style.css +1 -1
- package/components/Filters/Filters.tsx +3 -5
- package/components/Filters/components/Tabs.tsx +19 -7
- package/{styles → components/Filters}/filters.scss +3 -3
- package/components/Footnotes/FootnotesStandAlone.tsx +4 -2
- package/components/HeaderThemeSelector/HeaderThemeSelector.css +61 -5
- package/components/Layout/components/Responsive.tsx +14 -6
- package/components/Layout/components/Sidebar/components/Sidebar.tsx +1 -1
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +12 -18
- package/components/Layout/components/Visualization/index.tsx +39 -38
- package/components/Layout/components/Visualization/visualizations.scss +232 -15
- package/components/Layout/components/VisualizationContainer.test.tsx +67 -0
- package/components/Layout/components/VisualizationContainer.tsx +37 -0
- package/components/Layout/components/VisualizationContent.test.tsx +182 -0
- package/components/Layout/components/VisualizationContent.tsx +75 -0
- package/components/Layout/index.tsx +5 -5
- package/components/Layout/styles/editor-utils.scss +3 -3
- package/components/Layout/styles/editor.scss +4 -4
- package/components/Legend/Legend.Gradient.tsx +7 -1
- package/components/Loader/loader.styles.css +2 -2
- package/components/Loading.jsx +1 -1
- package/components/MediaControls.tsx +10 -2
- package/components/MultiSelect/multiselect.styles.css +19 -19
- package/components/NestedDropdown/nesteddropdown.styles.css +15 -15
- package/components/PaletteSelector/PaletteSelector.css +15 -15
- package/components/RichTooltip/richTooltip.css +6 -6
- package/components/Table/table.styles.css +2 -2
- package/components/Waiting.tsx +1 -1
- package/components/_stories/Filters.stories.tsx +1 -1
- package/components/_stories/styles.scss +0 -1
- package/components/elements/Button.jsx +1 -1
- package/components/elements/Card.jsx +1 -1
- package/{styles/v2/components → components/elements}/button.scss +9 -8
- package/components/inputs/InputCheckbox.jsx +1 -1
- package/components/inputs/InputSelect.tsx +1 -1
- package/components/inputs/InputText.jsx +1 -1
- package/components/inputs/InputToggle.tsx +1 -1
- package/{styles/v2/components/input → components/inputs}/_input-check-radio.scss +2 -2
- package/{styles/v2/components/input → components/inputs}/_input-group.scss +3 -3
- package/{styles/v2/components/input → components/inputs}/_input-slider.scss +2 -2
- package/{styles/v2/components/input → components/inputs}/_input.scss +5 -5
- package/{styles/v2/components/input → components/inputs}/index.scss +2 -2
- package/{styles → components}/loading.scss +1 -1
- package/components/managers/DataDesigner.tsx +1 -1
- package/{styles/v2/components → components/managers}/data-designer.scss +6 -7
- package/components/ui/Accordion.jsx +1 -1
- package/components/ui/Icon.tsx +1 -1
- package/components/ui/LoadSpin.jsx +1 -1
- package/components/ui/Modal.jsx +1 -1
- package/components/ui/Overlay.jsx +1 -1
- package/components/ui/Title/index.test.tsx +34 -0
- package/components/ui/Title/index.tsx +24 -7
- package/components/ui/Title/title.styles.css +119 -25
- package/components/ui/Tooltip.tsx +1 -1
- package/components/ui/_stories/Title.stories.tsx +1 -1
- package/{styles/v2/components → components/ui}/accordion.scss +3 -3
- package/components/ui/accordion.styles.css +11 -11
- package/{styles/v2/components → components/ui}/modal.scss +2 -2
- package/{styles/v2/components → components/ui}/overlay.scss +6 -6
- package/{styles/v2/components → components}/ui/tooltip.scss +1 -1
- package/{styles → components}/waiting.scss +9 -3
- package/devTemplate/dev.js +50 -0
- package/dist/cove-main.css +528 -231
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +2 -2
- package/helpers/backfillDefaults.ts +35 -0
- package/helpers/constants.ts +12 -0
- package/helpers/cove/date.ts +32 -3
- package/helpers/cove/number.ts +29 -15
- package/helpers/coveUpdateWorker.ts +12 -8
- package/helpers/displayDataAsText.ts +1 -1
- package/helpers/embed/embedHelper.js +13 -2
- package/helpers/embed/index.ts +0 -4
- package/helpers/extractDataAndMetadata.ts +20 -0
- package/helpers/fetchRemoteData.ts +14 -8
- package/helpers/labelHash.ts +9 -0
- package/helpers/markupProcessor.ts +56 -38
- package/helpers/prepareScreenshot.ts +6 -3
- package/helpers/testing.ts +1 -1
- package/helpers/tests/abbreviateNumber.test.ts +59 -0
- package/helpers/tests/backfillDefaults.test.ts +253 -0
- package/helpers/tests/date.test.ts +46 -0
- package/helpers/tests/extractDataAndMetadata.test.ts +93 -0
- package/helpers/tests/markupProcessor.test.ts +315 -124
- package/helpers/tests/number.test.ts +42 -0
- package/helpers/tests/prepareScreenshot.test.ts +28 -28
- package/helpers/tests/testStandaloneBuild.ts +36 -26
- package/helpers/tests/useDataVizClasses.test.ts +66 -0
- package/helpers/tests/visualizationWrapperUsage.test.ts +57 -0
- package/helpers/useDataVizClasses.ts +13 -7
- package/helpers/ver/4.24.4.ts +24 -0
- package/helpers/ver/4.26.3.ts +44 -0
- package/helpers/ver/4.26.4.ts +31 -0
- package/helpers/ver/tests/4.26.3.test.ts +168 -0
- package/helpers/ver/tests/4.26.4.test.ts +88 -0
- package/helpers/ver/tests/coveUpdateWorker.test.ts +57 -0
- package/package.json +2 -2
- package/styles/_global.scss +7 -7
- package/styles/_reset.scss +2 -2
- package/styles/{v2/base → base}/_file-selector.scss +4 -4
- package/styles/{v2/base → base}/_general.scss +2 -4
- package/styles/{v2/base → base}/index.scss +1 -1
- package/styles/base.scss +107 -165
- package/styles/cove-main.scss +3 -6
- package/styles/layout/_component.scss +110 -0
- package/styles/{v2/layout → layout}/_data-table.scss +7 -7
- package/styles/layout/_wrapper-padding.scss +27 -0
- package/styles/{v2/main.scss → main.scss} +3 -1
- package/styles/{v2/themes → themes}/_color-definitions.scss +46 -41
- package/styles/{_accessibility.scss → utils/_accessibility.scss} +1 -1
- package/styles/{_global-variables.scss → utils/_properties.scss} +133 -112
- package/styles/{v2/utils → utils}/index.scss +2 -1
- package/types/Axis.ts +2 -0
- package/types/ComponentStyles.ts +1 -0
- package/types/ConfigureData.ts +1 -0
- package/types/MarkupInclude.ts +1 -0
- package/types/MarkupVariable.ts +2 -1
- package/types/Palette.ts +1 -0
- package/types/Table.ts +9 -0
- package/types/Visualization.ts +1 -0
- package/styles/_common-components.css +0 -73
- package/styles/_variables.scss +0 -63
- package/styles/v2/layout/_component.scss +0 -21
- package/styles/v2/utils/_variables.scss +0 -9
- package/{styles/v2/components/card.scss → components/elements/card.css} +2 -2
- /package/{styles/v2/components → components/ui}/icon.scss +0 -0
- /package/{styles/v2/components → components/ui}/loadspin.scss +0 -0
- /package/styles/{v2/base → base}/_heading.scss +0 -0
- /package/styles/{v2/base → base}/_reset.scss +0 -0
- /package/styles/{v2/layout → layout}/_alert.scss +0 -0
- /package/styles/{v2/layout → layout}/_progression.scss +0 -0
- /package/styles/{v2/layout → layout}/_tooltip.scss +0 -0
- /package/styles/{v2/layout → layout}/index.scss +0 -0
- /package/styles/{v2/themes → themes}/index.scss +0 -0
- /package/styles/{v2/utils → utils}/_align.scss +0 -0
- /package/styles/{v2/utils → utils}/_animations.scss +0 -0
- /package/styles/{v2/utils → utils}/_breakpoints.scss +0 -0
- /package/styles/{v2/utils → utils}/_grid.scss +0 -0
- /package/styles/{v2/utils → utils}/_mixins.scss +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, useRef, ReactNode } from 'react'
|
|
2
2
|
import { cloneConfig } from '../../helpers/cloneConfig'
|
|
3
3
|
import ErrorBoundary from '../ErrorBoundary'
|
|
4
|
-
import
|
|
4
|
+
import { Sidebar } from '../Layout'
|
|
5
5
|
import './EditorPanel.styles.css'
|
|
6
6
|
|
|
7
7
|
export interface BaseEditorPanelProps<TConfig = any> {
|
|
@@ -101,7 +101,6 @@ export function EditorPanel<TConfig = any>({
|
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
103
|
* Toggle the editor panel visibility and update config to reflect the change.
|
|
104
|
-
* In dashboard context, also sets editing to false to return to dashboard editor.
|
|
105
104
|
*/
|
|
106
105
|
const onBackClick = () => {
|
|
107
106
|
const newDisplayPanel = !displayPanel
|
|
@@ -110,10 +109,6 @@ export function EditorPanel<TConfig = any>({
|
|
|
110
109
|
...config,
|
|
111
110
|
showEditorPanel: newDisplayPanel
|
|
112
111
|
}
|
|
113
|
-
// If in dashboard mode, set editing to false to return to dashboard editor
|
|
114
|
-
if (isDashboard) {
|
|
115
|
-
(newConfig as any).editing = false
|
|
116
|
-
}
|
|
117
112
|
|
|
118
113
|
// Update local config - the useEffect will handle syncing to parent
|
|
119
114
|
updateConfig(newConfig)
|
|
@@ -124,7 +119,7 @@ export function EditorPanel<TConfig = any>({
|
|
|
124
119
|
|
|
125
120
|
return (
|
|
126
121
|
<ErrorBoundary component='EditorPanel'>
|
|
127
|
-
<
|
|
122
|
+
<Sidebar
|
|
128
123
|
displayPanel={displayPanel}
|
|
129
124
|
isDashboard={isDashboard || false}
|
|
130
125
|
title={title}
|
|
@@ -136,7 +131,7 @@ export function EditorPanel<TConfig = any>({
|
|
|
136
131
|
displayPanel,
|
|
137
132
|
convertStateToConfig
|
|
138
133
|
})}
|
|
139
|
-
</
|
|
134
|
+
</Sidebar>
|
|
140
135
|
</ErrorBoundary>
|
|
141
136
|
)
|
|
142
137
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from 'react'
|
|
2
2
|
import ErrorBoundary from '../ErrorBoundary'
|
|
3
|
-
import
|
|
3
|
+
import { Sidebar } from '../Layout'
|
|
4
4
|
|
|
5
5
|
export interface EditorPanelDispatchProps<TState = any, TAction = any> {
|
|
6
6
|
state: TState
|
|
@@ -22,7 +22,7 @@ export interface EditorPanelDispatchChildProps<TState = any, TAction = any> {
|
|
|
22
22
|
*
|
|
23
23
|
* Provides common wrapper functionality including:
|
|
24
24
|
* - ErrorBoundary for error handling
|
|
25
|
-
* -
|
|
25
|
+
* - Sidebar for consistent panel display
|
|
26
26
|
* - State management for panel visibility
|
|
27
27
|
*
|
|
28
28
|
* @example
|
|
@@ -62,14 +62,14 @@ export function EditorPanelDispatch<TState = any, TAction = any>({
|
|
|
62
62
|
|
|
63
63
|
return (
|
|
64
64
|
<ErrorBoundary component='EditorPanel'>
|
|
65
|
-
<
|
|
65
|
+
<Sidebar
|
|
66
66
|
title={title}
|
|
67
67
|
onBackClick={onBackClick}
|
|
68
68
|
displayPanel={showEditorPanel}
|
|
69
69
|
isDashboard={isDashboard}
|
|
70
70
|
>
|
|
71
71
|
{children({ state, dispatch })}
|
|
72
|
-
</
|
|
72
|
+
</Sidebar>
|
|
73
73
|
</ErrorBoundary>
|
|
74
74
|
)
|
|
75
75
|
}
|
|
@@ -32,8 +32,8 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField,
|
|
|
32
32
|
if (dataSetChanged || noCachedData) {
|
|
33
33
|
setLoadingAPIData(true)
|
|
34
34
|
try {
|
|
35
|
-
|
|
36
|
-
newData = transform.autoStandardize(
|
|
35
|
+
const result = await fetchRemoteData(dataUrl)
|
|
36
|
+
newData = transform.autoStandardize(result.data)
|
|
37
37
|
} catch (e) {
|
|
38
38
|
setErrorMessage('There was an issue loading the data source. Please check the datasource URL and try again.')
|
|
39
39
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import cloneDeep from 'lodash/cloneDeep'
|
|
2
|
+
import uniq from 'lodash/uniq'
|
|
2
3
|
import { SubGrouping, VizFilter, OrderBy } from '../../../types/VizFilter'
|
|
3
4
|
import { handleSorting } from '../../Filters/helpers/handleSorting'
|
|
4
5
|
import { filterOrderOptions } from '../../../helpers/filterOrderOptions'
|
|
@@ -45,12 +46,12 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
|
|
|
45
46
|
|
|
46
47
|
const handleGroupingOrderBy = (order: OrderBy) => {
|
|
47
48
|
const groupSortObject = {
|
|
48
|
-
values:
|
|
49
|
+
values: cloneDeep(filter.values),
|
|
49
50
|
order
|
|
50
51
|
}
|
|
51
52
|
const newOrderedValues = handleSorting(groupSortObject).values
|
|
52
53
|
|
|
53
|
-
const newAllFilters =
|
|
54
|
+
const newAllFilters = cloneDeep(config.filters)
|
|
54
55
|
newAllFilters[filterIndex] = { ...filter, values: newOrderedValues, order }
|
|
55
56
|
if (order === 'cust') {
|
|
56
57
|
newAllFilters[filterIndex].orderedValues = newOrderedValues
|
|
@@ -68,7 +69,7 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
|
|
|
68
69
|
const filterGroups = filter.orderedValues?.length ? filter.orderedValues : filter.values
|
|
69
70
|
|
|
70
71
|
const valuesLookup = filterGroups.reduce((acc, groupName) => {
|
|
71
|
-
const values: string[] =
|
|
72
|
+
const values: string[] = uniq(
|
|
72
73
|
rawData
|
|
73
74
|
.map(d => {
|
|
74
75
|
return d[filter.columnName] === groupName ? d[value] : ''
|
|
@@ -115,10 +116,10 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
|
|
|
115
116
|
subGroupingFitlerOrder: string[],
|
|
116
117
|
groupName: string
|
|
117
118
|
) => {
|
|
118
|
-
const updatedGroupOrderedValues =
|
|
119
|
+
const updatedGroupOrderedValues = cloneDeep(subGroupingFitlerOrder)
|
|
119
120
|
const [movedItem] = updatedGroupOrderedValues.splice(currentIndex, 1)
|
|
120
121
|
updatedGroupOrderedValues.splice(newIndex, 0, movedItem)
|
|
121
|
-
const newSubGrouping =
|
|
122
|
+
const newSubGrouping = cloneDeep(subGrouping)
|
|
122
123
|
newSubGrouping.valuesLookup[groupName].values = updatedGroupOrderedValues
|
|
123
124
|
newSubGrouping.valuesLookup[groupName].orderedValues = updatedGroupOrderedValues
|
|
124
125
|
updateSubGroupingFilterProperty({ ...newSubGrouping, order: 'cust' })
|
|
@@ -3,7 +3,9 @@ import Tooltip from '../../ui/Tooltip'
|
|
|
3
3
|
import Icon from '../../ui/Icon'
|
|
4
4
|
import { Visualization } from '../../../types/Visualization'
|
|
5
5
|
import { UpdateFieldFunc } from '../../../types/UpdateFieldFunc'
|
|
6
|
-
import
|
|
6
|
+
import cloneDeep from 'lodash/cloneDeep'
|
|
7
|
+
import flatten from 'lodash/flatten'
|
|
8
|
+
import uniq from 'lodash/uniq'
|
|
7
9
|
import { MultiSelectFilter, VizFilter, VizFilterStyle } from '../../../types/VizFilter'
|
|
8
10
|
import { handleSorting } from '../../Filters/helpers/handleSorting'
|
|
9
11
|
import { filterOrderOptions } from '../../../helpers/filterOrderOptions'
|
|
@@ -27,7 +29,7 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
27
29
|
const openControls = useState({})
|
|
28
30
|
const [isNestedDragHovered, setIsNestedDragHovered] = useState(false)
|
|
29
31
|
const dataColumns = useMemo(() => {
|
|
30
|
-
return
|
|
32
|
+
return uniq(flatten(rawData?.map(row => Object.keys(row))))
|
|
31
33
|
}, [rawData])
|
|
32
34
|
|
|
33
35
|
// Helper function to get filter values from various sources
|
|
@@ -35,13 +37,13 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
35
37
|
if (filter.values && filter.values.length > 0) return filter.values
|
|
36
38
|
if (filter.orderedValues && filter.orderedValues.length > 0) return filter.orderedValues
|
|
37
39
|
if (filter.columnName && rawData && rawData.length > 0) {
|
|
38
|
-
return
|
|
40
|
+
return uniq(rawData.map(row => row[filter.columnName]))
|
|
39
41
|
}
|
|
40
42
|
return []
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
const removeFilter = index => {
|
|
44
|
-
let filters =
|
|
46
|
+
let filters = cloneDeep(config.filters)
|
|
45
47
|
|
|
46
48
|
filters.splice(index, 1)
|
|
47
49
|
|
|
@@ -53,7 +55,7 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
const updateFilterDefaultValue = (index, value) => {
|
|
56
|
-
const filters =
|
|
58
|
+
const filters = cloneDeep(config.filters)
|
|
57
59
|
const currentFilter = { ...filters[index], orderedValues: filters[index].values }
|
|
58
60
|
currentFilter.defaultValue = value
|
|
59
61
|
currentFilter.active = value
|
|
@@ -62,7 +64,7 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
const updateFilterStyle = (index, style: VizFilterStyle) => {
|
|
65
|
-
const filters =
|
|
67
|
+
const filters = cloneDeep(config.filters)
|
|
66
68
|
const currentFilter = { ...filters[index], orderedValues: filters[index].values }
|
|
67
69
|
currentFilter.filterStyle = style
|
|
68
70
|
if (style === 'multi-select') {
|
|
@@ -78,8 +80,8 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
const handleNameChange = (filterIndex, columnName) => {
|
|
81
|
-
const values =
|
|
82
|
-
const copiedFilter = { ...
|
|
83
|
+
const values = uniq(rawData.map(row => row[columnName]))
|
|
84
|
+
const copiedFilter = { ...cloneDeep(config.filters[filterIndex]), columnName, values }
|
|
83
85
|
handleSorting(copiedFilter) // sorts dropdown values in place
|
|
84
86
|
copiedFilter.active = copiedFilter.values[0]
|
|
85
87
|
const newFilters = config.filters.map((filter, index) => {
|
|
@@ -109,7 +111,7 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
109
111
|
const [movedItem] = updatedValues.splice(idx1, 1)
|
|
110
112
|
updatedValues.splice(idx2, 0, movedItem)
|
|
111
113
|
|
|
112
|
-
const filtersCopy =
|
|
114
|
+
const filtersCopy = cloneDeep(config.filters)
|
|
113
115
|
const filterItem = { ...filtersCopy[filterIndex] }
|
|
114
116
|
|
|
115
117
|
// Overwrite filterItem.values since thats what we map through in the editor panel
|
|
@@ -132,7 +134,7 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
132
134
|
|
|
133
135
|
const handleFilterReorder = (idx1: number, idx2: number) => {
|
|
134
136
|
if (idx1 === undefined || idx2 === undefined || idx1 === idx2) return
|
|
135
|
-
const filters =
|
|
137
|
+
const filters = cloneDeep(config.filters)
|
|
136
138
|
const [movedFilter] = filters.splice(idx1, 1)
|
|
137
139
|
filters.splice(idx2, 0, movedFilter)
|
|
138
140
|
updateField(null, null, 'filters', filters)
|
|
@@ -22,6 +22,8 @@ type MarkupVariablesEditorProps = {
|
|
|
22
22
|
enableMarkupVariables?: boolean
|
|
23
23
|
/** Callback when enable/disable toggle changes */
|
|
24
24
|
onToggleEnable?: (enabled: boolean) => void
|
|
25
|
+
/** File-level metadata extracted from the data source */
|
|
26
|
+
dataMetadata?: Record<string, string>
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export type { MarkupVariablesEditorProps }
|
|
@@ -37,7 +39,8 @@ const MarkupVariablesEditor: React.FC<MarkupVariablesEditorProps> = ({
|
|
|
37
39
|
config,
|
|
38
40
|
onChange,
|
|
39
41
|
enableMarkupVariables = false,
|
|
40
|
-
onToggleEnable
|
|
42
|
+
onToggleEnable,
|
|
43
|
+
dataMetadata = {}
|
|
41
44
|
}) => {
|
|
42
45
|
const [editingIndex, setEditingIndex] = useState<number | null>(null)
|
|
43
46
|
const [validationErrors, setValidationErrors] = useState<Record<number, string[]>>({})
|
|
@@ -63,6 +66,9 @@ const MarkupVariablesEditor: React.FC<MarkupVariablesEditorProps> = ({
|
|
|
63
66
|
return []
|
|
64
67
|
}, [data, datasets, config?.dataKey])
|
|
65
68
|
|
|
69
|
+
const metadataKeys = useMemo(() => Object.keys(dataMetadata || {}), [dataMetadata])
|
|
70
|
+
const hasMetadataKeys = metadataKeys.length > 0
|
|
71
|
+
|
|
66
72
|
// Get columns from the available data (memoized for performance)
|
|
67
73
|
const getAvailableColumns = useMemo((): string[] => {
|
|
68
74
|
const targetData = getTargetData()
|
|
@@ -92,18 +98,20 @@ const MarkupVariablesEditor: React.FC<MarkupVariablesEditorProps> = ({
|
|
|
92
98
|
if (!variable.tag || variable.tag.trim() === '') {
|
|
93
99
|
errors.push('Variable tag is required')
|
|
94
100
|
}
|
|
95
|
-
if (!variable.columnName || variable.columnName.trim() === '') {
|
|
101
|
+
if (!variable.metadataKey && (!variable.columnName || variable.columnName.trim() === '')) {
|
|
96
102
|
errors.push('Data column is required')
|
|
97
103
|
}
|
|
98
|
-
// Validate conditions
|
|
99
|
-
variable.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
// Validate conditions (not applicable to metadata-sourced variables)
|
|
105
|
+
if (!variable.metadataKey) {
|
|
106
|
+
variable.conditions?.forEach((condition, index) => {
|
|
107
|
+
if (!condition.columnName) {
|
|
108
|
+
errors.push(`Condition ${index + 1}: Column is required`)
|
|
109
|
+
}
|
|
110
|
+
if (!condition.value) {
|
|
111
|
+
errors.push(`Condition ${index + 1}: Value is required`)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
}
|
|
107
115
|
return errors
|
|
108
116
|
}, [])
|
|
109
117
|
|
|
@@ -240,9 +248,15 @@ const MarkupVariablesEditor: React.FC<MarkupVariablesEditorProps> = ({
|
|
|
240
248
|
{variable.tag}
|
|
241
249
|
</div>
|
|
242
250
|
<div style={{ fontSize: '13px', color: '#6c757d' }}>
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
251
|
+
{variable.metadataKey ? (
|
|
252
|
+
<>Metadata: <strong>{variable.metadataKey}</strong></>
|
|
253
|
+
) : (
|
|
254
|
+
<>
|
|
255
|
+
Column: <strong>{variable.columnName || 'Not selected'}</strong>
|
|
256
|
+
{variable.conditions && variable.conditions.length > 0 && (
|
|
257
|
+
<span> • {variable.conditions.length} condition{variable.conditions.length !== 1 ? 's' : ''}</span>
|
|
258
|
+
)}
|
|
259
|
+
</>
|
|
246
260
|
)}
|
|
247
261
|
</div>
|
|
248
262
|
{validationErrors[index] && validationErrors[index].length > 0 && (
|
|
@@ -270,6 +284,60 @@ const MarkupVariablesEditor: React.FC<MarkupVariablesEditorProps> = ({
|
|
|
270
284
|
<div className='mt-3 pt-3 border-t'>
|
|
271
285
|
<Accordion>
|
|
272
286
|
<Accordion.Section title='Basic Settings'>
|
|
287
|
+
{hasMetadataKeys && (
|
|
288
|
+
<div className='mb-3'>
|
|
289
|
+
<Select
|
|
290
|
+
value={variable.metadataKey ? 'metadata' : 'column'}
|
|
291
|
+
fieldName='variableSource'
|
|
292
|
+
label='Source'
|
|
293
|
+
options={[
|
|
294
|
+
{ value: 'column', label: 'Data Column' },
|
|
295
|
+
{ value: 'metadata', label: 'Data File Metadata' }
|
|
296
|
+
]}
|
|
297
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
298
|
+
if (value === 'metadata') {
|
|
299
|
+
updateVariable(index, { metadataKey: metadataKeys[0] || '', columnName: '', conditions: [] })
|
|
300
|
+
} else {
|
|
301
|
+
updateVariable(index, { metadataKey: undefined, columnName: '' })
|
|
302
|
+
}
|
|
303
|
+
}}
|
|
304
|
+
/>
|
|
305
|
+
</div>
|
|
306
|
+
)}
|
|
307
|
+
|
|
308
|
+
{variable.metadataKey !== undefined && variable.metadataKey !== null && hasMetadataKeys ? (
|
|
309
|
+
<div className='mb-3'>
|
|
310
|
+
<Select
|
|
311
|
+
value={variable.metadataKey}
|
|
312
|
+
fieldName='metadataKey'
|
|
313
|
+
label='Metadata Field'
|
|
314
|
+
options={metadataKeys.map(key => ({ value: key, label: `${key}: ${dataMetadata[key]}` }))}
|
|
315
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
316
|
+
updateVariable(index, {
|
|
317
|
+
metadataKey: value,
|
|
318
|
+
name: variable.name || value,
|
|
319
|
+
tag: variable.tag || generateTag(value)
|
|
320
|
+
})
|
|
321
|
+
}}
|
|
322
|
+
/>
|
|
323
|
+
</div>
|
|
324
|
+
) : (
|
|
325
|
+
<div className='mb-3'>
|
|
326
|
+
<Select
|
|
327
|
+
value={variable.columnName}
|
|
328
|
+
fieldName='columnName'
|
|
329
|
+
label='Data Column'
|
|
330
|
+
options={[
|
|
331
|
+
{ value: '', label: 'Select Column...' },
|
|
332
|
+
...getAvailableColumns.map(col => ({ value: col, label: col }))
|
|
333
|
+
]}
|
|
334
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
335
|
+
updateVariable(index, { columnName: value })
|
|
336
|
+
}}
|
|
337
|
+
/>
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
340
|
+
|
|
273
341
|
<div className='mb-3'>
|
|
274
342
|
<TextField
|
|
275
343
|
value={variable.name}
|
|
@@ -296,102 +364,88 @@ const MarkupVariablesEditor: React.FC<MarkupVariablesEditorProps> = ({
|
|
|
296
364
|
/>
|
|
297
365
|
</label>
|
|
298
366
|
</div>
|
|
299
|
-
|
|
300
|
-
<div className='mb-3'>
|
|
301
|
-
<Select
|
|
302
|
-
value={variable.columnName}
|
|
303
|
-
fieldName='columnName'
|
|
304
|
-
label='Data Column'
|
|
305
|
-
options={[
|
|
306
|
-
{ value: '', label: 'Select Column...' },
|
|
307
|
-
...getAvailableColumns.map(col => ({ value: col, label: col }))
|
|
308
|
-
]}
|
|
309
|
-
updateField={(_section, _subsection, _fieldName, value) => {
|
|
310
|
-
updateVariable(index, { columnName: value })
|
|
311
|
-
}}
|
|
312
|
-
/>
|
|
313
|
-
</div>
|
|
314
367
|
</Accordion.Section>
|
|
315
368
|
|
|
316
|
-
|
|
317
|
-
<
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
{variable.conditions && variable.conditions.length > 0 && (
|
|
322
|
-
<div className='conditions-list mb-2'>
|
|
323
|
-
{variable.conditions.map((condition, conditionIndex) => (
|
|
324
|
-
<div key={`condition-${index}-${conditionIndex}`} className='condition-item p-2 border rounded mb-2' style={{ backgroundColor: '#f8f9fa' }}>
|
|
325
|
-
<div className='mb-2'>
|
|
326
|
-
<Select
|
|
327
|
-
value={condition.columnName || ''}
|
|
328
|
-
fieldName={`condition-column-${index}-${conditionIndex}`}
|
|
329
|
-
label='Column'
|
|
330
|
-
options={[
|
|
331
|
-
{ value: '', label: 'Select Column...' },
|
|
332
|
-
...getAvailableColumns.map(col => ({ value: col, label: col }))
|
|
333
|
-
]}
|
|
334
|
-
updateField={(_section, _subsection, _fieldName, newColumnName) => {
|
|
335
|
-
// Reset value when column changes
|
|
336
|
-
updateCondition(index, conditionIndex, {
|
|
337
|
-
columnName: newColumnName,
|
|
338
|
-
value: ''
|
|
339
|
-
})
|
|
340
|
-
}}
|
|
341
|
-
/>
|
|
342
|
-
</div>
|
|
343
|
-
<div className='mb-2'>
|
|
344
|
-
<Select
|
|
345
|
-
value={condition.isOrIsNotEqualTo || 'is'}
|
|
346
|
-
fieldName={`condition-operator-${index}-${conditionIndex}`}
|
|
347
|
-
label='Operator'
|
|
348
|
-
options={[
|
|
349
|
-
{ value: 'is', label: 'is' },
|
|
350
|
-
{ value: 'is not', label: 'is not' }
|
|
351
|
-
]}
|
|
352
|
-
updateField={(_section, _subsection, _fieldName, value) => {
|
|
353
|
-
updateCondition(index, conditionIndex, { isOrIsNotEqualTo: value as 'is' | 'is not' })
|
|
354
|
-
}}
|
|
355
|
-
/>
|
|
356
|
-
</div>
|
|
357
|
-
<div className='mb-2'>
|
|
358
|
-
<Select
|
|
359
|
-
value={condition.value || ''}
|
|
360
|
-
fieldName={`condition-value-${index}-${conditionIndex}`}
|
|
361
|
-
label='Value'
|
|
362
|
-
options={[
|
|
363
|
-
{ value: '', label: 'Select Value...' },
|
|
364
|
-
...(condition.columnName
|
|
365
|
-
? getColumnValues(condition.columnName).map(val => ({
|
|
366
|
-
value: String(val),
|
|
367
|
-
label: String(val)
|
|
368
|
-
}))
|
|
369
|
-
: [])
|
|
370
|
-
]}
|
|
371
|
-
updateField={(_section, _subsection, _fieldName, value) => {
|
|
372
|
-
updateCondition(index, conditionIndex, { value })
|
|
373
|
-
}}
|
|
374
|
-
/>
|
|
375
|
-
</div>
|
|
376
|
-
<Button
|
|
377
|
-
className='btn-sm btn-danger'
|
|
378
|
-
onClick={() => removeCondition(index, conditionIndex)}
|
|
379
|
-
>
|
|
380
|
-
Remove Condition
|
|
381
|
-
</Button>
|
|
382
|
-
</div>
|
|
383
|
-
))}
|
|
369
|
+
{!variable.metadataKey && (
|
|
370
|
+
<Accordion.Section title='Conditions'>
|
|
371
|
+
<div className='text-sm text-gray-500 mb-2'>
|
|
372
|
+
Add conditions to filter when this variable should display data
|
|
384
373
|
</div>
|
|
385
|
-
)}
|
|
386
374
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
375
|
+
{variable.conditions && variable.conditions.length > 0 && (
|
|
376
|
+
<div className='conditions-list mb-2'>
|
|
377
|
+
{variable.conditions.map((condition, conditionIndex) => (
|
|
378
|
+
<div key={`condition-${index}-${conditionIndex}`} className='condition-item p-2 border rounded mb-2' style={{ backgroundColor: '#f8f9fa' }}>
|
|
379
|
+
<div className='mb-2'>
|
|
380
|
+
<Select
|
|
381
|
+
value={condition.columnName || ''}
|
|
382
|
+
fieldName={`condition-column-${index}-${conditionIndex}`}
|
|
383
|
+
label='Column'
|
|
384
|
+
options={[
|
|
385
|
+
{ value: '', label: 'Select Column...' },
|
|
386
|
+
...getAvailableColumns.map(col => ({ value: col, label: col }))
|
|
387
|
+
]}
|
|
388
|
+
updateField={(_section, _subsection, _fieldName, newColumnName) => {
|
|
389
|
+
updateCondition(index, conditionIndex, {
|
|
390
|
+
columnName: newColumnName,
|
|
391
|
+
value: ''
|
|
392
|
+
})
|
|
393
|
+
}}
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
396
|
+
<div className='mb-2'>
|
|
397
|
+
<Select
|
|
398
|
+
value={condition.isOrIsNotEqualTo || 'is'}
|
|
399
|
+
fieldName={`condition-operator-${index}-${conditionIndex}`}
|
|
400
|
+
label='Operator'
|
|
401
|
+
options={[
|
|
402
|
+
{ value: 'is', label: 'is' },
|
|
403
|
+
{ value: 'is not', label: 'is not' }
|
|
404
|
+
]}
|
|
405
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
406
|
+
updateCondition(index, conditionIndex, { isOrIsNotEqualTo: value as 'is' | 'is not' })
|
|
407
|
+
}}
|
|
408
|
+
/>
|
|
409
|
+
</div>
|
|
410
|
+
<div className='mb-2'>
|
|
411
|
+
<Select
|
|
412
|
+
value={condition.value || ''}
|
|
413
|
+
fieldName={`condition-value-${index}-${conditionIndex}`}
|
|
414
|
+
label='Value'
|
|
415
|
+
options={[
|
|
416
|
+
{ value: '', label: 'Select Value...' },
|
|
417
|
+
...(condition.columnName
|
|
418
|
+
? getColumnValues(condition.columnName).map(val => ({
|
|
419
|
+
value: String(val),
|
|
420
|
+
label: String(val)
|
|
421
|
+
}))
|
|
422
|
+
: [])
|
|
423
|
+
]}
|
|
424
|
+
updateField={(_section, _subsection, _fieldName, value) => {
|
|
425
|
+
updateCondition(index, conditionIndex, { value })
|
|
426
|
+
}}
|
|
427
|
+
/>
|
|
428
|
+
</div>
|
|
429
|
+
<Button
|
|
430
|
+
className='btn-sm btn-danger'
|
|
431
|
+
onClick={() => removeCondition(index, conditionIndex)}
|
|
432
|
+
>
|
|
433
|
+
Remove Condition
|
|
434
|
+
</Button>
|
|
435
|
+
</div>
|
|
436
|
+
))}
|
|
437
|
+
</div>
|
|
438
|
+
)}
|
|
439
|
+
|
|
440
|
+
<Button
|
|
441
|
+
className='btn-sm'
|
|
442
|
+
onClick={() => addCondition(index)}
|
|
443
|
+
>
|
|
444
|
+
<Icon display='plus' size={14} className='mr-1' />
|
|
445
|
+
Add Condition
|
|
446
|
+
</Button>
|
|
447
|
+
</Accordion.Section>
|
|
448
|
+
)}
|
|
395
449
|
|
|
396
450
|
<Accordion.Section title='Formatting Options'>
|
|
397
451
|
<div className='mb-3'>
|
|
@@ -18,6 +18,8 @@ type PanelMarkupProps = {
|
|
|
18
18
|
onToggleEnable: (enabled: boolean) => void
|
|
19
19
|
/** Optional: wrap in accordion. Default true */
|
|
20
20
|
withAccordion?: boolean
|
|
21
|
+
/** File-level metadata extracted from the data source */
|
|
22
|
+
dataMetadata?: Record<string, string>
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
/**
|
|
@@ -31,7 +33,8 @@ const PanelMarkup: React.FC<PanelMarkupProps> = ({
|
|
|
31
33
|
enableMarkupVariables,
|
|
32
34
|
onMarkupVariablesChange,
|
|
33
35
|
onToggleEnable,
|
|
34
|
-
withAccordion = true
|
|
36
|
+
withAccordion = true,
|
|
37
|
+
dataMetadata
|
|
35
38
|
}) => {
|
|
36
39
|
const content = (
|
|
37
40
|
<MarkupVariablesEditor
|
|
@@ -40,6 +43,7 @@ const PanelMarkup: React.FC<PanelMarkupProps> = ({
|
|
|
40
43
|
onChange={onMarkupVariablesChange}
|
|
41
44
|
enableMarkupVariables={enableMarkupVariables || false}
|
|
42
45
|
onToggleEnable={onToggleEnable}
|
|
46
|
+
dataMetadata={dataMetadata}
|
|
43
47
|
/>
|
|
44
48
|
)
|
|
45
49
|
|