From 913aed1c31782a0848397064030088bf9e426268 Mon Sep 17 00:00:00 2001 From: jinbagi <4094424+jinbagi@users.noreply.github.com> Date: Sat, 4 Jul 2026 03:43:38 +0900 Subject: [PATCH] feat: add explicit json apply actions --- docs/design/json-editor-standard.md | 2 + e2e/tests/resource-required-templates.spec.ts | 26 +++++++++++- .../FormItemPlugins/PluginEditorDrawer.tsx | 37 +++++++++++++---- src/components/form/FormJsonTabs.tsx | 41 ++++++++++++++----- 4 files changed, 87 insertions(+), 19 deletions(-) diff --git a/docs/design/json-editor-standard.md b/docs/design/json-editor-standard.md index 656ce47e..e12e3b3f 100644 --- a/docs/design/json-editor-standard.md +++ b/docs/design/json-editor-standard.md @@ -23,6 +23,8 @@ These contexts have different actions, but they share one editing standard. ## Contextual Actions - Payload JSON submits the complete create payload through the form workflow. +- Same-draft editors such as Payload JSON and Plugin JSON provide an explicit + apply action for reviewing valid JSON edits in the paired visual editor. - Admin API JSON editors show identity fields separately as values managed by the Admin API path. The editable JSON excludes read-only fields, sends changed editable fields with PATCH, and verifies the saved resource with a follow-up diff --git a/e2e/tests/resource-required-templates.spec.ts b/e2e/tests/resource-required-templates.spec.ts index 3ea578d3..99f0dc88 100644 --- a/e2e/tests/resource-required-templates.spec.ts +++ b/e2e/tests/resource-required-templates.spec.ts @@ -15,7 +15,10 @@ * limitations under the License. */ import { test } from '@e2e/utils/test'; -import { uiGetMonacoEditor, uiSelectByLabel } from '@e2e/utils/ui'; +import { + uiGetMonacoEditor, + uiSelectByLabel, +} from '@e2e/utils/ui'; import { expect, type Locator, type Page } from '@playwright/test'; const requiredFields = (page: Page) => @@ -55,6 +58,21 @@ test('create forms show conditional required fields and minimal JSON templates', await page.locator('input[name="uri"]').fill('/orders'); await expect.poll(() => requiredFields(page)).not.toContain('uris'); + await page.getByRole('tab', { name: 'Payload JSON' }).click(); + await expect.poll(async () => { + const payload = JSON.parse(await readMonacoValue(page, routeJsonEditor)) as { + uri?: string; + }; + return payload.uri; + }).toBe('/orders'); + + await page.getByRole('button', { name: 'Apply to Visual Editor' }).click(); + await expect(page.getByRole('tab', { name: 'Visual Editor' })).toHaveAttribute( + 'aria-selected', + 'true' + ); + await expect(page.locator('input[name="uri"]')).toHaveValue('/orders'); + await page.goto('/ui/services/add'); await page.getByRole('tab', { name: 'Payload JSON' }).click(); await expect @@ -177,4 +195,10 @@ test('plugin add JSON prefills required fields from APISIX schema', async ({ count: 1, time_window: 1, }); + + await addPluginDialog.getByRole('button', { name: 'Apply to Fields' }).click(); + await expect(addPluginDialog.getByRole('tab', { name: 'Fields' })).toHaveAttribute( + 'aria-selected', + 'true' + ); }); diff --git a/src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx b/src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx index d812428f..648173e1 100644 --- a/src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx +++ b/src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx @@ -117,23 +117,39 @@ export const PluginEditorDrawer = (props: PluginEditorDrawerProps) => { setActiveTab(canUseForm ? 'form' : 'json'); }, [canUseForm, name]); + const applyJsonToFields = useCallback(() => { + try { + const parsed = JSON.parse(methods.getValues('config') || '{}') as unknown; + if (!isRecord(parsed)) { + setSaveError('Plugin config must be a JSON object.'); + return false; + } + setFormValue(parsed); + setSaveError(null); + return true; + } catch { + setSaveError('Fix the Plugin JSON syntax error before switching to Fields.'); + return false; + } + }, [methods]); + + const handleApplyJsonToFields = useCallback(() => { + if (applyJsonToFields()) { + setActiveTab('form'); + } + }, [applyJsonToFields]); + const handleTabChange = useCallback((key: string) => { if (key === 'json' && activeTab === 'form') { // Serialize form values to JSON editor methods.setValue('config', toConfigStr(formValue as object)); } else if (key === 'form' && activeTab === 'json') { - // Parse JSON editor to form values. If the JSON is currently invalid, stay - // on the JSON tab and surface the error rather than silently discarding edits. - try { - const parsed = JSON.parse(methods.getValues('config') || '{}') as Record; - setFormValue(parsed); - } catch { - setSaveError('Fix the Plugin JSON syntax error before switching to Fields.'); + if (!applyJsonToFields()) { return; } } setActiveTab(key); - }, [activeTab, formValue, methods]); + }, [activeTab, applyJsonToFields, formValue, methods]); const handleFormChange = useCallback((val: Record) => { setFormValue(val); @@ -412,6 +428,11 @@ export const PluginEditorDrawer = (props: PluginEditorDrawerProps) => { ))} {activeTab === 'json' && mode !== 'view' && ( + {canUseForm && ( + + )}