@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,228 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react'
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
+
import { DashboardContext, DashboardDispatchContext, initialState } from '../DashboardContext'
|
|
5
|
+
import { GlobalContext } from '@cdc/core/components/GlobalContext'
|
|
6
|
+
import Row from './Row'
|
|
7
|
+
|
|
8
|
+
vi.mock('@cdc/core/components/ui/Icon', () => ({
|
|
9
|
+
default: props => <span data-testid='mock-icon' {...props} />
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
vi.mock('../images/icon-col-12.svg', () => ({ default: () => <svg data-testid='icon-col-12' /> }))
|
|
13
|
+
vi.mock('../images/icon-col-6.svg', () => ({ default: () => <svg data-testid='icon-col-6' /> }))
|
|
14
|
+
vi.mock('../images/icon-col-4.svg', () => ({ default: () => <svg data-testid='icon-col-4' /> }))
|
|
15
|
+
vi.mock('../images/icon-col-4-8.svg', () => ({ default: () => <svg data-testid='icon-col-4-8' /> }))
|
|
16
|
+
vi.mock('../images/icon-col-8-4.svg', () => ({ default: () => <svg data-testid='icon-col-8-4' /> }))
|
|
17
|
+
vi.mock('../images/icon-toggle.svg', () => ({ default: () => <svg data-testid='icon-toggle' /> }))
|
|
18
|
+
|
|
19
|
+
const renderRow = (dashboardCondition = undefined) => {
|
|
20
|
+
const openOverlay = vi.fn()
|
|
21
|
+
|
|
22
|
+
render(
|
|
23
|
+
<GlobalContext.Provider
|
|
24
|
+
value={{
|
|
25
|
+
overlay: {
|
|
26
|
+
object: null,
|
|
27
|
+
show: false,
|
|
28
|
+
disableBgClose: false,
|
|
29
|
+
actions: {
|
|
30
|
+
openOverlay,
|
|
31
|
+
toggleOverlay: vi.fn()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<DashboardContext.Provider
|
|
37
|
+
value={{
|
|
38
|
+
...initialState,
|
|
39
|
+
config: {
|
|
40
|
+
type: 'dashboard',
|
|
41
|
+
dashboard: { sharedFilters: [] },
|
|
42
|
+
datasets: {},
|
|
43
|
+
rows: [
|
|
44
|
+
{
|
|
45
|
+
columns: [],
|
|
46
|
+
dashboardCondition,
|
|
47
|
+
expandCollapseAllButtons: false
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
visualizations: {}
|
|
51
|
+
} as any,
|
|
52
|
+
outerContainerRef: vi.fn(),
|
|
53
|
+
setParentConfig: vi.fn(),
|
|
54
|
+
isDebug: false,
|
|
55
|
+
isEditor: true,
|
|
56
|
+
reloadURLData: vi.fn(),
|
|
57
|
+
loadAPIFilters: vi.fn(),
|
|
58
|
+
setAPIFilterDropdowns: vi.fn(),
|
|
59
|
+
setAPILoading: vi.fn()
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<DashboardDispatchContext.Provider value={vi.fn()}>
|
|
63
|
+
<Row row={{ columns: [], dashboardCondition, expandCollapseAllButtons: false } as any} idx={0} uuid='row-1' />
|
|
64
|
+
</DashboardDispatchContext.Provider>
|
|
65
|
+
</DashboardContext.Provider>
|
|
66
|
+
</GlobalContext.Provider>
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return { openOverlay }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const renderRowWithConfig = config => {
|
|
73
|
+
const dispatch = vi.fn()
|
|
74
|
+
|
|
75
|
+
render(
|
|
76
|
+
<GlobalContext.Provider
|
|
77
|
+
value={{
|
|
78
|
+
overlay: {
|
|
79
|
+
object: null,
|
|
80
|
+
show: false,
|
|
81
|
+
disableBgClose: false,
|
|
82
|
+
actions: {
|
|
83
|
+
openOverlay: vi.fn(),
|
|
84
|
+
toggleOverlay: vi.fn()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
<DashboardContext.Provider
|
|
90
|
+
value={{
|
|
91
|
+
...initialState,
|
|
92
|
+
config,
|
|
93
|
+
outerContainerRef: vi.fn(),
|
|
94
|
+
setParentConfig: vi.fn(),
|
|
95
|
+
isDebug: false,
|
|
96
|
+
isEditor: true,
|
|
97
|
+
reloadURLData: vi.fn(),
|
|
98
|
+
loadAPIFilters: vi.fn(),
|
|
99
|
+
setAPIFilterDropdowns: vi.fn(),
|
|
100
|
+
setAPILoading: vi.fn()
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<DashboardDispatchContext.Provider value={dispatch}>
|
|
104
|
+
<Row row={config.rows[0]} idx={0} uuid='row-1' />
|
|
105
|
+
</DashboardDispatchContext.Provider>
|
|
106
|
+
</DashboardContext.Provider>
|
|
107
|
+
</GlobalContext.Provider>
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return { dispatch }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
describe('Row', () => {
|
|
114
|
+
it('renders the row label without a separator', () => {
|
|
115
|
+
renderRow()
|
|
116
|
+
|
|
117
|
+
expect(screen.getByText('Row 1')).toBeInTheDocument()
|
|
118
|
+
expect(screen.queryByText('Row - 1')).not.toBeInTheDocument()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('renders separate row toolbar buttons for data and dashboard conditions', () => {
|
|
122
|
+
const { openOverlay } = renderRow()
|
|
123
|
+
|
|
124
|
+
fireEvent.click(screen.getByTitle('Configure Data'))
|
|
125
|
+
fireEvent.click(screen.getByTitle('Configure Dashboard Condition'))
|
|
126
|
+
|
|
127
|
+
expect(openOverlay).toHaveBeenCalledTimes(2)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('does not render a row condition summary when no condition exists', () => {
|
|
131
|
+
renderRow()
|
|
132
|
+
|
|
133
|
+
expect(screen.getByTitle('Configure Dashboard Condition')).toBeInTheDocument()
|
|
134
|
+
expect(screen.queryByRole('button', { name: /Configure Dashboard Condition:/ })).not.toBeInTheDocument()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('shows the active row condition button and summary strip when a condition exists', () => {
|
|
138
|
+
renderRow({ id: 'row-condition-1', datasetKey: 'condition-data', operator: 'hasData' })
|
|
139
|
+
|
|
140
|
+
expect(screen.getByTitle('Configure Dashboard Condition')).toHaveClass('is-active')
|
|
141
|
+
expect(screen.getByRole('button', { name: "Configure Dashboard Condition: Show when there's data" })).toHaveClass(
|
|
142
|
+
'dashboard-condition-summary'
|
|
143
|
+
)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('opens the condition modal from the row condition button or summary strip', () => {
|
|
147
|
+
const { openOverlay } = renderRow({ id: 'row-condition-1', datasetKey: 'condition-data', operator: 'hasData' })
|
|
148
|
+
|
|
149
|
+
fireEvent.click(screen.getByTitle('Configure Dashboard Condition'))
|
|
150
|
+
fireEvent.click(screen.getByRole('button', { name: "Configure Dashboard Condition: Show when there's data" }))
|
|
151
|
+
|
|
152
|
+
expect(openOverlay).toHaveBeenCalledTimes(2)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('remaps row targets and preserves unknown targets when deleting a row', () => {
|
|
156
|
+
const { dispatch } = renderRowWithConfig({
|
|
157
|
+
type: 'dashboard',
|
|
158
|
+
dashboard: {
|
|
159
|
+
sharedFilters: [
|
|
160
|
+
{
|
|
161
|
+
key: 'County',
|
|
162
|
+
type: 'datafilter',
|
|
163
|
+
columnName: 'county',
|
|
164
|
+
usedBy: ['legacy-footnote-target', 'viz-1', 0, 1]
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
datasets: {},
|
|
169
|
+
rows: [
|
|
170
|
+
{
|
|
171
|
+
columns: [],
|
|
172
|
+
dashboardCondition: { id: 'row-condition-1', datasetKey: 'condition-data', operator: 'hasData' },
|
|
173
|
+
expandCollapseAllButtons: false
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
columns: [{ width: 12, widget: 'dashboard-filters-1' }],
|
|
177
|
+
dashboardCondition: { id: 'condition-1', datasetKey: 'condition-data', operator: 'hasData' },
|
|
178
|
+
expandCollapseAllButtons: false
|
|
179
|
+
}
|
|
180
|
+
],
|
|
181
|
+
visualizations: {
|
|
182
|
+
'dashboard-filters-1': {
|
|
183
|
+
uid: 'dashboard-filters-1',
|
|
184
|
+
type: 'dashboardFilters',
|
|
185
|
+
sharedFilterIndexes: [0]
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} as any)
|
|
189
|
+
|
|
190
|
+
fireEvent.click(screen.getByTitle('Delete Row'))
|
|
191
|
+
|
|
192
|
+
const nextConfig = dispatch.mock.calls[0][0].payload[0]
|
|
193
|
+
expect(nextConfig.dashboard.sharedFilters[0].usedBy).toEqual(['legacy-footnote-target', 'viz-1', 0])
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('assigns distinct row uuids when moving a row even if Date.now matches', () => {
|
|
197
|
+
const mathRandomSpy = vi.spyOn(Math, 'random')
|
|
198
|
+
mathRandomSpy.mockReturnValueOnce(0.123456789).mockReturnValueOnce(0.23456789)
|
|
199
|
+
|
|
200
|
+
const { dispatch } = renderRowWithConfig({
|
|
201
|
+
type: 'dashboard',
|
|
202
|
+
dashboard: { sharedFilters: [] },
|
|
203
|
+
datasets: {},
|
|
204
|
+
rows: [
|
|
205
|
+
{
|
|
206
|
+
uuid: 'row-a',
|
|
207
|
+
columns: [],
|
|
208
|
+
expandCollapseAllButtons: false
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
uuid: 'row-b',
|
|
212
|
+
columns: [],
|
|
213
|
+
expandCollapseAllButtons: false
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
visualizations: {}
|
|
217
|
+
} as any)
|
|
218
|
+
|
|
219
|
+
fireEvent.click(screen.getByTitle('Move Row Down'))
|
|
220
|
+
|
|
221
|
+
const nextConfig = dispatch.mock.calls[0][0].payload[0]
|
|
222
|
+
expect(nextConfig.rows[0].uuid).not.toEqual(nextConfig.rows[1].uuid)
|
|
223
|
+
expect(nextConfig.rows[0].uuid).toMatch(/^row-[a-z0-9]{8}$/)
|
|
224
|
+
expect(nextConfig.rows[1].uuid).toMatch(/^row-[a-z0-9]{8}$/)
|
|
225
|
+
|
|
226
|
+
mathRandomSpy.mockRestore()
|
|
227
|
+
})
|
|
228
|
+
})
|
package/src/components/Row.tsx
CHANGED
|
@@ -14,11 +14,18 @@ import EightFourColIcon from '../images/icon-col-8-4.svg'
|
|
|
14
14
|
import ToggleIcon from '../images/icon-toggle.svg'
|
|
15
15
|
import { ConfigRow } from '../types/ConfigRow'
|
|
16
16
|
import { DataDesignerModal } from './DataDesignerModal'
|
|
17
|
+
import { DashboardConditionModal } from './DashboardConditionModal'
|
|
18
|
+
import { DashboardConditionSummary } from './DashboardConditionSummary'
|
|
17
19
|
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
20
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
18
21
|
import { iconHash } from '../helpers/iconHash'
|
|
19
22
|
import _ from 'lodash'
|
|
20
23
|
import { Visualization } from '@cdc/core/types/Visualization'
|
|
21
24
|
import { labelHash } from '@cdc/core/helpers/labelHash'
|
|
25
|
+
import { removeDashboardFilter } from '../helpers/removeDashboardFilter'
|
|
26
|
+
import { dashboardConditionsSupportedForRow, remapRowTargetsInSharedFilters } from '../helpers/dashboardFilterTargets'
|
|
27
|
+
import { getColumnPrimaryWidget, getColumnWidgetKeys } from '../helpers/dashboardColumnWidgets'
|
|
28
|
+
import { createCoveId } from '@cdc/core/helpers/createCoveId'
|
|
22
29
|
|
|
23
30
|
type RowMenuProps = {
|
|
24
31
|
rowIdx: number
|
|
@@ -45,7 +52,7 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
45
52
|
const newRows = _.cloneDeep(rows)
|
|
46
53
|
newRows[rowIdx].toggle = toggle
|
|
47
54
|
const rowColumns = newRows[rowIdx].columns
|
|
48
|
-
const columnsWithWidgets = rowColumns.filter(c => c.
|
|
55
|
+
const columnsWithWidgets = rowColumns.filter(c => getColumnWidgetKeys(c).length > 0)
|
|
49
56
|
|
|
50
57
|
const totalWidgets = columnsWithWidgets.length
|
|
51
58
|
if (totalWidgets > layout.length) {
|
|
@@ -58,7 +65,8 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
58
65
|
|
|
59
66
|
// Adds placeholder column name that defaults to the visualization type.
|
|
60
67
|
newRows[rowIdx].columns.forEach((col, idx) => {
|
|
61
|
-
|
|
68
|
+
const primaryWidget = getColumnPrimaryWidget(col)
|
|
69
|
+
col.toggleName = col.toggleName || labelHash[config.visualizations[primaryWidget]?.type] || undefined
|
|
62
70
|
})
|
|
63
71
|
|
|
64
72
|
newRows[rowIdx].columns = layout.map((width, colIndex) => {
|
|
@@ -87,10 +95,20 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
87
95
|
rows[newIdx] = row
|
|
88
96
|
rows[rowIdx] = temp
|
|
89
97
|
|
|
90
|
-
rows
|
|
91
|
-
rows[
|
|
98
|
+
const existingRowUuids = rows.map(row => row.uuid).filter(uuid => uuid !== undefined)
|
|
99
|
+
rows[newIdx].uuid = createCoveId('row', { existingIds: existingRowUuids })
|
|
100
|
+
rows[rowIdx].uuid = createCoveId('row', { existingIds: [...existingRowUuids, rows[newIdx].uuid] })
|
|
92
101
|
|
|
93
|
-
|
|
102
|
+
const remappedSharedFilters = remapRowTargetsInSharedFilters(
|
|
103
|
+
config.dashboard.sharedFilters || [],
|
|
104
|
+
targetRowIndex => {
|
|
105
|
+
if (targetRowIndex === rowIdx) return newIdx
|
|
106
|
+
if (targetRowIndex === newIdx) return rowIdx
|
|
107
|
+
return targetRowIndex
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
updateConfig({ ...config, rows, dashboard: { ...config.dashboard, sharedFilters: remappedSharedFilters } })
|
|
94
112
|
|
|
95
113
|
// TODO: Migrate this animation to a React animation library once one is selected for COVE. This is pretty minor so can stay for now.
|
|
96
114
|
let calcRowMove = dir === 'down' ? 202 : -202
|
|
@@ -99,6 +117,8 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
99
117
|
let rowEle = document.querySelector("[data-row-id='" + rowIdx + "']") as HTMLElement
|
|
100
118
|
let rowNewEle = document.querySelector("[data-row-id='" + newIdx + "']") as HTMLElement
|
|
101
119
|
|
|
120
|
+
if (!rowEle || !rowNewEle) return
|
|
121
|
+
|
|
102
122
|
rowEle.style.pointerEvents = 'none'
|
|
103
123
|
rowNewEle.style.pointerEvents = 'none'
|
|
104
124
|
rowEle.style.top = calcRowMove + 'px'
|
|
@@ -119,19 +139,47 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
119
139
|
|
|
120
140
|
const deleteRow = () => {
|
|
121
141
|
let newVisualizations = { ...config.visualizations }
|
|
142
|
+
let newSharedFilters = remapRowTargetsInSharedFilters(config.dashboard.sharedFilters || [], targetRowIndex => {
|
|
143
|
+
if (targetRowIndex === rowIdx) return null
|
|
144
|
+
if (targetRowIndex > rowIdx) return targetRowIndex - 1
|
|
145
|
+
return targetRowIndex
|
|
146
|
+
})
|
|
122
147
|
|
|
123
|
-
|
|
124
|
-
if (rows[rowIdx] && rows[rowIdx].columns && rows[rowIdx].columns.length && config.visualizations) {
|
|
148
|
+
if (rows[rowIdx]?.columns?.length && config.visualizations) {
|
|
125
149
|
rows[rowIdx].columns.forEach(column => {
|
|
126
|
-
|
|
127
|
-
delete newVisualizations[
|
|
128
|
-
|
|
150
|
+
getColumnWidgetKeys(column).forEach(widgetKey => {
|
|
151
|
+
delete newVisualizations[widgetKey]
|
|
152
|
+
newSharedFilters.forEach(sharedFilter => {
|
|
153
|
+
if (sharedFilter.usedBy) {
|
|
154
|
+
sharedFilter.usedBy = sharedFilter.usedBy.filter(uid => uid !== widgetKey)
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
})
|
|
129
158
|
})
|
|
130
159
|
}
|
|
131
160
|
|
|
132
|
-
rows.splice(rowIdx, 1)
|
|
161
|
+
rows.splice(rowIdx, 1)
|
|
162
|
+
|
|
163
|
+
// Remove shared filters no longer referenced by any remaining dashboardFilters widget,
|
|
164
|
+
// iterating in reverse so removals don't invalidate earlier indices. removeDashboardFilter
|
|
165
|
+
// shifts sharedFilterIndexes in all remaining vizs so indices stay consistent.
|
|
166
|
+
let currentFilters = newSharedFilters
|
|
167
|
+
let currentVizs = newVisualizations
|
|
168
|
+
for (let i = currentFilters.length - 1; i >= 0; i--) {
|
|
169
|
+
const isReferenced = Object.values(currentVizs).some(
|
|
170
|
+
v => v.type === 'dashboardFilters' && (v as any).sharedFilterIndexes?.includes(i)
|
|
171
|
+
)
|
|
172
|
+
if (!isReferenced) {
|
|
173
|
+
;[currentFilters, currentVizs] = removeDashboardFilter(i, currentFilters, currentVizs as any)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
133
176
|
|
|
134
|
-
updateConfig({
|
|
177
|
+
updateConfig({
|
|
178
|
+
...config,
|
|
179
|
+
rows,
|
|
180
|
+
visualizations: currentVizs,
|
|
181
|
+
dashboard: { ...config.dashboard, sharedFilters: currentFilters }
|
|
182
|
+
})
|
|
135
183
|
}
|
|
136
184
|
|
|
137
185
|
const layoutList = [
|
|
@@ -191,8 +239,9 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
191
239
|
<nav className='row-menu'>
|
|
192
240
|
<ul className='row-menu__flyout'>{layoutList}</ul>
|
|
193
241
|
{isMultiColumn && (
|
|
194
|
-
<
|
|
195
|
-
|
|
242
|
+
<Button
|
|
243
|
+
variant={row.equalHeight ? 'primary' : undefined}
|
|
244
|
+
className='row-menu__btn border-0'
|
|
196
245
|
title={row.equalHeight ? 'Disable Equal Height Rows' : 'Enable Equal Height Rows'}
|
|
197
246
|
onClick={toggleEqualHeight}
|
|
198
247
|
>
|
|
@@ -201,33 +250,36 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
201
250
|
<rect x='14' y='2' width='9' height='14' rx='1' />
|
|
202
251
|
<line x1='0' y1='19' x2='24' y2='19' stroke='#fff' strokeWidth='2' strokeDasharray='3 2' />
|
|
203
252
|
</svg>
|
|
204
|
-
</
|
|
253
|
+
</Button>
|
|
205
254
|
)}
|
|
206
255
|
<div className='spacer'></div>
|
|
207
|
-
<
|
|
208
|
-
|
|
256
|
+
<Button
|
|
257
|
+
variant='primary'
|
|
258
|
+
className='row-menu__btn border-0'
|
|
209
259
|
title='Move Row Up'
|
|
210
260
|
onClick={() => moveRow('up')}
|
|
211
261
|
disabled={rowIdx === 0}
|
|
212
262
|
>
|
|
213
263
|
<Icon display='caretUp' color='#fff' size={25} />
|
|
214
|
-
</
|
|
215
|
-
<
|
|
216
|
-
|
|
264
|
+
</Button>
|
|
265
|
+
<Button
|
|
266
|
+
variant='primary'
|
|
267
|
+
className='row-menu__btn border-0'
|
|
217
268
|
title='Move Row Down'
|
|
218
269
|
onClick={() => moveRow('down')}
|
|
219
270
|
disabled={rowIdx + 1 === rows.length}
|
|
220
271
|
>
|
|
221
272
|
<Icon display='caretDown' color='#fff' size={25} />
|
|
222
|
-
</
|
|
223
|
-
<
|
|
224
|
-
|
|
273
|
+
</Button>
|
|
274
|
+
<Button
|
|
275
|
+
variant='danger'
|
|
276
|
+
className='row-menu__btn row-menu__btn--remove border-0'
|
|
225
277
|
title='Delete Row'
|
|
226
278
|
onClick={deleteRow}
|
|
227
279
|
disabled={rowIdx === 0 && rows.length === 1}
|
|
228
280
|
>
|
|
229
281
|
<Icon display='close' color='#fff' size={25} />
|
|
230
|
-
</
|
|
282
|
+
</Button>
|
|
231
283
|
</nav>
|
|
232
284
|
)
|
|
233
285
|
}
|
|
@@ -236,20 +288,44 @@ type RowProps = { row: ConfigRow; idx: number; uuid: number | string }
|
|
|
236
288
|
|
|
237
289
|
const Row: React.FC<RowProps> = ({ row, idx: rowIdx, uuid }) => {
|
|
238
290
|
const { overlay } = useGlobalContext()
|
|
291
|
+
const supportsDashboardConditions = dashboardConditionsSupportedForRow(row)
|
|
292
|
+
const hasDashboardCondition = !!row.dashboardCondition
|
|
293
|
+
|
|
239
294
|
return (
|
|
240
295
|
<>
|
|
241
296
|
<div className='builder-row' data-row-id={rowIdx}>
|
|
242
297
|
<RowMenu rowIdx={rowIdx} />
|
|
243
|
-
<span className='
|
|
244
|
-
<
|
|
298
|
+
<span className='builder-row__label'>Row {rowIdx + 1}</span>
|
|
299
|
+
<Button
|
|
245
300
|
title='Configure Data'
|
|
246
|
-
className='btn btn-configure-row'
|
|
301
|
+
className='btn-configure-row btn-configure-row--data'
|
|
247
302
|
onClick={() => {
|
|
248
303
|
overlay?.actions.openOverlay(<DataDesignerModal rowIndex={rowIdx} />)
|
|
249
304
|
}}
|
|
250
305
|
>
|
|
251
306
|
{iconHash['gearMulti']}
|
|
252
|
-
</
|
|
307
|
+
</Button>
|
|
308
|
+
<Button
|
|
309
|
+
title={
|
|
310
|
+
supportsDashboardConditions
|
|
311
|
+
? 'Configure Dashboard Condition'
|
|
312
|
+
: 'Dashboard conditions are not available for toggle or multi-visualization rows'
|
|
313
|
+
}
|
|
314
|
+
className={`btn-configure-row btn-configure-row--condition${hasDashboardCondition ? ' is-active' : ''}`}
|
|
315
|
+
disabled={!supportsDashboardConditions}
|
|
316
|
+
onClick={() => {
|
|
317
|
+
overlay?.actions.openOverlay(<DashboardConditionModal rowIndex={rowIdx} />)
|
|
318
|
+
}}
|
|
319
|
+
>
|
|
320
|
+
{iconHash['condition']}
|
|
321
|
+
</Button>
|
|
322
|
+
{row.dashboardCondition && (
|
|
323
|
+
<DashboardConditionSummary
|
|
324
|
+
className='dashboard-condition-summary--row'
|
|
325
|
+
dashboardCondition={row.dashboardCondition}
|
|
326
|
+
rowIndex={rowIdx}
|
|
327
|
+
/>
|
|
328
|
+
)}
|
|
253
329
|
<div className='column-container'>
|
|
254
330
|
{row.columns
|
|
255
331
|
.filter(column => column.width)
|