@cdc/dashboard 4.26.3 → 4.26.5
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/CONFIG.md +219 -0
- package/README.md +60 -20
- package/dist/cdcdashboard-CY9IcPSi.es.js +6 -0
- package/dist/cdcdashboard-DlpiY3fQ.es.js +4 -0
- package/dist/cdcdashboard.js +61559 -58048
- package/examples/__data__/data-2.json +6 -0
- package/examples/__data__/data.json +6 -0
- package/examples/dashboard-conditions-filters-incomplete.json +221 -0
- package/examples/dashboard-missing-datasets-multi.json +174 -0
- package/examples/dashboard-missing-datasets-single.json +121 -0
- package/examples/dashboard-multi-dashboard-version-regression.json +146 -0
- package/examples/dashboard-shared-filter-row-delete-cleanup.json +186 -0
- package/examples/dashboard-stale-dataset-keys.json +181 -0
- package/examples/dashboard-tiered-filter-regression.json +190 -0
- package/examples/legend-issue.json +1 -1
- package/examples/minimal-example.json +34 -0
- package/examples/private/cfa-dashboard.json +651 -0
- package/examples/private/data-bite-wrap.json +6936 -0
- package/examples/private/dengue.json +4640 -0
- package/examples/private/link_to_file.json +16662 -0
- package/examples/private/multi-dash-fix.json +16963 -0
- package/examples/private/versions.json +41612 -0
- package/examples/sankey.json +3 -3
- package/examples/test-api-filter-reset.json +4 -4
- package/examples/tp5-test.json +86 -4
- package/examples/us-map-filter-example.json +1074 -0
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +6 -2
- package/src/CdcDashboardComponent.tsx +179 -88
- package/src/DashboardCopyPasteContext.test.tsx +33 -0
- package/src/DashboardCopyPasteContext.tsx +48 -0
- package/src/_stories/Dashboard.EditorRegression.stories.tsx +72 -0
- package/src/_stories/Dashboard.Regression.stories.tsx +196 -0
- package/src/_stories/Dashboard.Zoom.stories.tsx +88 -0
- package/src/_stories/Dashboard.smoke.stories.tsx +33 -0
- package/src/_stories/Dashboard.stories.tsx +337 -2
- package/src/_stories/FilteredTextMigrationComparison.stories.tsx +87 -0
- package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
- package/src/_stories/_mock/tp5-test.json +86 -5
- package/src/components/Column.test.tsx +176 -0
- package/src/components/Column.tsx +214 -13
- package/src/components/DashboardConditionModal.test.tsx +420 -0
- package/src/components/DashboardConditionModal.tsx +367 -0
- package/src/components/DashboardConditionSummary.tsx +59 -0
- package/src/components/DashboardEditors.tsx +23 -0
- package/src/components/DashboardFilters/DashboardFilters.test.tsx +267 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +193 -172
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.test.tsx +164 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +46 -6
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +5 -3
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +59 -58
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +304 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +43 -36
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +2 -2
- package/src/components/DashboardFilters/DashboardFiltersWrapper.test.tsx +142 -0
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +32 -27
- package/src/components/DashboardFilters/dashboardfilter.styles.css +42 -27
- package/src/components/DataDesignerModal.tsx +2 -1
- package/src/components/ExpandCollapseButtons.tsx +6 -4
- package/src/components/Grid.tsx +12 -7
- package/src/components/Header/Header.tsx +36 -17
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
- package/src/components/Row.test.tsx +228 -0
- package/src/components/Row.tsx +104 -28
- package/src/components/VisualizationRow.test.tsx +396 -0
- package/src/components/VisualizationRow.tsx +177 -51
- package/src/components/VisualizationsPanel/VisualizationsPanel.test.tsx +49 -0
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +14 -13
- package/src/components/Widget/Widget.test.tsx +218 -0
- package/src/components/Widget/Widget.tsx +123 -20
- package/src/components/Widget/widget.styles.css +58 -14
- package/src/components/dashboard-condition-modal.css +76 -0
- package/src/components/dashboard-condition-summary.css +87 -0
- package/src/data/initial-state.js +1 -0
- package/src/helpers/addValuesToDashboardFilters.ts +3 -5
- package/src/helpers/addVisualization.ts +17 -4
- package/src/helpers/cloneDashboardWidget.ts +127 -0
- package/src/helpers/dashboardColumnWidgets.ts +99 -0
- package/src/helpers/dashboardConditionUi.ts +47 -0
- package/src/helpers/dashboardConditions.ts +200 -0
- package/src/helpers/dashboardFilterTargets.ts +156 -0
- package/src/helpers/filterData.ts +4 -9
- package/src/helpers/filterVisibility.ts +20 -0
- package/src/helpers/formatConfigBeforeSave.ts +2 -2
- package/src/helpers/getFilteredData.ts +18 -5
- package/src/helpers/getUpdateConfig.ts +43 -12
- package/src/helpers/getVizRowColumnLocator.ts +11 -1
- package/src/helpers/iconHash.tsx +9 -3
- package/src/helpers/mapDataToConfig.ts +31 -29
- package/src/helpers/reloadURLHelpers.ts +25 -5
- package/src/helpers/removeDashboardFilter.ts +33 -33
- package/src/helpers/tests/addVisualization.test.ts +53 -9
- package/src/helpers/tests/cloneDashboardWidget.test.ts +136 -0
- package/src/helpers/tests/dashboardColumnWidgets.test.ts +99 -0
- package/src/helpers/tests/dashboardConditionUi.test.ts +41 -0
- package/src/helpers/tests/dashboardConditions.test.ts +428 -0
- package/src/helpers/tests/formatConfigBeforeSave.test.ts +51 -0
- package/src/helpers/tests/getFilteredData.test.ts +265 -86
- package/src/helpers/tests/getUpdateConfig.test.ts +338 -0
- package/src/helpers/tests/reloadURLHelpers.test.ts +394 -238
- package/src/index.tsx +6 -3
- package/src/scss/grid.scss +281 -22
- package/src/scss/main.scss +215 -64
- package/src/store/dashboard.actions.ts +17 -4
- package/src/store/dashboard.reducer.test.ts +538 -0
- package/src/store/dashboard.reducer.ts +136 -22
- package/src/test/CdcDashboard.test.jsx +24 -0
- package/src/test/CdcDashboard.test.tsx +148 -0
- package/src/test/CdcDashboardComponent.test.tsx +935 -2
- package/src/types/ConfigRow.ts +15 -0
- package/src/types/DashboardFilters.ts +4 -0
- package/src/types/SharedFilter.ts +2 -0
- package/tests/fixtures/dashboard-config-with-metadata.json +1 -1
- package/dist/cdcdashboard-vr9HZwRt.es.js +0 -6
- package/examples/DEV-6574.json +0 -2224
- package/examples/api-dashboard-data.json +0 -272
- package/examples/api-dashboard-years.json +0 -11
- package/examples/api-geographies-data.json +0 -11
- package/examples/chart-data.json +0 -5409
- package/examples/custom/css/respiratory.css +0 -236
- package/examples/custom/js/respiratory.js +0 -242
- package/examples/default-data.json +0 -368
- package/examples/default-filter-control.json +0 -209
- package/examples/default-multi-dataset-shared-filter.json +0 -1729
- package/examples/default-multi-dataset.json +0 -506
- package/examples/ed-visits-county-file.json +0 -402
- package/examples/filters/Alabama.json +0 -72
- package/examples/filters/Alaska.json +0 -1737
- package/examples/filters/Arkansas.json +0 -4713
- package/examples/filters/California.json +0 -212
- package/examples/filters/Colorado.json +0 -1500
- package/examples/filters/Connecticut.json +0 -559
- package/examples/filters/Delaware.json +0 -63
- package/examples/filters/DistrictofColumbia.json +0 -63
- package/examples/filters/Florida.json +0 -4217
- package/examples/filters/States.json +0 -146
- package/examples/state-level.json +0 -90136
- package/examples/state-points.json +0 -10474
- package/examples/temp-example-data.json +0 -130
- package/examples/test-dashboard-simple.json +0 -503
- package/examples/test-example.json +0 -752
- package/examples/test-file.json +0 -147
- package/examples/test.json +0 -752
- package/examples/testing.json +0 -94456
- /package/examples/{data → __data__}/data-with-metadata.json +0 -0
- /package/examples/{legend-issue-data.json → __data__/legend-issue-data.json} +0 -0
- /package/examples/api-test/{categories.json → __data__/categories.json} +0 -0
- /package/examples/api-test/{chart-data.json → __data__/chart-data.json} +0 -0
- /package/examples/api-test/{topics.json → __data__/topics.json} +0 -0
- /package/examples/api-test/{years.json → __data__/years.json} +0 -0
- /package/src/_stories/{Dashboard.Pages.stories.tsx → Dashboard.Pages.smoke.stories.tsx} +0 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
2
|
+
import DataTransform from '@cdc/core/helpers/DataTransform'
|
|
3
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
4
|
+
import Loader from '@cdc/core/components/Loader'
|
|
5
|
+
import Modal from '@cdc/core/components/ui/Modal'
|
|
6
|
+
import MultiSelect from '@cdc/core/components/MultiSelect'
|
|
7
|
+
import { Select } from '@cdc/core/components/EditorPanel/Inputs'
|
|
8
|
+
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
9
|
+
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
10
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
11
|
+
import { createCoveId } from '@cdc/core/helpers/createCoveId'
|
|
12
|
+
import { useContext, useEffect, useMemo, useState } from 'react'
|
|
13
|
+
import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
|
|
14
|
+
import { hasConditionalWidgets, normalizeConditionalColumn } from '../helpers/dashboardColumnWidgets'
|
|
15
|
+
import { getDashboardConditionIds } from '../helpers/dashboardConditions'
|
|
16
|
+
import { DASHBOARD_CONDITION_TYPE_OPTIONS, DashboardConditionTypeOption } from '../helpers/dashboardConditionUi'
|
|
17
|
+
import { dashboardConditionsSupportedForRow } from '../helpers/dashboardFilterTargets'
|
|
18
|
+
import { DashboardCondition } from '../types/ConfigRow'
|
|
19
|
+
|
|
20
|
+
import './dashboard-condition-modal.css'
|
|
21
|
+
|
|
22
|
+
type DashboardConditionModalProps = {
|
|
23
|
+
rowIndex: number
|
|
24
|
+
columnIndex?: number
|
|
25
|
+
entryIndex?: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type DashboardConditionFormState = {
|
|
29
|
+
datasetKey: string
|
|
30
|
+
operator: DashboardConditionTypeOption
|
|
31
|
+
columnName: string
|
|
32
|
+
values: string[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const getDashboardConditionFormState = (dashboardCondition?: DashboardCondition): DashboardConditionFormState => ({
|
|
36
|
+
datasetKey: dashboardCondition?.datasetKey || '',
|
|
37
|
+
operator: dashboardCondition?.operator || '',
|
|
38
|
+
columnName: dashboardCondition?.columnName || '',
|
|
39
|
+
values: dashboardCondition?.values || []
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const tooltipIcon = (label: string) => (
|
|
43
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
44
|
+
<Tooltip.Target>
|
|
45
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} alt={label} />
|
|
46
|
+
</Tooltip.Target>
|
|
47
|
+
<Tooltip.Content>
|
|
48
|
+
<p className='dashboard-condition-modal__tooltip-text'>{label}</p>
|
|
49
|
+
</Tooltip.Content>
|
|
50
|
+
</Tooltip>
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
export const DashboardConditionModal: React.FC<DashboardConditionModalProps> = ({
|
|
54
|
+
rowIndex,
|
|
55
|
+
columnIndex,
|
|
56
|
+
entryIndex
|
|
57
|
+
}) => {
|
|
58
|
+
const { config } = useContext(DashboardContext)
|
|
59
|
+
const dispatch = useContext(DashboardDispatchContext)
|
|
60
|
+
const { overlay } = useGlobalContext()
|
|
61
|
+
const transform = new DataTransform()
|
|
62
|
+
|
|
63
|
+
const row = config.rows[rowIndex]
|
|
64
|
+
const column = columnIndex === undefined ? undefined : row.columns[columnIndex]
|
|
65
|
+
const isConditionalEntryEditor =
|
|
66
|
+
columnIndex !== undefined && entryIndex !== undefined && hasConditionalWidgets(column)
|
|
67
|
+
const existingDashboardCondition =
|
|
68
|
+
columnIndex === undefined
|
|
69
|
+
? row.dashboardCondition
|
|
70
|
+
: isConditionalEntryEditor
|
|
71
|
+
? column?.conditionalWidgets?.[entryIndex]?.dashboardCondition
|
|
72
|
+
: undefined
|
|
73
|
+
const [formState, setFormState] = useState<DashboardConditionFormState>(
|
|
74
|
+
getDashboardConditionFormState(existingDashboardCondition)
|
|
75
|
+
)
|
|
76
|
+
const [datasetRows, setDatasetRows] = useState<Record<string, any>[]>([])
|
|
77
|
+
const [columns, setColumns] = useState<string[]>([])
|
|
78
|
+
const [loadingColumns, setLoadingColumns] = useState(false)
|
|
79
|
+
const [errorMessage, setErrorMessage] = useState('')
|
|
80
|
+
const supportsDashboardConditions = dashboardConditionsSupportedForRow(row)
|
|
81
|
+
|
|
82
|
+
const title =
|
|
83
|
+
columnIndex === undefined
|
|
84
|
+
? `Row ${rowIndex + 1} Dashboard Condition`
|
|
85
|
+
: isConditionalEntryEditor
|
|
86
|
+
? `Row ${rowIndex + 1} Column ${columnIndex + 1} Component ${entryIndex + 1} Dashboard Condition`
|
|
87
|
+
: `Row ${rowIndex + 1} Column ${columnIndex + 1} Dashboard Condition`
|
|
88
|
+
const targetLabel = columnIndex === undefined ? 'row' : 'component'
|
|
89
|
+
|
|
90
|
+
const availableDatasets = Object.keys(config.datasets || {})
|
|
91
|
+
const needsValueMatch = formState.operator === 'columnHasAnyValue'
|
|
92
|
+
const usesDashboardFilterState = formState.operator === 'filtersIncomplete'
|
|
93
|
+
const hasCondition = !!formState.operator
|
|
94
|
+
const shouldShowDatasetSelect = hasCondition && !usesDashboardFilterState
|
|
95
|
+
const shouldShowColumnSelect = shouldShowDatasetSelect && needsValueMatch && !!formState.datasetKey
|
|
96
|
+
const shouldShowValueSelect = shouldShowColumnSelect && !!formState.columnName
|
|
97
|
+
|
|
98
|
+
const canSave = useMemo(() => {
|
|
99
|
+
if (!hasCondition) return true
|
|
100
|
+
if (usesDashboardFilterState) return true
|
|
101
|
+
if (!formState.datasetKey || !formState.operator) return false
|
|
102
|
+
if (!needsValueMatch) return true
|
|
103
|
+
|
|
104
|
+
return !!formState.columnName && formState.values.length > 0
|
|
105
|
+
}, [formState, hasCondition, needsValueMatch, usesDashboardFilterState])
|
|
106
|
+
|
|
107
|
+
const updateDashboardCondition = (dashboardCondition?: DashboardCondition) => {
|
|
108
|
+
if (columnIndex === undefined) {
|
|
109
|
+
dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: { dashboardCondition } } })
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const columns = row.columns.map((currentColumn, currentColumnIndex) => {
|
|
114
|
+
if (currentColumnIndex !== columnIndex) return currentColumn
|
|
115
|
+
|
|
116
|
+
if (isConditionalEntryEditor) {
|
|
117
|
+
const conditionalWidgets = [...(currentColumn.conditionalWidgets || [])]
|
|
118
|
+
conditionalWidgets[entryIndex] = {
|
|
119
|
+
...conditionalWidgets[entryIndex],
|
|
120
|
+
dashboardCondition
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return normalizeConditionalColumn({
|
|
124
|
+
...currentColumn,
|
|
125
|
+
conditionalWidgets
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!dashboardCondition) {
|
|
130
|
+
return currentColumn
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return normalizeConditionalColumn({
|
|
134
|
+
...currentColumn,
|
|
135
|
+
widget: undefined,
|
|
136
|
+
conditionalWidgets: [
|
|
137
|
+
{
|
|
138
|
+
widget: currentColumn.widget,
|
|
139
|
+
dashboardCondition
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: { columns } } })
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const closeModal = () => {
|
|
148
|
+
overlay?.actions.toggleOverlay(false)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const loadColumns = async (datasetKey: string) => {
|
|
152
|
+
if (!datasetKey) {
|
|
153
|
+
setDatasetRows([])
|
|
154
|
+
setColumns([])
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const dataset = config.datasets[datasetKey]
|
|
159
|
+
if (!dataset) {
|
|
160
|
+
setDatasetRows([])
|
|
161
|
+
setColumns([])
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
setLoadingColumns(true)
|
|
166
|
+
setErrorMessage('')
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
let nextData = dataset.data
|
|
170
|
+
if (!nextData && dataset.dataUrl) {
|
|
171
|
+
const response = await fetchRemoteData(dataset.dataUrl)
|
|
172
|
+
nextData = transform.autoStandardize(response.data)
|
|
173
|
+
if (dataset.dataDescription) {
|
|
174
|
+
nextData = transform.developerStandardize(nextData, dataset.dataDescription)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
setDatasetRows(nextData || [])
|
|
179
|
+
setColumns(Object.keys(nextData?.[0] || {}))
|
|
180
|
+
} catch (_error) {
|
|
181
|
+
setDatasetRows([])
|
|
182
|
+
setColumns([])
|
|
183
|
+
setErrorMessage('There was an issue loading the condition dataset. Please try again.')
|
|
184
|
+
} finally {
|
|
185
|
+
setLoadingColumns(false)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
loadColumns(formState.datasetKey)
|
|
191
|
+
}, [formState.datasetKey])
|
|
192
|
+
|
|
193
|
+
const selectedColumnValues = useMemo(() => {
|
|
194
|
+
if (!needsValueMatch || !formState.columnName) return []
|
|
195
|
+
|
|
196
|
+
const distinctValues = datasetRows.reduce((acc, row) => {
|
|
197
|
+
const value = row?.[formState.columnName]
|
|
198
|
+
if (value === undefined || value === null) return acc
|
|
199
|
+
|
|
200
|
+
const normalizedValue = String(value)
|
|
201
|
+
if (!acc.includes(normalizedValue)) {
|
|
202
|
+
acc.push(normalizedValue)
|
|
203
|
+
}
|
|
204
|
+
return acc
|
|
205
|
+
}, [] as string[])
|
|
206
|
+
|
|
207
|
+
return distinctValues
|
|
208
|
+
}, [datasetRows, formState.columnName, needsValueMatch])
|
|
209
|
+
|
|
210
|
+
if (!supportsDashboardConditions) {
|
|
211
|
+
return (
|
|
212
|
+
<Modal>
|
|
213
|
+
<Modal.Content>
|
|
214
|
+
<p>{title} editing is not available for toggle or multi-visualization rows in v1.</p>
|
|
215
|
+
<Button variant='primary' onClick={closeModal}>
|
|
216
|
+
Close
|
|
217
|
+
</Button>
|
|
218
|
+
</Modal.Content>
|
|
219
|
+
</Modal>
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<Modal>
|
|
225
|
+
<Modal.Content>
|
|
226
|
+
{loadingColumns && <Loader fullScreen />}
|
|
227
|
+
<div className='dashboard-condition-modal'>
|
|
228
|
+
<h3>{title}</h3>
|
|
229
|
+
|
|
230
|
+
<div className='dashboard-condition-modal__fields'>
|
|
231
|
+
<Select
|
|
232
|
+
className='dashboard-condition-modal__select py-2 ps-2 w-100 d-block'
|
|
233
|
+
fieldName='operator'
|
|
234
|
+
label='Condition Type'
|
|
235
|
+
options={DASHBOARD_CONDITION_TYPE_OPTIONS}
|
|
236
|
+
tooltip={tooltipIcon(
|
|
237
|
+
`Choose whether this ${targetLabel} should appear when the filtered condition dataset has data, has no data, contains one of the selected column values, or when targeted filters are incomplete. Use "Show when filters are incomplete" for static helper content, such as a markup include message explaining that filters must be selected to proceed.`
|
|
238
|
+
)}
|
|
239
|
+
value={formState.operator}
|
|
240
|
+
onChange={event => {
|
|
241
|
+
const operator = event.target.value as DashboardConditionTypeOption
|
|
242
|
+
setFormState(currentState => ({
|
|
243
|
+
...currentState,
|
|
244
|
+
operator,
|
|
245
|
+
datasetKey: operator && operator !== 'filtersIncomplete' ? currentState.datasetKey : '',
|
|
246
|
+
columnName: operator === 'columnHasAnyValue' ? currentState.columnName : '',
|
|
247
|
+
values: operator === 'columnHasAnyValue' ? currentState.values : []
|
|
248
|
+
}))
|
|
249
|
+
}}
|
|
250
|
+
/>
|
|
251
|
+
|
|
252
|
+
{shouldShowDatasetSelect && (
|
|
253
|
+
<>
|
|
254
|
+
<Select
|
|
255
|
+
className='dashboard-condition-modal__select py-2 ps-2 w-100 d-block'
|
|
256
|
+
fieldName='datasetKey'
|
|
257
|
+
label='Condition Dataset'
|
|
258
|
+
options={[
|
|
259
|
+
{ value: '', label: '- Select Option -' },
|
|
260
|
+
...availableDatasets.map(key => ({ value: key, label: key }))
|
|
261
|
+
]}
|
|
262
|
+
value={formState.datasetKey}
|
|
263
|
+
onChange={event => {
|
|
264
|
+
const datasetKey = event.target.value
|
|
265
|
+
setFormState(currentState => ({
|
|
266
|
+
...currentState,
|
|
267
|
+
datasetKey,
|
|
268
|
+
columnName: datasetKey === currentState.datasetKey ? currentState.columnName : '',
|
|
269
|
+
values: datasetKey === currentState.datasetKey ? currentState.values : []
|
|
270
|
+
}))
|
|
271
|
+
}}
|
|
272
|
+
/>
|
|
273
|
+
|
|
274
|
+
{shouldShowColumnSelect && (
|
|
275
|
+
<>
|
|
276
|
+
<Select
|
|
277
|
+
className='dashboard-condition-modal__select py-2 ps-2 w-100 d-block'
|
|
278
|
+
fieldName='columnName'
|
|
279
|
+
label='Column'
|
|
280
|
+
options={[
|
|
281
|
+
{ value: '', label: '- Select Option -' },
|
|
282
|
+
...columns.map(columnName => ({ value: columnName, label: columnName }))
|
|
283
|
+
]}
|
|
284
|
+
tooltip={tooltipIcon('Select the dataset column to inspect for this condition.')}
|
|
285
|
+
value={formState.columnName}
|
|
286
|
+
onChange={event => {
|
|
287
|
+
const columnName = event.target.value
|
|
288
|
+
setFormState(currentState => ({
|
|
289
|
+
...currentState,
|
|
290
|
+
columnName,
|
|
291
|
+
values: columnName === currentState.columnName ? currentState.values : []
|
|
292
|
+
}))
|
|
293
|
+
}}
|
|
294
|
+
/>
|
|
295
|
+
|
|
296
|
+
{shouldShowValueSelect && (
|
|
297
|
+
<div className='dashboard-condition-modal__multiselect-field'>
|
|
298
|
+
<span className='edit-label column-heading'>
|
|
299
|
+
Column Values
|
|
300
|
+
{tooltipIcon(
|
|
301
|
+
'Choose one or more matching values from the selected column. This condition passes when the filtered dataset contains at least one row with one of these values.'
|
|
302
|
+
)}
|
|
303
|
+
</span>
|
|
304
|
+
<MultiSelect
|
|
305
|
+
fieldName='values'
|
|
306
|
+
options={selectedColumnValues.map(value => ({ value, label: value }))}
|
|
307
|
+
selected={formState.values}
|
|
308
|
+
updateField={(_section, _subSection, _fieldName, values) => {
|
|
309
|
+
setFormState(currentState => ({
|
|
310
|
+
...currentState,
|
|
311
|
+
values
|
|
312
|
+
}))
|
|
313
|
+
}}
|
|
314
|
+
/>
|
|
315
|
+
</div>
|
|
316
|
+
)}
|
|
317
|
+
</>
|
|
318
|
+
)}
|
|
319
|
+
</>
|
|
320
|
+
)}
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
{errorMessage && <p className='text-danger'>{errorMessage}</p>}
|
|
324
|
+
|
|
325
|
+
<div className='d-flex gap-2 mt-3'>
|
|
326
|
+
<Button
|
|
327
|
+
disabled={!canSave}
|
|
328
|
+
onClick={() => {
|
|
329
|
+
if (!hasCondition || !formState.operator) {
|
|
330
|
+
updateDashboardCondition(undefined)
|
|
331
|
+
closeModal()
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const nextCondition: DashboardCondition = {
|
|
336
|
+
id:
|
|
337
|
+
existingDashboardCondition?.id ||
|
|
338
|
+
createCoveId('condition', { existingIds: getDashboardConditionIds(config.rows) }),
|
|
339
|
+
operator: formState.operator
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!usesDashboardFilterState) {
|
|
343
|
+
nextCondition.datasetKey = formState.datasetKey
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (needsValueMatch) {
|
|
347
|
+
nextCondition.columnName = formState.columnName
|
|
348
|
+
nextCondition.values = formState.values
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
updateDashboardCondition(nextCondition)
|
|
352
|
+
closeModal()
|
|
353
|
+
}}
|
|
354
|
+
variant='primary'
|
|
355
|
+
>
|
|
356
|
+
Save
|
|
357
|
+
</Button>
|
|
358
|
+
|
|
359
|
+
<Button onClick={closeModal} variant='secondary'>
|
|
360
|
+
Cancel
|
|
361
|
+
</Button>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
</Modal.Content>
|
|
365
|
+
</Modal>
|
|
366
|
+
)
|
|
367
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
3
|
+
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
4
|
+
import { iconHash } from '../helpers/iconHash'
|
|
5
|
+
import { getColumnHasAnyValueSummaryParts, getDashboardConditionSummary } from '../helpers/dashboardConditionUi'
|
|
6
|
+
import { DashboardCondition } from '../types/ConfigRow'
|
|
7
|
+
import { DashboardConditionModal } from './DashboardConditionModal'
|
|
8
|
+
|
|
9
|
+
import './dashboard-condition-summary.css'
|
|
10
|
+
|
|
11
|
+
type DashboardConditionSummaryProps = {
|
|
12
|
+
dashboardCondition: DashboardCondition
|
|
13
|
+
rowIndex: number
|
|
14
|
+
columnIndex?: number
|
|
15
|
+
entryIndex?: number
|
|
16
|
+
className?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const DashboardConditionSummary: React.FC<DashboardConditionSummaryProps> = ({
|
|
20
|
+
dashboardCondition,
|
|
21
|
+
rowIndex,
|
|
22
|
+
columnIndex,
|
|
23
|
+
entryIndex,
|
|
24
|
+
className = ''
|
|
25
|
+
}) => {
|
|
26
|
+
const { overlay } = useGlobalContext()
|
|
27
|
+
const summary = getDashboardConditionSummary(dashboardCondition)
|
|
28
|
+
const columnHasAnyValueSummaryParts =
|
|
29
|
+
dashboardCondition.operator === 'columnHasAnyValue'
|
|
30
|
+
? getColumnHasAnyValueSummaryParts(dashboardCondition.columnName)
|
|
31
|
+
: undefined
|
|
32
|
+
const summaryContent = columnHasAnyValueSummaryParts ? (
|
|
33
|
+
<>
|
|
34
|
+
{columnHasAnyValueSummaryParts.prefix}
|
|
35
|
+
<strong>{columnHasAnyValueSummaryParts.columnName}</strong>
|
|
36
|
+
{columnHasAnyValueSummaryParts.suffix}
|
|
37
|
+
</>
|
|
38
|
+
) : (
|
|
39
|
+
summary
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Button
|
|
44
|
+
aria-label={`Configure Dashboard Condition: ${summary}`}
|
|
45
|
+
className={['dashboard-condition-summary', className].filter(Boolean).join(' ')}
|
|
46
|
+
title={summary}
|
|
47
|
+
onClick={() => {
|
|
48
|
+
overlay?.actions.openOverlay(
|
|
49
|
+
<DashboardConditionModal rowIndex={rowIndex} columnIndex={columnIndex} entryIndex={entryIndex} />
|
|
50
|
+
)
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<span className='dashboard-condition-summary__icon' aria-hidden='true'>
|
|
54
|
+
{iconHash['condition']}
|
|
55
|
+
</span>
|
|
56
|
+
<span className='dashboard-condition-summary__text'>{summaryContent}</span>
|
|
57
|
+
</Button>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -17,6 +17,8 @@ type DashboardEditorProps = {
|
|
|
17
17
|
_updateConfig: (config: any) => void
|
|
18
18
|
isDebug?: boolean
|
|
19
19
|
setSharedFilter?: Function
|
|
20
|
+
clearSharedFilter?: (key: string) => void
|
|
21
|
+
hasActiveSharedFilter?: (key: string) => boolean
|
|
20
22
|
apiFilterDropdowns?: APIFilterDropdowns
|
|
21
23
|
state: DashboardState
|
|
22
24
|
interactionLabel: string
|
|
@@ -28,6 +30,8 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
|
|
|
28
30
|
_updateConfig,
|
|
29
31
|
isDebug,
|
|
30
32
|
setSharedFilter,
|
|
33
|
+
clearSharedFilter,
|
|
34
|
+
hasActiveSharedFilter,
|
|
31
35
|
apiFilterDropdowns,
|
|
32
36
|
state,
|
|
33
37
|
interactionLabel = ''
|
|
@@ -63,6 +67,10 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
|
|
|
63
67
|
isDebug={isDebug}
|
|
64
68
|
setConfig={_updateConfig}
|
|
65
69
|
setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
|
|
70
|
+
clearSharedFilter={setsSharedFilter ? clearSharedFilter : undefined}
|
|
71
|
+
hasActiveSharedFilter={
|
|
72
|
+
setsSharedFilter && hasActiveSharedFilter ? hasActiveSharedFilter(visualizationKey) : false
|
|
73
|
+
}
|
|
66
74
|
setSharedFilterValue={setSharedFilterValue}
|
|
67
75
|
isDashboard={true}
|
|
68
76
|
showLoader={false}
|
|
@@ -77,6 +85,11 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
|
|
|
77
85
|
<CdcDataBite
|
|
78
86
|
key={visualizationKey}
|
|
79
87
|
config={{ ...visualizationConfig, newViz: true }}
|
|
88
|
+
rawData={
|
|
89
|
+
state.data?.[visualizationConfig.dataKey] ||
|
|
90
|
+
state.config.datasets?.[visualizationConfig.dataKey]?.data ||
|
|
91
|
+
[]
|
|
92
|
+
}
|
|
80
93
|
isEditor={true}
|
|
81
94
|
setConfig={_updateConfig}
|
|
82
95
|
isDashboard={true}
|
|
@@ -89,6 +102,11 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
|
|
|
89
102
|
<CdcWaffleChart
|
|
90
103
|
key={visualizationKey}
|
|
91
104
|
config={visualizationConfig}
|
|
105
|
+
rawData={
|
|
106
|
+
state.data?.[visualizationConfig.dataKey] ||
|
|
107
|
+
state.config.datasets?.[visualizationConfig.dataKey]?.data ||
|
|
108
|
+
[]
|
|
109
|
+
}
|
|
92
110
|
isEditor={true}
|
|
93
111
|
setConfig={_updateConfig}
|
|
94
112
|
isDashboard={true}
|
|
@@ -101,6 +119,11 @@ const DashboardEditors: React.FC<DashboardEditorProps> = ({
|
|
|
101
119
|
<CdcMarkupInclude
|
|
102
120
|
key={visualizationKey}
|
|
103
121
|
config={visualizationConfig}
|
|
122
|
+
rawData={
|
|
123
|
+
state.data?.[visualizationConfig.dataKey] ||
|
|
124
|
+
state.config.datasets?.[visualizationConfig.dataKey]?.data ||
|
|
125
|
+
[]
|
|
126
|
+
}
|
|
104
127
|
isEditor={true}
|
|
105
128
|
setConfig={_updateConfig}
|
|
106
129
|
isDashboard={true}
|