From 9d7945ce2c0fe6e12916563eaa03b3a1fb8358ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=AB=E9=9B=A8=E6=B0=B4=E8=BF=87=E6=BB=A4=E7=9A=84?= =?UTF-8?q?=E7=A9=BA=E6=B0=94-Rain?= <958414905@qq.com> Date: Tue, 10 Oct 2023 23:28:20 +0800 Subject: [PATCH] fix: should save operators (#2794) --- .../src/__tests__/e2e/bugs/T-2183.test.ts | 177 ++++++++++++++++++ .../antd/filter/FilterAction.tsx | 22 ++- 2 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 packages/core/client/src/__tests__/e2e/bugs/T-2183.test.ts diff --git a/packages/core/client/src/__tests__/e2e/bugs/T-2183.test.ts b/packages/core/client/src/__tests__/e2e/bugs/T-2183.test.ts new file mode 100644 index 000000000..615da80cb --- /dev/null +++ b/packages/core/client/src/__tests__/e2e/bugs/T-2183.test.ts @@ -0,0 +1,177 @@ +import { expect, test } from '@nocobase/test/client'; + +const config = { + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + properties: { + '3c1ivjciciu': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'BlockInitializers', + properties: { + '1n5r9s23amo': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + bsmuf6ly8b2: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + ppv3te8l7mo: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-decorator-props': { + collection: 'users', + resource: 'users', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + disableTemplate: false, + }, + 'x-designer': 'TableBlockDesigner', + 'x-component': 'CardItem', + 'x-filter-targets': [], + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'TableActionInitializers', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + properties: { + o8wqytvd6as: { + 'x-uid': '9vghmn6u5eo', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Filter") }}', + 'x-action': 'filter', + 'x-designer': 'Filter.Action.Designer', + 'x-component': 'Filter.Action', + 'x-component-props': { + icon: 'FilterOutlined', + useProps: '{{ useFilterActionProps }}', + }, + 'x-align': 'left', + default: { + $and: [], + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'qq7hs80xlut', + 'x-async': false, + 'x-index': 1, + }, + r02zx6qq4ql: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'TableColumnInitializers', + 'x-component': 'TableV2', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + useProps: '{{ useTableBlockProps }}', + }, + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-designer': 'TableV2.ActionColumnDesigner', + 'x-initializer': 'TableActionColumnInitializers', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-uid': '4fcw4u63f3p', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'pxqbve61e9i', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'kta6g2v3m9s', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'yu418id0x3t', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'xrvoeyz6uop', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'u64df000i8u', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'hofapxvp8p7', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'w7nv1c98j2g', + 'x-async': true, + 'x-index': 1, + }, +}; + +test('BUG: should save conditions', async ({ page, mockPage }) => { + await mockPage(config).goto(); + await page.getByTestId('filter-action').click(); + await page.getByText('Add condition', { exact: true }).click(); + await page.getByTestId('antd-cascader').getByLabel('Search').click(); + await page.getByRole('menuitemcheckbox', { name: 'ID' }).click(); + await page.getByRole('button', { name: 'Save conditions' }).click(); + + await page.reload(); + await page.getByTestId('filter-action').click(); + + // After refreshing the browser, the set field and operator should still be visible + await expect(page.getByTestId('antd-cascader').getByTitle('ID')).toBeVisible(); + await expect(page.getByText('IDis')).toBeVisible(); +}); diff --git a/packages/core/client/src/schema-component/antd/filter/FilterAction.tsx b/packages/core/client/src/schema-component/antd/filter/FilterAction.tsx index ff09e181b..4d060a20a 100644 --- a/packages/core/client/src/schema-component/antd/filter/FilterAction.tsx +++ b/packages/core/client/src/schema-component/antd/filter/FilterAction.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/css'; import { createForm, Field, Form } from '@formily/core'; import { observer, useField, useFieldSchema, useForm } from '@formily/react'; +import { flatten, unflatten } from '@nocobase/utils/client'; import { Button, Space } from 'antd'; import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -112,10 +113,12 @@ const SaveConditions = () => { onClick={() => { const defaultValue = { ...form.values.filter }; fieldSchema.default = defaultValue; + dn.emit('patch', { schema: { 'x-uid': fieldSchema['x-uid'], - default: defaultValue, + // undefined 会在转成 JSON 时被删除,这里转成 null 是为了防止被删除 + default: undefinedToNull(defaultValue), }, }); dn.refresh(); @@ -125,3 +128,20 @@ const SaveConditions = () => { ); }; + +/** + * 将一个对象中所有值为 undefined 的属性转换为值为 null 的 + * @param value + * @returns + */ +function undefinedToNull(value) { + const flat = flatten(value); + + Object.keys(flat).forEach((key) => { + if (flat[key] === undefined) { + flat[key] = null; + } + }); + + return unflatten(flat); +}