fix(filter-form): fix auto-trigger filter action without default value (#4627)

* test: add e2e test

* test: add unit tests

* fix(reset-button): prevent data clear when default values exist

* fix: resolve known issues and add tests
This commit is contained in:
Zeke Zhang 2024-06-12 11:07:04 +08:00 committed by GitHub
parent 5d48b3b5aa
commit e6848858d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 1062 additions and 60 deletions

View File

@ -32,7 +32,7 @@ import {
import { useAPIClient, useRequest } from '../../api-client';
import { useFormBlockContext } from '../../block-provider/FormBlockProvider';
import { useCollectionManager_deprecated, useCollection_deprecated } from '../../collection-manager';
import { useFilterBlock } from '../../filter-provider/FilterProvider';
import { DataBlock, useFilterBlock } from '../../filter-provider/FilterProvider';
import { mergeFilter, transformToFilter } from '../../filter-provider/utils';
import { useTreeParentRecord } from '../../modules/blocks/data-blocks/table/TreeRecordProvider';
import { useRecord } from '../../record-provider';
@ -430,6 +430,10 @@ const useDoFilter = () => {
const { name } = useCollection();
const { targets = [], uid } = useMemo(() => findFilterTargets(fieldSchema), [fieldSchema]);
const getFilterFromCurrentForm = useCallback(() => {
return removeNullCondition(transformToFilter(form.values, getOperators(), getCollectionJoinField, name));
}, [form.values, getCollectionJoinField, getOperators, name]);
const doFilter = useCallback(
async ({ doNothingWhenFilterIsEmpty = false } = {}) => {
try {
@ -443,20 +447,19 @@ const useDoFilter = () => {
// 保留原有的 filter
const storedFilter = block.service.params?.[1]?.filters || {};
storedFilter[uid] = removeNullCondition(
transformToFilter(form.values, getOperators(), getCollectionJoinField, name),
);
// 由当前表单转换而来的 filter
storedFilter[uid] = getFilterFromCurrentForm();
const mergedFilter = mergeFilter([
...Object.values(storedFilter).map((filter) => removeNullCondition(filter)),
block.defaultFilter,
]);
if (doNothingWhenFilterIsEmpty && _.isEmpty(mergedFilter)) {
if (doNothingWhenFilterIsEmpty && _.isEmpty(storedFilter[uid])) {
return;
}
if (block.dataLoadingMode === 'manual' && _.isEmpty(mergedFilter)) {
if (block.dataLoadingMode === 'manual' && _.isEmpty(storedFilter[uid])) {
return block.clearData();
}
@ -474,7 +477,7 @@ const useDoFilter = () => {
console.error(error);
}
},
[form.values, getCollectionJoinField, getDataBlocks, getOperators, name, targets, uid],
[getDataBlocks, getFilterFromCurrentForm, targets, uid],
);
// 这里的代码是为了实现:筛选表单的筛选操作在首次渲染时自动执行一次
@ -487,6 +490,10 @@ const useDoFilter = () => {
*
*/
doFilter,
/**
* filter
*/
getFilterFromCurrentForm,
};
};
@ -504,52 +511,35 @@ export const useFilterBlockActionProps = () => {
};
};
export const useResetBlockActionProps = () => {
const useDoReset = () => {
const form = useForm();
const actionField = useField();
const fieldSchema = useFieldSchema();
const { getDataBlocks } = useFilterBlock();
const { targets, uid } = findFilterTargets(fieldSchema);
const { doFilter, getFilterFromCurrentForm } = useDoFilter();
return {
doReset: async () => {
await form.reset();
if (_.isEmpty(getFilterFromCurrentForm())) {
return doReset({ getDataBlocks, targets, uid });
}
await doFilter();
},
};
};
export const useResetBlockActionProps = () => {
const actionField = useField();
const { doReset } = useDoReset();
actionField.data = actionField.data || {};
return {
async onClick() {
const { targets, uid } = findFilterTargets(fieldSchema);
form.reset();
actionField.data.loading = true;
try {
// 收集 filter 的值
await Promise.all(
getDataBlocks().map(async (block) => {
const target = targets.find((target) => target.uid === block.uid);
if (!target) return;
if (block.dataLoadingMode === 'manual') {
return block.clearData();
}
const param = block.service.params?.[0] || {};
// 保留原有的 filter
const storedFilter = block.service.params?.[1]?.filters || {};
delete storedFilter[uid];
const mergedFilter = mergeFilter([...Object.values(storedFilter), block.defaultFilter]);
return block.doFilter(
{
...param,
page: 1,
filter: mergedFilter,
},
{ filters: storedFilter },
);
}),
);
actionField.data.loading = false;
} catch (error) {
actionField.data.loading = false;
}
await doReset();
actionField.data.loading = false;
},
};
};
@ -1347,6 +1337,52 @@ export const useAssociationFilterBlockProps = () => {
labelKey,
};
};
async function doReset({
getDataBlocks,
targets,
uid,
}: {
getDataBlocks: () => DataBlock[];
targets: {
/** field uid */
uid: string;
/** associated field */
field?: string;
}[];
uid: string;
}) {
try {
await Promise.all(
getDataBlocks().map(async (block) => {
const target = targets.find((target) => target.uid === block.uid);
if (!target) return;
if (block.dataLoadingMode === 'manual') {
return block.clearData();
}
const param = block.service.params?.[0] || {};
// 保留原有的 filter
const storedFilter = block.service.params?.[1]?.filters || {};
delete storedFilter[uid];
const mergedFilter = mergeFilter([...Object.values(storedFilter), block.defaultFilter]);
return block.doFilter(
{
...param,
page: 1,
filter: mergedFilter,
},
{ filters: storedFilter },
);
}),
);
} catch (error) {
console.error(error);
}
}
export function getAssociationPath(str) {
const lastIndex = str.lastIndexOf('.');
if (lastIndex !== -1) {

View File

@ -8,11 +8,11 @@
*/
import {
CollectionFieldInterface,
isTitleField,
Application,
useDataSourceHeaders,
CollectionFieldInterface,
DEFAULT_DATA_SOURCE_KEY,
isTitleField,
useDataSourceHeaders,
} from '@nocobase/client';
import { renderHook } from '@nocobase/test/client';

View File

@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { getSupportFieldsByAssociation, getSupportFieldsByForeignKey } from '../utils';
import { getSupportFieldsByAssociation, getSupportFieldsByForeignKey, transformToFilter } from '../utils';
describe('getSupportFieldsByAssociation', () => {
it('should return all associated fields matching the inherited collections chain', () => {
@ -140,3 +140,80 @@ describe('getSupportFieldsByForeignKey', () => {
]);
});
});
describe('transformToFilter', () => {
const values = {
field1: 'value1',
field2: 'value2',
field3: [
{
id: 'value3',
},
{
id: 'value4',
},
],
};
const operators = {
field1: '$eq',
field2: '$ne',
field3: '$in',
};
const collectionName = 'collection';
const getCollectionJoinField = vi.fn((name: string) => {
if (name === `${collectionName}.field1`) return {};
if (name === `${collectionName}.field2`) return {};
if (name === `${collectionName}.field3`) return { target: 'targetCollection', targetKey: 'id' };
return {};
});
it('should transform values to filter', () => {
const expectedFilter = {
$and: [
{
field1: {
$eq: 'value1',
},
},
{
field2: {
$ne: 'value2',
},
},
{
'field3.id': {
$eq: ['value3', 'value4'],
},
},
],
};
const filter = transformToFilter(values, operators, getCollectionJoinField, collectionName);
expect(filter).toEqual(expectedFilter);
});
it('should handle null values', () => {
const valuesWithNull = {
field1: null,
field2: 'value2',
};
const expectedFilter = {
$and: [
{
field2: {
$ne: 'value2',
},
},
],
};
const filter = transformToFilter(valuesWithNull, operators, getCollectionJoinField, collectionName);
expect(filter).toEqual(expectedFilter);
});
});

View File

@ -8,7 +8,7 @@
*/
import { expect, test } from '@nocobase/test/e2e';
import { tableListDetailsGridCardWithUsers } from './templatesOfBug';
import { TableBlockWithDataScope, tableListDetailsGridCardWithUsers } from './templatesOfBug';
test.describe('setDataLoadingModeSettingsItem', () => {
test('basic', async ({ page, mockPage }) => {
@ -72,4 +72,30 @@ test.describe('setDataLoadingModeSettingsItem', () => {
await expect(page.getByLabel('block-item-CardItem-users-list').getByText('No data')).toBeVisible();
await expect(page.getByLabel('block-item-BlockItem-users-').getByText('No data')).toBeVisible();
});
test('When the data block has data scope settings and dataLoadingMode is manual, data should not be displayed after the first page load', async ({
page,
mockPage,
}) => {
await mockPage(TableBlockWithDataScope).goto();
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
// 此时点击 filter 按钮,应该还是没数据,因为表单没有值
await page.getByLabel('action-Action-Filter-submit-').click({
position: {
x: 10,
y: 10,
},
});
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
// 点击 Reset 按钮,也是一样
await page.getByLabel('action-Action-Reset-users-').click({
position: {
x: 10,
y: 10,
},
});
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
});
});

View File

@ -1558,3 +1558,366 @@ export const detailsBlockWithLinkageRule = {
'x-index': 1,
},
};
export const TableBlockWithDataScope = {
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
properties: {
an34615vknp: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
properties: {
'62fssedpl0w': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.0.1-alpha.2',
properties: {
'93tyrk65qym': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.0.1-alpha.2',
properties: {
'0b7maevxkfs': {
'x-uid': 'uqnziogzhkq',
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'FilterFormBlockProvider',
'x-use-decorator-props': 'useFilterFormBlockDecoratorProps',
'x-decorator-props': {
dataSource: 'main',
collection: 'users',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:filterForm',
'x-component': 'CardItem',
'x-filter-targets': [
{
uid: 'yg26txxpq4l',
},
],
'x-app-version': '1.0.1-alpha.2',
properties: {
swmsovm7d4t: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'FormV2',
'x-use-component-props': 'useFilterFormBlockProps',
'x-app-version': '1.0.1-alpha.2',
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'filterForm:configureFields',
'x-app-version': '1.0.1-alpha.2',
properties: {
mzd4ludncd8: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.0.1-alpha.2',
properties: {
w67kg0y8wn6: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.0.1-alpha.2',
properties: {
nickname: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'string',
required: false,
'x-toolbar': 'FormItemSchemaToolbar',
'x-settings': 'fieldSettings:FilterFormItem',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-use-decorator-props': 'useFormItemProps',
'x-collection-field': 'users.nickname',
'x-component-props': {},
'x-app-version': '1.0.1-alpha.2',
'x-uid': '3azj6wv3v2r',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '1jmg205s2sa',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'wjoskqiuk5b',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 't3gxqyl52ca',
'x-async': false,
'x-index': 1,
},
ceahs5hahgg: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'filterForm:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
style: {
float: 'right',
},
},
'x-app-version': '1.0.1-alpha.2',
properties: {
xgjhwz2ln8l: {
_isJSONSchemaObject: true,
version: '2.0',
title: '{{ t("Filter") }}',
'x-action': 'submit',
'x-component': 'Action',
'x-use-component-props': 'useFilterBlockActionProps',
'x-designer': 'Action.Designer',
'x-component-props': {
type: 'primary',
htmlType: 'submit',
},
'x-action-settings': {},
type: 'void',
'x-app-version': '1.0.1-alpha.2',
'x-uid': 'ev40o2gk87b',
'x-async': false,
'x-index': 1,
},
qxhdilp319s: {
_isJSONSchemaObject: true,
version: '2.0',
title: '{{ t("Reset") }}',
'x-component': 'Action',
'x-use-component-props': 'useResetBlockActionProps',
'x-designer': 'Action.Designer',
'x-action-settings': {},
type: 'void',
'x-app-version': '1.0.1-alpha.2',
'x-uid': '559bivcwabh',
'x-async': false,
'x-index': 2,
},
},
'x-uid': '8q25gzurfeh',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'v3mwerc709f',
'x-async': false,
'x-index': 1,
},
},
'x-async': false,
'x-index': 1,
},
},
'x-uid': '952up9gdrhf',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'da58ptz1cie',
'x-async': false,
'x-index': 1,
},
dxd8oaoh4a7: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.0.1-alpha.2',
properties: {
'27ijajdwyd2': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.0.1-alpha.2',
properties: {
'7yz5da41nt3': {
'x-uid': 'yg26txxpq4l',
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'users:list',
'x-use-decorator-props': 'useTableBlockDecoratorProps',
'x-decorator-props': {
collection: 'users',
dataSource: 'main',
action: 'list',
params: {
pageSize: 20,
filter: {
$and: [
{
nickname: {
$includes: '{{$user.nickname}}',
},
},
],
},
},
rowKey: 'id',
showIndex: true,
dragSort: false,
dataLoadingMode: 'manual',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-filter-targets': [],
'x-app-version': '1.0.1-alpha.2',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
'x-app-version': '1.0.1-alpha.2',
'x-uid': '73fy8oxxxk9',
'x-async': false,
'x-index': 1,
},
wo6mmz3gm4e: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-use-component-props': 'useTableBlockProps',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
},
'x-app-version': '1.0.1-alpha.2',
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-toolbar': 'TableColumnSchemaToolbar',
'x-initializer': 'table:configureItemActions',
'x-settings': 'fieldSettings:TableColumn',
'x-toolbar-props': {
initializer: 'table:configureItemActions',
},
'x-app-version': '1.0.1-alpha.2',
properties: {
k96lkyh071r: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
'x-app-version': '1.0.1-alpha.2',
'x-uid': 'e0u8ctx63xa',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'kscb8lws4pt',
'x-async': false,
'x-index': 1,
},
tpeettygzf9: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableV2.Column.Decorator',
'x-toolbar': 'TableColumnSchemaToolbar',
'x-settings': 'fieldSettings:TableColumn',
'x-component': 'TableV2.Column',
'x-app-version': '1.0.1-alpha.2',
properties: {
nickname: {
_isJSONSchemaObject: true,
version: '2.0',
'x-collection-field': 'users.nickname',
'x-component': 'CollectionField',
'x-component-props': {
ellipsis: true,
},
'x-read-pretty': true,
'x-decorator': null,
'x-decorator-props': {
labelStyle: {
display: 'none',
},
},
'x-app-version': '1.0.1-alpha.2',
'x-uid': 'fyeg48brfk6',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '1zzxomha3yb',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'f8zia156lux',
'x-async': false,
'x-index': 2,
},
},
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'j9c5x3pl112',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'qkazgqmj5zk',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'iabe2fdu20i',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'dxf2yvxqvra',
'x-async': true,
'x-index': 1,
},
keepUid: true,
};

View File

@ -8,7 +8,7 @@
*/
import { expect, test } from '@nocobase/test/e2e';
import { oneFilterFormAndTable } from './templates';
import { oneFilterFormAndTable, oneFilterFormAndTableWithManualLoadingData } from './templates';
test.describe('filter form', () => {
test('When the filter form field is set with a default value, it should trigger a filtering action on the first page load', async ({
@ -29,7 +29,65 @@ test.describe('filter form', () => {
page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'Super Admin' }),
).not.toBeVisible();
// 2. 点击重置按钮后,会显示出全部数据
// 2. 点击重置按钮后,数据不变
await page.getByLabel('action-Action-Reset-users-').click({
position: {
x: 10,
y: 10,
},
});
await expect(
page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'test name' }),
).toBeVisible();
await expect(
page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'Super Admin' }),
).not.toBeVisible();
// 3. 清空 nickname 的值后,点击筛选按钮,应该只显示出所有数据
await page.getByLabel('block-item-CollectionField-').getByRole('textbox').clear();
await page.getByLabel('action-Action-Filter-submit-').click({
position: {
x: 10,
y: 10,
},
});
await expect(
page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'test name' }),
).toBeVisible();
await expect(
page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'Super Admin' }),
).toBeVisible();
// 4. 此时点击 Reset 按钮,应该只显示一条数据,因为会把 nickname 的值重置为 {{$user.nickname}}
await page.getByLabel('action-Action-Reset-users-').click({
position: {
x: 10,
y: 10,
},
});
await expect(
page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'test name' }),
).toBeVisible();
await expect(
page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'Super Admin' }),
).not.toBeVisible();
});
test('with dataLoadingMode is manual', async ({ page, mockPage, mockRecord }) => {
// nickname 字段的默认值是 {{$user.nickname}}
const nocoPage = await mockPage(oneFilterFormAndTableWithManualLoadingData).waitForInit();
await mockRecord('users', { nickname: 'test name' });
await nocoPage.goto();
// 1. 首次加载,应该已经触发过一次筛选的动作
await expect(
page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'Super Admin' }),
).toBeVisible();
await expect(
page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'test name' }),
).not.toBeVisible();
// 2. 点击重置按钮后,数据不变
await page.getByLabel('action-Action-Reset-users-').click({
position: {
x: 10,
@ -41,20 +99,30 @@ test.describe('filter form', () => {
).toBeVisible();
await expect(
page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'test name' }),
).toBeVisible();
).not.toBeVisible();
// 3. 再次点击筛选按钮,应该只显示出符合条件的数据
// 3. 清空 nickname 输入框,然后点击 Filter 按钮,应该显示空数据
await page.getByLabel('block-item-CollectionField-').getByRole('textbox').clear();
await page.getByLabel('action-Action-Filter-submit-').click({
position: {
x: 10,
y: 10,
},
});
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
// 4. 此时点击 Reset 按钮,应该只显示一条数据,因为会把 nickname 的值重置为 {{$user.nickname}}
await page.getByLabel('action-Action-Reset-users-').click({
position: {
x: 10,
y: 10,
},
});
await expect(
page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'Super Admin' }),
).not.toBeVisible();
).toBeVisible();
await expect(
page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'test name' }),
).toBeVisible();
).not.toBeVisible();
});
});

View File

@ -501,3 +501,367 @@ export const oneFilterFormAndTable = {
},
keepUid: true,
};
export const oneFilterFormAndTableWithManualLoadingData = {
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
'x-index': 1,
properties: {
an34615vknp: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
'x-index': 1,
properties: {
'62fssedpl0w': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
properties: {
'93tyrk65qym': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
properties: {
'0b7maevxkfs': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'FilterFormBlockProvider',
'x-use-decorator-props': 'useFilterFormBlockDecoratorProps',
'x-decorator-props': {
dataSource: 'main',
collection: 'users',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:filterForm',
'x-component': 'CardItem',
'x-filter-targets': [
{
uid: 'yg26txxpq4l',
},
],
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
properties: {
swmsovm7d4t: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'FormV2',
'x-use-component-props': 'useFilterFormBlockProps',
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'filterForm:configureFields',
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
properties: {
mzd4ludncd8: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
properties: {
w67kg0y8wn6: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
properties: {
nickname: {
'x-uid': '3azj6wv3v2r',
_isJSONSchemaObject: true,
version: '2.0',
type: 'string',
required: false,
'x-toolbar': 'FormItemSchemaToolbar',
'x-settings': 'fieldSettings:FilterFormItem',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-use-decorator-props': 'useFormItemProps',
'x-collection-field': 'users.nickname',
'x-component-props': {},
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
default: '{{$user.nickname}}',
'x-async': false,
},
},
'x-uid': '1jmg205s2sa',
'x-async': false,
},
},
'x-uid': 'wjoskqiuk5b',
'x-async': false,
},
},
'x-uid': 't3gxqyl52ca',
'x-async': false,
},
ceahs5hahgg: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'filterForm:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
style: {
float: 'right',
},
},
'x-app-version': '1.0.1-alpha.2',
'x-index': 2,
properties: {
xgjhwz2ln8l: {
_isJSONSchemaObject: true,
version: '2.0',
title: '{{ t("Filter") }}',
'x-action': 'submit',
'x-component': 'Action',
'x-use-component-props': 'useFilterBlockActionProps',
'x-designer': 'Action.Designer',
'x-component-props': {
type: 'primary',
htmlType: 'submit',
},
'x-action-settings': {},
type: 'void',
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
'x-uid': 'ev40o2gk87b',
'x-async': false,
},
qxhdilp319s: {
_isJSONSchemaObject: true,
version: '2.0',
title: '{{ t("Reset") }}',
'x-component': 'Action',
'x-use-component-props': 'useResetBlockActionProps',
'x-designer': 'Action.Designer',
'x-action-settings': {},
type: 'void',
'x-app-version': '1.0.1-alpha.2',
'x-index': 2,
'x-uid': '559bivcwabh',
'x-async': false,
},
},
'x-uid': '8q25gzurfeh',
'x-async': false,
},
},
'x-uid': 'v3mwerc709f',
'x-async': false,
},
},
'x-uid': 'uqnziogzhkq',
'x-async': false,
},
},
'x-uid': '952up9gdrhf',
'x-async': false,
},
},
'x-uid': 'da58ptz1cie',
'x-async': false,
},
dxd8oaoh4a7: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.0.1-alpha.2',
'x-index': 2,
properties: {
'27ijajdwyd2': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
properties: {
'7yz5da41nt3': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'users:list',
'x-use-decorator-props': 'useTableBlockDecoratorProps',
'x-decorator-props': {
collection: 'users',
dataSource: 'main',
action: 'list',
params: {
pageSize: 20,
filter: {
$and: [
{
nickname: {
$includes: '{{$user.nickname}}',
},
},
],
},
},
rowKey: 'id',
showIndex: true,
dragSort: false,
dataLoadingMode: 'manual',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-filter-targets': [],
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
'x-uid': '73fy8oxxxk9',
'x-async': false,
},
wo6mmz3gm4e: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-use-component-props': 'useTableBlockProps',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
},
'x-app-version': '1.0.1-alpha.2',
'x-index': 2,
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-toolbar': 'TableColumnSchemaToolbar',
'x-initializer': 'table:configureItemActions',
'x-settings': 'fieldSettings:TableColumn',
'x-toolbar-props': {
initializer: 'table:configureItemActions',
},
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
properties: {
k96lkyh071r: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
'x-uid': 'e0u8ctx63xa',
'x-async': false,
},
},
'x-uid': 'kscb8lws4pt',
'x-async': false,
},
tpeettygzf9: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableV2.Column.Decorator',
'x-toolbar': 'TableColumnSchemaToolbar',
'x-settings': 'fieldSettings:TableColumn',
'x-component': 'TableV2.Column',
'x-app-version': '1.0.1-alpha.2',
'x-index': 2,
properties: {
nickname: {
_isJSONSchemaObject: true,
version: '2.0',
'x-collection-field': 'users.nickname',
'x-component': 'CollectionField',
'x-component-props': {
ellipsis: true,
},
'x-read-pretty': true,
'x-decorator': null,
'x-decorator-props': {
labelStyle: {
display: 'none',
},
},
'x-app-version': '1.0.1-alpha.2',
'x-index': 1,
'x-uid': 'fyeg48brfk6',
'x-async': false,
},
},
'x-uid': '1zzxomha3yb',
'x-async': false,
},
},
'x-uid': 'f8zia156lux',
'x-async': false,
},
},
'x-uid': 'yg26txxpq4l',
'x-async': false,
},
},
'x-uid': 'j9c5x3pl112',
'x-async': false,
},
},
'x-uid': 'qkazgqmj5zk',
'x-async': false,
},
},
'x-uid': 'iabe2fdu20i',
'x-async': false,
},
},
'x-uid': 'dxf2yvxqvra',
'x-async': true,
},
keepUid: true,
};

View File

@ -0,0 +1,68 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { removeNullCondition } from '../useFilterActionProps';
describe('removeNullCondition', () => {
it('should remove null conditions', () => {
const filter = {
field1: null,
field2: 'value2',
field3: null,
field4: 'value4',
};
const expected = {
field2: 'value2',
field4: 'value4',
};
const result = removeNullCondition(filter);
expect(result).toEqual(expected);
});
it('should remove undefined conditions', () => {
const filter = {
field1: undefined,
field2: 'value2',
field3: undefined,
field4: 'value4',
};
const expected = {
field2: 'value2',
field4: 'value4',
};
const result = removeNullCondition(filter);
expect(result).toEqual(expected);
});
it('should handle empty filter', () => {
const filter = {};
const expected = {};
const result = removeNullCondition(filter);
expect(result).toEqual(expected);
});
it('should handle nested filter', () => {
const filter = {
field1: null,
field2: 'value2',
field3: {
subfield1: null,
subfield2: 'value2',
},
};
const expected = {
field2: 'value2',
field3: {
subfield2: 'value2',
},
};
const result = removeNullCondition(filter);
expect(result).toEqual(expected);
});
});

View File

@ -10,12 +10,12 @@
import { Field } from '@formily/core';
import { useField, useFieldSchema } from '@formily/react';
import flat from 'flat';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
import { useBlockRequestContext } from '../../../block-provider';
import { useCollection_deprecated, useCollectionManager_deprecated } from '../../../collection-manager';
import { useCollectionManager_deprecated, useCollection_deprecated } from '../../../collection-manager';
import { mergeFilter } from '../../../filter-provider/utils';
import { useDataLoadingMode } from '../../../modules/blocks/data-blocks/details-multi/setDataLoadingModeSettingsItem';
import _ from 'lodash';
export const useGetFilterOptions = () => {
const { getCollectionFields } = useCollectionManager_deprecated();
@ -157,8 +157,8 @@ const isEmpty = (obj) => {
);
};
export const removeNullCondition = (filter) => {
const items = flat(filter || {});
export const removeNullCondition = (filter, customFlat = flat) => {
const items = customFlat(filter || {});
const values = {};
for (const key in items) {
const value = items[key];
@ -166,7 +166,7 @@ export const removeNullCondition = (filter) => {
values[key] = value;
}
}
return flat.unflatten(values);
return customFlat.unflatten(values);
};
export const useFilterActionProps = () => {