Skip to content

Commit 9caf306

Browse files
committed
chore: testing improvements
1 parent dcd36bd commit 9caf306

8 files changed

Lines changed: 312 additions & 84 deletions

File tree

classes/Visualizer/ChartBuilder/src/AIBuilder/api.js

Lines changed: 22 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55

66
const { ajaxUrl, nonce } = window.vizAIBuilder || {};
77

8-
async function post( action, body ) {
8+
async function post( action, body, options = {} ) {
9+
const { nonceOverride = null, omitEmpty = false } = options;
910
const form = new FormData();
1011
form.append( 'action', action );
11-
form.append( 'nonce', nonce );
12+
form.append( 'nonce', nonceOverride || nonce );
1213
for ( const [ key, val ] of Object.entries( body ) ) {
14+
if ( omitEmpty && ( val === null || val === undefined || val === '' ) ) {
15+
continue;
16+
}
1317
form.append( key, val );
1418
}
1519
const res = await fetch( ajaxUrl, { method: 'POST', body: form } );
@@ -51,61 +55,33 @@ export async function saveChart( chartId, title, code ) {
5155

5256
// ── Upload helpers ─────────────────────────────────────────────────────────────
5357

54-
/** Build a FormData for any upload call, including the per-chart upload nonce. */
55-
function uploadForm( action, chartId, uploadNonce, fields = {} ) {
56-
const form = new FormData();
57-
form.append( 'action', action );
58-
form.append( 'nonce', uploadNonce );
59-
form.append( 'chart_id', chartId );
60-
for ( const [ key, val ] of Object.entries( fields ) ) {
61-
if ( val !== null && val !== undefined && val !== '' ) {
62-
form.append( key, val );
63-
}
64-
}
65-
return form;
66-
}
67-
68-
async function uploadFetch( form ) {
69-
const res = await fetch( ajaxUrl, { method: 'POST', body: form } );
70-
let json;
71-
try {
72-
json = await res.json();
73-
} catch {
74-
throw new Error( 'Server returned an unexpected response.' );
75-
}
76-
if ( ! json.success ) {
77-
throw new Error( json.data?.message || 'Upload failed.' );
78-
}
79-
return json.data;
80-
}
81-
8258
/** Upload pasted CSV string. Returns { series, data }. */
8359
export async function uploadCsvString( chartId, uploadNonce, csvData ) {
84-
const form = uploadForm( 'visualizer-ai-upload', chartId, uploadNonce, {
60+
return post( 'visualizer-ai-upload', {
61+
chart_id: chartId,
8562
source_type: 'csv_string',
8663
csv_data: csvData,
87-
} );
88-
return uploadFetch( form );
64+
}, { nonceOverride: uploadNonce, omitEmpty: true } );
8965
}
9066

9167
/** Upload a CSV or XLSX file. Returns { series, data }. */
9268
export async function uploadFile( chartId, uploadNonce, file ) {
9369
const ext = file.name.split( '.' ).pop().toLowerCase();
94-
const form = uploadForm( 'visualizer-ai-upload', chartId, uploadNonce, {
70+
return post( 'visualizer-ai-upload', {
71+
chart_id: chartId,
9572
source_type: ext === 'xlsx' ? 'xlsx_file' : 'csv_file',
96-
} );
97-
form.append( 'data_file', file );
98-
return uploadFetch( form );
73+
data_file: file,
74+
}, { nonceOverride: uploadNonce, omitEmpty: true } );
9975
}
10076

10177
/** Upload a remote CSV/XLSX URL. Returns { series, data }. */
10278
export async function uploadFileUrl( chartId, uploadNonce, url, schedule = '' ) {
103-
const form = uploadForm( 'visualizer-ai-upload', chartId, uploadNonce, {
79+
return post( 'visualizer-ai-upload', {
80+
chart_id: chartId,
10481
source_type: 'file_url',
10582
file_url: url,
10683
schedule: schedule,
107-
} );
108-
return uploadFetch( form );
84+
}, { nonceOverride: uploadNonce, omitEmpty: true } );
10985
}
11086

11187
/** Upload a JSON URL source. Returns { series, data }. */
@@ -115,7 +91,8 @@ export async function uploadJsonUrl( chartId, uploadNonce, params ) {
11591
auth = '', username = '', password = '', headers = '',
11692
schedule = '',
11793
} = params;
118-
const form = uploadForm( 'visualizer-ai-upload', chartId, uploadNonce, {
94+
return post( 'visualizer-ai-upload', {
95+
chart_id: chartId,
11996
source_type: 'json_url',
12097
json_url: url,
12198
json_root: root,
@@ -126,8 +103,7 @@ export async function uploadJsonUrl( chartId, uploadNonce, params ) {
126103
json_password: password,
127104
json_headers: headers,
128105
json_schedule: schedule,
129-
} );
130-
return uploadFetch( form );
106+
}, { nonceOverride: uploadNonce, omitEmpty: true } );
131107
}
132108

133109
// ── AI generation helpers ──────────────────────────────────────────────────────
@@ -168,7 +144,8 @@ export async function uploadDbQuery( chartId, uploadNonce, query, dbParams = {}
168144
host = '', port = 3306, name = '',
169145
username = '', password = '', type = 'mysql',
170146
} = dbParams;
171-
const form = uploadForm( 'visualizer-ai-upload', chartId, uploadNonce, {
147+
return post( 'visualizer-ai-upload', {
148+
chart_id: chartId,
172149
source_type: 'db_query',
173150
db_query: query,
174151
db_host: host,
@@ -177,6 +154,5 @@ export async function uploadDbQuery( chartId, uploadNonce, query, dbParams = {}
177154
db_username: username,
178155
db_password: password,
179156
db_type: type,
180-
} );
181-
return uploadFetch( form );
157+
}, { nonceOverride: uploadNonce, omitEmpty: true } );
182158
}

classes/Visualizer/ChartBuilder/src/AIBuilder/index.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* AI Builder — two-column layout matching layout.html design
33
*
44
* Left : Step 1 (DataSource) + Step 2 (Describe)
5-
* Right : Live preview sidebar (256px)
5+
* Right : Live preview sidebar (560px)
66
* Footer: Cancel + Generate / Publish actions
77
*/
88
import { useState, useEffect, useRef } from '@wordpress/element';
@@ -579,6 +579,12 @@ export default function AIBuilder( { onClose, initialChartId = null } ) {
579579
<Text fontSize="12px" color="#b45309">{ editorError }</Text>
580580
</Box>
581581
) }
582+
<Text fontSize="11px" color={ C.gray2 }>
583+
{ __( 'Need a refresher? Read the D3 documentation to understand selections, scales, and shapes:', 'visualizer' ) }{' '}
584+
<a href="https://d3js.org/getting-started" target="_blank" rel="noopener noreferrer">
585+
{ __( 'd3js.org/getting-started', 'visualizer' ) }
586+
</a>
587+
</Text>
582588
<Flex align="center" justify="flex-end" gap="2">
583589
<PillBtn onClick={ closeEditor } bg="white" color={ C.gray1 } border="1.5px solid" borderColor={ C.border }>
584590
{ __( 'Cancel', 'visualizer' ) }
@@ -628,16 +634,22 @@ export default function AIBuilder( { onClose, initialChartId = null } ) {
628634
>
629635
<Box
630636
as="textarea"
631-
w="100%" h="68px"
637+
w="100%" minH="68px"
632638
border="none" outline="none"
633639
bg="transparent"
634640
p="10px 12px"
635641
fontSize="13px" fontFamily="inherit"
636-
resize="none"
642+
resize="vertical"
637643
color={ C.dark }
638644
placeholder={ __( 'e.g. Create a grouped bar chart comparing revenue and profit by quarter', 'visualizer' ) }
639645
value={ prompt }
640646
onChange={ isLocked ? undefined : ( e ) => setPrompt( e.target.value ) }
647+
onKeyDown={ isLocked ? undefined : ( e ) => {
648+
if ( e.key === 'Enter' && ! e.shiftKey ) {
649+
e.preventDefault();
650+
if ( canGenerate ) handleGenerate();
651+
}
652+
} }
641653
disabled={ isLocked }
642654
display="block"
643655
sx={ { '&::placeholder': { color: C.gray3 } } }
@@ -700,6 +712,9 @@ export default function AIBuilder( { onClose, initialChartId = null } ) {
700712
) }
701713
</Flex>
702714
</Box>
715+
<Text fontSize="11px" color={ C.gray2 }>
716+
{ __( 'Press Enter to generate. Shift + Enter for a new line.', 'visualizer' ) }
717+
</Text>
703718

704719
<input
705720
ref={ refImageInputRef }
@@ -759,7 +774,7 @@ export default function AIBuilder( { onClose, initialChartId = null } ) {
759774
</Box>
760775

761776
{ /* ── Right: rebuilt preview panel ─────────────────────────────── */ }
762-
<Box w="460px" flexShrink={ 0 } display="flex" flexDirection="column" bg="#f7f7f8" borderLeft="1px solid #e2e3e6">
777+
<Box w="560px" flexShrink={ 0 } display="flex" flexDirection="column" bg="#f7f7f8" borderLeft="1px solid #e2e3e6">
763778
<Flex align="center" justify="space-between" px="5" py="4" borderBottom="1px solid #e2e3e6">
764779
<Box>
765780
<Text fontSize="12px" fontWeight="700" color={ C.dark } letterSpacing="0.02em">

classes/Visualizer/ChartBuilder/src/ChooserModal.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@
55
* 1. Classic Builder — opens the existing iframe-based wizard
66
* 2. AI Chart Builder — opens the new D3-powered React wizard
77
*/
8+
import { useEffect, useState } from '@wordpress/element';
89
import { __ } from '@wordpress/i18n';
910

1011
function ChooserModal( { isOpen, onClassic, onAIBuilder, onClose } ) {
12+
const [ rememberChoice, setRememberChoice ] = useState( false );
13+
14+
useEffect( () => {
15+
if ( isOpen ) {
16+
setRememberChoice( false );
17+
}
18+
}, [ isOpen ] );
19+
1120
if ( ! isOpen ) {
1221
return null;
1322
}
@@ -34,7 +43,10 @@ function ChooserModal( { isOpen, onClassic, onAIBuilder, onClose } ) {
3443

3544
<div className="viz-chooser-options">
3645

37-
<button className="viz-chooser-option viz-chooser-option--ai" onClick={ onAIBuilder }>
46+
<button
47+
className="viz-chooser-option viz-chooser-option--ai"
48+
onClick={ () => onAIBuilder( rememberChoice ) }
49+
>
3850
<span className="viz-chooser-option__badge">
3951
{ __( 'New', 'visualizer' ) }
4052
</span>
@@ -51,7 +63,10 @@ function ChooserModal( { isOpen, onClassic, onAIBuilder, onClose } ) {
5163
</span>
5264
</button>
5365

54-
<button className="viz-chooser-option" onClick={ onClassic }>
66+
<button
67+
className="viz-chooser-option"
68+
onClick={ () => onClassic( rememberChoice ) }
69+
>
5570
<span className="viz-chooser-option__icon viz-chooser-option__icon--classic">
5671
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
5772
<rect x="3" y="14" width="4" height="7" rx="1" fill="currentColor"/>
@@ -69,6 +84,20 @@ function ChooserModal( { isOpen, onClassic, onAIBuilder, onClose } ) {
6984

7085
</div>
7186

87+
<div className="viz-chooser-remember">
88+
<label className="viz-chooser-remember__label">
89+
<input
90+
type="checkbox"
91+
checked={ rememberChoice }
92+
onChange={ ( event ) => setRememberChoice( event.target.checked ) }
93+
/>
94+
<span>{ __( 'Don\'t ask me again', 'visualizer' ) }</span>
95+
</label>
96+
<p className="viz-chooser-remember__help">
97+
{ __( 'We will open your selected builder by default next time.', 'visualizer' ) }
98+
</p>
99+
</div>
100+
72101
<button className="viz-chooser-cancel" onClick={ onClose }>
73102
{ __( 'Cancel', 'visualizer' ) }
74103
</button>

classes/Visualizer/ChartBuilder/src/index.js

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,24 @@ import ChooserModal from './ChooserModal';
1111
import AIBuilder from './AIBuilder/index';
1212
import './style.scss';
1313

14+
const CHOOSER_STORAGE_KEY = 'viz_chart_builder_default';
15+
16+
function getStoredBuilderChoice() {
17+
try {
18+
return window.localStorage.getItem( CHOOSER_STORAGE_KEY );
19+
} catch ( err ) {
20+
return null;
21+
}
22+
}
23+
24+
function setStoredBuilderChoice( choice ) {
25+
try {
26+
window.localStorage.setItem( CHOOSER_STORAGE_KEY, choice );
27+
} catch ( err ) {
28+
// ignore storage failures
29+
}
30+
}
31+
1432
function ChartBuilderApp() {
1533
// mode: 'hidden' | 'chooser' | 'ai-builder'
1634
const [ mode, setMode ] = useState( 'hidden' );
@@ -21,23 +39,45 @@ function ChartBuilderApp() {
2139
window.vizOpenChartChooser = ( cb ) => {
2240
setEditChartId( null );
2341
setClassicCallback( () => cb );
42+
const storedChoice = getStoredBuilderChoice();
43+
if ( storedChoice === 'classic' ) {
44+
cb();
45+
return;
46+
}
47+
if ( storedChoice === 'ai' ) {
48+
setMode( 'ai-builder' );
49+
return;
50+
}
2451
setMode( 'chooser' );
2552
};
2653
window.vizOpenAIBuilderEdit = ( chartId ) => {
2754
setEditChartId( String( chartId ) );
2855
setMode( 'ai-builder' );
2956
};
57+
window.vizOpenAIBuilderNew = () => {
58+
setEditChartId( null );
59+
setMode( 'ai-builder' );
60+
};
3061
return () => {
3162
delete window.vizOpenChartChooser;
3263
delete window.vizOpenAIBuilderEdit;
64+
delete window.vizOpenAIBuilderNew;
3365
};
3466
}, [] );
3567

36-
function handleClassic() {
68+
function handleClassic( rememberChoice = false ) {
69+
if ( rememberChoice ) {
70+
setStoredBuilderChoice( 'classic' );
71+
}
3772
setMode( 'hidden' );
3873
if ( typeof classicCallback === 'function' ) classicCallback();
3974
}
40-
function handleAIBuilder() { setMode( 'ai-builder' ); }
75+
function handleAIBuilder( rememberChoice = false ) {
76+
if ( rememberChoice ) {
77+
setStoredBuilderChoice( 'ai' );
78+
}
79+
setMode( 'ai-builder' );
80+
}
4181
function handleClose() { setMode( 'hidden' ); setEditChartId( null ); }
4282

4383
return (

classes/Visualizer/ChartBuilder/src/style.scss

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,34 @@
142142
line-height: 1.5;
143143
}
144144

145+
.viz-chooser-remember {
146+
display: flex;
147+
flex-direction: column;
148+
align-items: center;
149+
gap: 6px;
150+
margin-bottom: 14px;
151+
}
152+
153+
.viz-chooser-remember__label {
154+
display: inline-flex;
155+
align-items: center;
156+
gap: 8px;
157+
font-size: 13px;
158+
color: #1d2327;
159+
cursor: pointer;
160+
}
161+
162+
.viz-chooser-remember__label input {
163+
width: 16px;
164+
height: 16px;
165+
}
166+
167+
.viz-chooser-remember__help {
168+
margin: 0;
169+
font-size: 12px;
170+
color: #646970;
171+
}
172+
145173
.viz-chooser-cancel {
146174
background: none;
147175
border: none;

0 commit comments

Comments
 (0)