@cdc/editor 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/dist/cdceditor-CY9IcPSi.es.js +6 -0
- package/dist/cdceditor-DlpiY3fQ.es.js +4 -0
- package/dist/cdceditor.js +78238 -74548
- package/example/private/dashboard-filter-issue/dashboard-filter-issue.json +957 -0
- package/package.json +9 -9
- package/src/_stories/Editor.stories.tsx +124 -0
- package/src/assets.d.ts +4 -0
- package/src/components/ChooseTab.test.tsx +64 -1
- package/src/components/ChooseTab.tsx +82 -4
- package/src/components/DataImport/components/DataImport.tsx +135 -50
- package/src/components/DataImport/components/SampleData.test.tsx +16 -0
- package/src/components/DataImport/components/SampleData.tsx +6 -0
- package/src/components/DataImport/components/samples/valid-heatmap-varicella-cases.csv +13 -0
- package/src/components/DataImport/helpers/applyAutoDetectedDateParseFormat.ts +35 -0
- package/src/components/DataImport/tests/applyAutoDetectedDateParseFormat.test.ts +128 -0
- package/src/components/PreviewDataTable.test.tsx +184 -0
- package/src/components/PreviewDataTable.tsx +18 -7
- package/src/components/modal/Confirmation.jsx +5 -4
- package/src/scss/choose-vis-tab.scss +7 -1
- package/src/scss/main.scss +14 -6
- package/dist/cdceditor-vr9HZwRt.es.js +0 -6
- package/example/data-horizontal-filters.json +0 -8
- package/example/data-horizontal-multiseries-filters.json +0 -18
- package/example/data-horizontal-multiseries.json +0 -6
- package/example/data-horizontal.json +0 -4
- package/example/data-vertical-filters.json +0 -10
- package/example/data-vertical-multiseries-filters.json +0 -18
- package/example/data-vertical-multiseries-multirow-filters.json +0 -50
- package/example/data-vertical-multiseries-multirow.json +0 -14
- package/example/data-vertical-multiseries.json +0 -6
- package/example/data-vertical.json +0 -6
- package/example/region-map.json +0 -33
- package/example/test.json +0 -110280
- package/example/valid-county-data.json +0 -3049
- package/example/valid-scatterplot.csv +0 -17
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react'
|
|
3
|
+
import ConfigContext, { EditorDispatchContext } from '@cdc/core/contexts/EditorContext'
|
|
4
|
+
import PreviewDataTable from './PreviewDataTable'
|
|
5
|
+
|
|
6
|
+
describe('PreviewDataTable', () => {
|
|
7
|
+
const renderPreview = config => {
|
|
8
|
+
const dispatch = vi.fn()
|
|
9
|
+
|
|
10
|
+
return render(
|
|
11
|
+
<ConfigContext.Provider
|
|
12
|
+
value={
|
|
13
|
+
{
|
|
14
|
+
config,
|
|
15
|
+
errors: [],
|
|
16
|
+
currentViewport: 'lg',
|
|
17
|
+
globalActive: 1,
|
|
18
|
+
setTempConfig: vi.fn()
|
|
19
|
+
} as any
|
|
20
|
+
}
|
|
21
|
+
>
|
|
22
|
+
<EditorDispatchContext.Provider value={dispatch}>
|
|
23
|
+
<PreviewDataTable />
|
|
24
|
+
</EditorDispatchContext.Provider>
|
|
25
|
+
</ConfigContext.Provider>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
it('updates the rendered preview rows when the dashboard preview source changes', async () => {
|
|
30
|
+
const initialConfig = {
|
|
31
|
+
type: 'dashboard',
|
|
32
|
+
datasets: {
|
|
33
|
+
source_a: {
|
|
34
|
+
data: [{ label: 'Old Source Row', value: 'A' }],
|
|
35
|
+
preview: true
|
|
36
|
+
},
|
|
37
|
+
source_b: {
|
|
38
|
+
data: [{ label: 'Secondary Row', value: 'B' }],
|
|
39
|
+
preview: false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const nextConfig = {
|
|
45
|
+
...initialConfig,
|
|
46
|
+
datasets: {
|
|
47
|
+
source_b: {
|
|
48
|
+
data: [{ label: 'New Source Row', value: 'B2' }],
|
|
49
|
+
preview: true
|
|
50
|
+
},
|
|
51
|
+
source_a: {
|
|
52
|
+
data: [{ label: 'Old Source Row', value: 'A' }],
|
|
53
|
+
preview: false
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const view = renderPreview(initialConfig)
|
|
59
|
+
|
|
60
|
+
expect(await screen.findByText('Old Source Row')).toBeInTheDocument()
|
|
61
|
+
expect(screen.queryByText('New Source Row')).not.toBeInTheDocument()
|
|
62
|
+
|
|
63
|
+
view.rerender(
|
|
64
|
+
<ConfigContext.Provider
|
|
65
|
+
value={
|
|
66
|
+
{
|
|
67
|
+
config: nextConfig,
|
|
68
|
+
errors: [],
|
|
69
|
+
currentViewport: 'lg',
|
|
70
|
+
globalActive: 1,
|
|
71
|
+
setTempConfig: vi.fn()
|
|
72
|
+
} as any
|
|
73
|
+
}
|
|
74
|
+
>
|
|
75
|
+
<EditorDispatchContext.Provider value={vi.fn()}>
|
|
76
|
+
<PreviewDataTable />
|
|
77
|
+
</EditorDispatchContext.Provider>
|
|
78
|
+
</ConfigContext.Provider>
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
await waitFor(() => {
|
|
82
|
+
expect(screen.getByText('New Source Row')).toBeInTheDocument()
|
|
83
|
+
})
|
|
84
|
+
expect(screen.queryByText('Old Source Row')).not.toBeInTheDocument()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('shows another loaded dataset after the active preview dataset is removed', async () => {
|
|
88
|
+
const initialConfig = {
|
|
89
|
+
type: 'dashboard',
|
|
90
|
+
datasets: {
|
|
91
|
+
source_a: {
|
|
92
|
+
data: [{ label: 'Removed Source Row', value: 'A' }],
|
|
93
|
+
preview: true
|
|
94
|
+
},
|
|
95
|
+
source_b: {
|
|
96
|
+
data: [{ label: 'Fallback Source Row', value: 'B' }],
|
|
97
|
+
preview: false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const nextConfig = {
|
|
103
|
+
type: 'dashboard',
|
|
104
|
+
datasets: {
|
|
105
|
+
source_b: {
|
|
106
|
+
data: [{ label: 'Fallback Source Row', value: 'B' }],
|
|
107
|
+
preview: true
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const view = renderPreview(initialConfig)
|
|
113
|
+
|
|
114
|
+
expect(await screen.findByText('Removed Source Row')).toBeInTheDocument()
|
|
115
|
+
|
|
116
|
+
view.rerender(
|
|
117
|
+
<ConfigContext.Provider
|
|
118
|
+
value={
|
|
119
|
+
{
|
|
120
|
+
config: nextConfig,
|
|
121
|
+
errors: [],
|
|
122
|
+
currentViewport: 'lg',
|
|
123
|
+
globalActive: 1,
|
|
124
|
+
setTempConfig: vi.fn()
|
|
125
|
+
} as any
|
|
126
|
+
}
|
|
127
|
+
>
|
|
128
|
+
<EditorDispatchContext.Provider value={vi.fn()}>
|
|
129
|
+
<PreviewDataTable />
|
|
130
|
+
</EditorDispatchContext.Provider>
|
|
131
|
+
</ConfigContext.Provider>
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
await waitFor(() => {
|
|
135
|
+
expect(screen.getByText('Fallback Source Row')).toBeInTheDocument()
|
|
136
|
+
})
|
|
137
|
+
expect(screen.queryByText('Removed Source Row')).not.toBeInTheDocument()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('returns to the initial empty preview when the final dataset is removed', async () => {
|
|
141
|
+
const initialConfig = {
|
|
142
|
+
type: 'dashboard',
|
|
143
|
+
datasets: {
|
|
144
|
+
source_a: {
|
|
145
|
+
data: [{ label: 'Last Source Row', value: 'A' }],
|
|
146
|
+
preview: true
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const nextConfig = {
|
|
152
|
+
type: 'dashboard',
|
|
153
|
+
datasets: {}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const view = renderPreview(initialConfig)
|
|
157
|
+
|
|
158
|
+
expect(await screen.findByText('Last Source Row')).toBeInTheDocument()
|
|
159
|
+
|
|
160
|
+
view.rerender(
|
|
161
|
+
<ConfigContext.Provider
|
|
162
|
+
value={
|
|
163
|
+
{
|
|
164
|
+
config: nextConfig,
|
|
165
|
+
errors: [],
|
|
166
|
+
currentViewport: 'lg',
|
|
167
|
+
globalActive: 1,
|
|
168
|
+
setTempConfig: vi.fn()
|
|
169
|
+
} as any
|
|
170
|
+
}
|
|
171
|
+
>
|
|
172
|
+
<EditorDispatchContext.Provider value={vi.fn()}>
|
|
173
|
+
<PreviewDataTable />
|
|
174
|
+
</EditorDispatchContext.Provider>
|
|
175
|
+
</ConfigContext.Provider>
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
await waitFor(() => {
|
|
179
|
+
expect(screen.getByText('No Data')).toBeInTheDocument()
|
|
180
|
+
})
|
|
181
|
+
expect(screen.getByText('Import data to preview')).toBeInTheDocument()
|
|
182
|
+
expect(screen.queryByText('Last Source Row')).not.toBeInTheDocument()
|
|
183
|
+
})
|
|
184
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useContext, useMemo,
|
|
1
|
+
import React, { useState, useContext, useMemo, useEffect, useRef, memo } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
useTable,
|
|
4
4
|
useBlockLayout,
|
|
@@ -18,7 +18,7 @@ import { GrFormPrevious } from 'react-icons/gr'
|
|
|
18
18
|
import validateFipsCodeLength from '@cdc/core/helpers/validateFipsCodeLength'
|
|
19
19
|
import { errorMessages } from '../helpers/errorMessages'
|
|
20
20
|
import { DataSet } from '@cdc/core/types/DataSet'
|
|
21
|
-
import
|
|
21
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
22
22
|
|
|
23
23
|
const TableFilter = memo(({ globalFilter, setGlobalFilter = () => {}, disabled = false }: any) => {
|
|
24
24
|
const [filterValue, setFilterValue] = useState(globalFilter ?? '')
|
|
@@ -61,25 +61,27 @@ const Footer = memo(({ previousPage, nextPage, canPreviousPage, canNextPage, pag
|
|
|
61
61
|
<footer className='data-table-pagination mt-2'>
|
|
62
62
|
<ul>
|
|
63
63
|
<li>
|
|
64
|
-
<
|
|
64
|
+
<Button
|
|
65
65
|
onClick={() => previousPage()}
|
|
66
66
|
className='btn btn-prev display-flex align-items-center justify-content-center'
|
|
67
67
|
disabled={!canPreviousPage}
|
|
68
68
|
title='Previous Page'
|
|
69
|
+
flexCenter
|
|
69
70
|
>
|
|
70
71
|
{' '}
|
|
71
72
|
<GrFormPrevious />
|
|
72
|
-
</
|
|
73
|
+
</Button>
|
|
73
74
|
</li>
|
|
74
75
|
<li className='me-2'>
|
|
75
|
-
<
|
|
76
|
+
<Button
|
|
76
77
|
onClick={() => nextPage()}
|
|
77
78
|
className='btn btn-next display-flex align-items-center justify-content-center'
|
|
78
79
|
disabled={!canNextPage}
|
|
79
80
|
title='Next Page'
|
|
81
|
+
flexCenter
|
|
80
82
|
>
|
|
81
83
|
<MdNavigateNext />
|
|
82
|
-
</
|
|
84
|
+
</Button>
|
|
83
85
|
</li>
|
|
84
86
|
</ul>
|
|
85
87
|
<span>
|
|
@@ -110,7 +112,12 @@ const PreviewDataTable = () => {
|
|
|
110
112
|
return normalizedData
|
|
111
113
|
}
|
|
112
114
|
const setTableData = td => {
|
|
113
|
-
if (!Array.isArray(td) || td.length === 0)
|
|
115
|
+
if (!Array.isArray(td) || td.length === 0) {
|
|
116
|
+
// When the active dataset is removed, clear the cached source so the placeholder can render again.
|
|
117
|
+
lastDataSourceRef.current = null
|
|
118
|
+
_setTableData(null)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
114
121
|
if (lastDataSourceRef.current === td) return
|
|
115
122
|
|
|
116
123
|
lastDataSourceRef.current = td
|
|
@@ -142,6 +149,10 @@ const PreviewDataTable = () => {
|
|
|
142
149
|
const loadData = async () => {
|
|
143
150
|
if (!config.data) {
|
|
144
151
|
if (config.type === 'dashboard') {
|
|
152
|
+
if (!previewData) {
|
|
153
|
+
setTableData(null)
|
|
154
|
+
return
|
|
155
|
+
}
|
|
145
156
|
await handleDashboardData(config.datasets)
|
|
146
157
|
} else {
|
|
147
158
|
if (config.dataUrl) {
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
2
3
|
|
|
3
4
|
export const ConfirmationModal = props => {
|
|
4
5
|
return (
|
|
5
6
|
<>
|
|
6
7
|
<p className='message'>{props.message}</p>
|
|
7
8
|
<div className='confirmation-buttons'>
|
|
8
|
-
<
|
|
9
|
+
<Button type='button' className='btn btn-inline' onClick={props.onCancel}>
|
|
9
10
|
No
|
|
10
|
-
</
|
|
11
|
-
<
|
|
11
|
+
</Button>
|
|
12
|
+
<Button type='button' className='btn btn-inline' onClick={props.onConfirm}>
|
|
12
13
|
Yes
|
|
13
|
-
</
|
|
14
|
+
</Button>
|
|
14
15
|
</div>
|
|
15
16
|
</>
|
|
16
17
|
)
|
|
@@ -84,7 +84,8 @@
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
svg
|
|
87
|
+
svg,
|
|
88
|
+
.choose-vis__heatmap-icon {
|
|
88
89
|
display: block;
|
|
89
90
|
margin: 0 auto;
|
|
90
91
|
box-sizing: border-box;
|
|
@@ -92,6 +93,11 @@
|
|
|
92
93
|
height: 80px;
|
|
93
94
|
flex-shrink: 0;
|
|
94
95
|
}
|
|
96
|
+
|
|
97
|
+
.choose-vis__heatmap-icon {
|
|
98
|
+
object-fit: contain;
|
|
99
|
+
mix-blend-mode: multiply;
|
|
100
|
+
}
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
}
|
package/src/scss/main.scss
CHANGED
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
background: #f2f2f2;
|
|
32
32
|
line-height: 3rem;
|
|
33
33
|
border-bottom: 1px solid var(--lightGray);
|
|
34
|
-
color: #333;
|
|
35
34
|
flex-grow: 1;
|
|
36
35
|
flex-shrink: 0;
|
|
37
36
|
width: 25%;
|
|
@@ -202,9 +201,22 @@
|
|
|
202
201
|
font-size: 1em;
|
|
203
202
|
display: block;
|
|
204
203
|
border-radius: 5px;
|
|
205
|
-
transition:
|
|
204
|
+
transition: background-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
|
|
206
205
|
cursor: pointer;
|
|
207
206
|
|
|
207
|
+
@media (hover: hover) {
|
|
208
|
+
&:hover:not(:disabled) {
|
|
209
|
+
background: #0b4778;
|
|
210
|
+
transform: translateY(-1px);
|
|
211
|
+
box-shadow: 0 0.5rem 1.1rem rgb(0 94 170 / 22%);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
&:active:not(:disabled) {
|
|
216
|
+
transform: translateY(0);
|
|
217
|
+
box-shadow: 0 0.15rem 0.45rem rgb(0 94 170 / 12%);
|
|
218
|
+
}
|
|
219
|
+
|
|
208
220
|
&.btn-inline {
|
|
209
221
|
display: inline-block;
|
|
210
222
|
margin: 10px 0 0 10px;
|
|
@@ -212,10 +224,6 @@
|
|
|
212
224
|
}
|
|
213
225
|
}
|
|
214
226
|
|
|
215
|
-
section.introText {
|
|
216
|
-
padding: 15px 0;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
227
|
section.footnotes {
|
|
220
228
|
border-top: 1px solid #ddd;
|
|
221
229
|
margin-top: 70px;
|