This is an automated email from the ASF dual-hosted git repository.
moonming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git
The following commit(s) were added to refs/heads/master by this push:
new 2ec818cf5 Fix/unsaved plugin config warning (#3333)
2ec818cf5 is described below
commit 2ec818cf53a8f3f99cb4c9e9da93fc9f039c7bd6
Author: Kumar Vanshaj <[email protected]>
AuthorDate: Wed May 20 05:02:07 2026 +0530
Fix/unsaved plugin config warning (#3333)
* fix(i18n): add missing services.empty translation key in zh, de, es, and
tr locales
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI
<[email protected]>
* fix(i18n): fix button label consistency in es and tr services.empty
translations
* fix(ux): show confirmation dialog when closing plugin editor with unsaved
changes
* fix(ux): show confirmation dialog when closing plugin editor with unsaved
changes
* fix(a11y): add aria-label to plugin editor drawer close button
---------
Co-authored-by: Copilot Autofix powered by AI
<[email protected]>
---
.../plugin_editor_drawer.unsaved-changes.spec.ts | 88 ++++++++++++++++++++++
.../FormItemPlugins/PluginEditorDrawer.tsx | 24 +++++-
src/locales/de/common.json | 5 ++
src/locales/en/common.json | 5 ++
src/locales/es/common.json | 5 ++
src/locales/tr/common.json | 5 ++
src/locales/zh/common.json | 5 ++
7 files changed, 133 insertions(+), 4 deletions(-)
diff --git a/e2e/tests/plugin_editor_drawer.unsaved-changes.spec.ts
b/e2e/tests/plugin_editor_drawer.unsaved-changes.spec.ts
new file mode 100644
index 000000000..63f0a5082
--- /dev/null
+++ b/e2e/tests/plugin_editor_drawer.unsaved-changes.spec.ts
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { pluginConfigsPom } from '@e2e/pom/plugin_configs';
+import { test } from '@e2e/utils/test';
+import { uiFillMonacoEditor, uiGetMonacoEditor } from '@e2e/utils/ui';
+import { expect } from '@playwright/test';
+
+const openResponseRewriteDrawer = async (page: Parameters<typeof
test>[1]['page']) => {
+ await pluginConfigsPom.toAdd(page);
+ await pluginConfigsPom.isAddPage(page);
+
+ await page.getByRole('button', { name: 'Select Plugins' }).click();
+
+ const selectPluginsDialog = page.getByRole('dialog', { name: 'Select
Plugins' });
+ await
selectPluginsDialog.getByPlaceholder('Search').fill('response-rewrite');
+ await selectPluginsDialog
+ .getByTestId('plugin-response-rewrite')
+ .getByRole('button', { name: 'Add' })
+ .click();
+
+ const drawer = page.getByRole('dialog', { name: 'Add Plugin' });
+ await expect(drawer).toBeVisible();
+ return drawer;
+};
+
+test('should show unsaved changes dialog when closing plugin editor with
unsaved changes', async ({
+ page,
+}) => {
+ const drawer = await openResponseRewriteDrawer(page);
+
+ await test.step('make changes in the editor', async () => {
+ const editor = await uiGetMonacoEditor(page, drawer);
+ await uiFillMonacoEditor(page, editor, '{"body": "test"}');
+ });
+
+ await test.step('closing with unsaved changes shows confirmation dialog',
async () => {
+ await drawer.getByRole('button', { name: 'Close' }).click();
+
+ const confirmModal = page.getByRole('dialog', { name: 'Unsaved Changes' });
+ await expect(confirmModal).toBeVisible();
+ await expect(
+ confirmModal.getByText('You have unsaved changes. Are you sure you want
to close?')
+ ).toBeVisible();
+ });
+
+ await test.step('cancel keeps drawer open with changes preserved', async ()
=> {
+ const confirmModal = page.getByRole('dialog', { name: 'Unsaved Changes' });
+ await confirmModal.getByRole('button', { name: 'Cancel' }).click();
+
+ await expect(confirmModal).toBeHidden();
+ await expect(drawer).toBeVisible();
+ });
+
+ await test.step('discard changes closes the drawer', async () => {
+ await drawer.getByRole('button', { name: 'Close' }).click();
+
+ const confirmModal = page.getByRole('dialog', { name: 'Unsaved Changes' });
+ await confirmModal.getByRole('button', { name: 'Discard Changes'
}).click();
+
+ await expect(confirmModal).toBeHidden();
+ await expect(drawer).toBeHidden();
+ });
+});
+
+test('should close plugin editor silently when there are no unsaved changes',
async ({ page }) => {
+ const drawer = await openResponseRewriteDrawer(page);
+
+ await test.step('closing without changes does not show confirmation dialog',
async () => {
+ await drawer.getByRole('button', { name: 'Close' }).click();
+
+ await expect(page.getByRole('dialog', { name: 'Unsaved Changes'
})).toBeHidden();
+ await expect(drawer).toBeHidden();
+ });
+});
diff --git a/src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx
b/src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx
index 1547af8c7..25175b420 100644
--- a/src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx
+++ b/src/components/form-slice/FormItemPlugins/PluginEditorDrawer.tsx
@@ -14,7 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Drawer, Group, Title } from '@mantine/core';
+import { Drawer, Group, Text, Title } from '@mantine/core';
+import { modals } from '@mantine/modals';
import { isEmpty, isNil } from 'rambdax';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
@@ -47,8 +48,21 @@ export const PluginEditorDrawer = (props:
PluginEditorDrawerProps) => {
defaultValues: { config: toConfigStr(config) },
});
const handleClose = () => {
- onClose();
- methods.reset();
+ if (mode !== 'view' && methods.getValues('config') !==
toConfigStr(config)) {
+ modals.openConfirmModal({
+ centered: true,
+ title: t('info.unsaved.title'),
+ children: <Text size="sm">{t('info.unsaved.content')}</Text>,
+ labels: { confirm: t('info.unsaved.confirm'), cancel:
t('form.btn.cancel') },
+ onConfirm: () => {
+ onClose();
+ methods.reset();
+ },
+ });
+ } else {
+ onClose();
+ methods.reset();
+ }
};
useEffect(() => {
@@ -64,6 +78,7 @@ export const PluginEditorDrawer = (props:
PluginEditorDrawerProps) => {
closeOnEscape={false}
opened={opened}
onClose={handleClose}
+ closeButtonProps={{ 'aria-label': 'Close' }}
styles={{ body: { paddingTop: '18px' } }}
{...(mode === 'add' && { title: t('form.plugins.addPlugin') })}
{...(mode === 'edit' && { title: t('form.plugins.editPlugin') })}
@@ -91,7 +106,8 @@ export const PluginEditorDrawer = (props:
PluginEditorDrawerProps) => {
variant="light"
onClick={methods.handleSubmit(({ config }) => {
onSave({ name, config: JSON.parse(config) });
- handleClose();
+ onClose();
+ methods.reset();
})}
>
{mode === 'add' && t('form.btn.add')}
diff --git a/src/locales/de/common.json b/src/locales/de/common.json
index 89817b6a1..95cfb9ad5 100644
--- a/src/locales/de/common.json
+++ b/src/locales/de/common.json
@@ -304,6 +304,11 @@
"edit": {
"success": "{{name}} erfolgreich bearbeitet",
"title": "{{name}} bearbeiten"
+ },
+ "unsaved": {
+ "confirm": "Änderungen verwerfen",
+ "content": "Sie haben nicht gespeicherte Änderungen. Möchten Sie
wirklich schließen?",
+ "title": "Nicht gespeicherte Änderungen"
}
},
"mark": {
diff --git a/src/locales/en/common.json b/src/locales/en/common.json
index 6544bf790..9625981d2 100644
--- a/src/locales/en/common.json
+++ b/src/locales/en/common.json
@@ -304,6 +304,11 @@
"edit": {
"success": "Edit {{name}} Successfully",
"title": "Edit {{name}}"
+ },
+ "unsaved": {
+ "confirm": "Discard Changes",
+ "content": "You have unsaved changes. Are you sure you want to close?",
+ "title": "Unsaved Changes"
}
},
"mark": {
diff --git a/src/locales/es/common.json b/src/locales/es/common.json
index d46f14f9d..b57953e9c 100644
--- a/src/locales/es/common.json
+++ b/src/locales/es/common.json
@@ -304,6 +304,11 @@
"edit": {
"success": "Editado {{name}} con éxito",
"title": "Editar {{name}}"
+ },
+ "unsaved": {
+ "confirm": "Descartar cambios",
+ "content": "Tiene cambios sin guardar. ¿Está seguro de que desea
cerrar?",
+ "title": "Cambios sin guardar"
}
},
"mark": {
diff --git a/src/locales/tr/common.json b/src/locales/tr/common.json
index 63745f663..46da33e48 100644
--- a/src/locales/tr/common.json
+++ b/src/locales/tr/common.json
@@ -304,6 +304,11 @@
"edit": {
"success": "{{name}} başarıyla güncellendi",
"title": "{{name}} Düzenle"
+ },
+ "unsaved": {
+ "confirm": "Değişiklikleri at",
+ "content": "Kaydedilmemiş değişiklikleriniz var. Kapatmak istediğinizden
emin misiniz?",
+ "title": "Kaydedilmemiş Değişiklikler"
}
},
"mark": {
diff --git a/src/locales/zh/common.json b/src/locales/zh/common.json
index b5861692f..a8097fa1d 100644
--- a/src/locales/zh/common.json
+++ b/src/locales/zh/common.json
@@ -304,6 +304,11 @@
"edit": {
"success": "编辑 {{name}} 成功",
"title": "编辑 {{name}}"
+ },
+ "unsaved": {
+ "confirm": "放弃更改",
+ "content": "您有未保存的更改,确定要关闭吗?",
+ "title": "未保存的更改"
}
},
"mark": {