From 8985f31192121db94625d63c6e2846287474adad Mon Sep 17 00:00:00 2001 From: Jiann Date: Thu, 2 Jul 2026 15:44:55 +0800 Subject: [PATCH] fix(client-v2): prune missing URL params from data scope filters (#9869) --- .../actions/__tests__/dataScopeFilter.test.ts | 62 +++++++++++++++++++ .../src/flow/actions/dataScopeFilter.ts | 11 +++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/packages/core/client-v2/src/flow/actions/__tests__/dataScopeFilter.test.ts b/packages/core/client-v2/src/flow/actions/__tests__/dataScopeFilter.test.ts index 46cfb1604f1..f8b88d30088 100644 --- a/packages/core/client-v2/src/flow/actions/__tests__/dataScopeFilter.test.ts +++ b/packages/core/client-v2/src/flow/actions/__tests__/dataScopeFilter.test.ts @@ -64,6 +64,40 @@ describe('normalizeDataScopeFilter', () => { }); }); + it('prunes a missing URL search param variable', () => { + const rawFilter = { + logic: '$and', + items: [{ path: 'departmentId', operator: '$eq', value: '{{ ctx.urlSearchParams.departmentId }}' }], + }; + const resolvedFilter = { + logic: '$and', + items: [{ path: 'departmentId', operator: '$eq', value: undefined }], + }; + + expect(normalizeDataScopeFilter(rawFilter, resolvedFilter)).toBeUndefined(); + }); + + it('only prunes the missing URL search param condition from mixed filters', () => { + const rawFilter = { + logic: '$and', + items: [ + { path: 'status', operator: '$eq', value: 'active' }, + { path: 'departmentId', operator: '$eq', value: '{{ ctx.urlSearchParams.departmentId }}' }, + ], + }; + const resolvedFilter = { + logic: '$and', + items: [ + { path: 'status', operator: '$eq', value: 'active' }, + { path: 'departmentId', operator: '$eq', value: undefined }, + ], + }; + + expect(normalizeDataScopeFilter(rawFilter, resolvedFilter)).toEqual({ + $and: [{ status: { $eq: 'active' } }], + }); + }); + it('still prunes empty constant values', () => { const filter = { logic: '$and', @@ -139,6 +173,34 @@ describe('normalizeDataScopeFilter', () => { expect(resource.removeFilterGroup).not.toHaveBeenCalled(); }); + it('dataScope handler removes data scope when a URL search param is missing', async () => { + const resource = { + addFilterGroup: vi.fn(), + removeFilterGroup: vi.fn(), + }; + const ctx = { + model: { + uid: 'field-1', + resource, + }, + resolveJsonTemplate: vi.fn(async (template) => ({ + ...template, + items: [{ ...template.items[0], value: undefined }], + })), + }; + const params = { + filter: { + logic: '$and', + items: [{ path: 'departmentId', operator: '$eq', value: '{{ ctx.urlSearchParams.departmentId }}' }], + }, + }; + + await (dataScope as any).handler(ctx, params); + + expect(resource.removeFilterGroup).toHaveBeenCalledWith('field-1'); + expect(resource.addFilterGroup).not.toHaveBeenCalled(); + }); + it('dataScope handler preserves current role as server-side variable', async () => { const engine = new FlowEngine(); const resource = { diff --git a/packages/core/client-v2/src/flow/actions/dataScopeFilter.ts b/packages/core/client-v2/src/flow/actions/dataScopeFilter.ts index ff473756e5f..038787c4bcf 100644 --- a/packages/core/client-v2/src/flow/actions/dataScopeFilter.ts +++ b/packages/core/client-v2/src/flow/actions/dataScopeFilter.ts @@ -7,7 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { isVariableExpression, pruneFilter } from '@nocobase/flow-engine'; +import { extractPropertyPath, isVariableExpression, pruneFilter } from '@nocobase/flow-engine'; import { transformFilter } from '@nocobase/utils/client'; import _ from 'lodash'; @@ -45,6 +45,14 @@ function restorePreservedNull(value: any): any { return value; } +function isUrlSearchParamsExpression(value: any) { + if (!isVariableExpression(value)) { + return false; + } + + return extractPropertyPath(value)?.[0] === 'urlSearchParams'; +} + function markEmptyVariableValues(rawNode: any, resolvedNode: any) { if (!rawNode || !resolvedNode || typeof rawNode !== 'object' || typeof resolvedNode !== 'object') { return; @@ -57,6 +65,7 @@ function markEmptyVariableValues(rawNode: any, resolvedNode: any) { } if ( isVariableExpression(rawNode.value) && + !isUrlSearchParamsExpression(rawNode.value) && (resolvedNode.value === undefined || resolvedNode.value === null || resolvedNode.value === '') ) { resolvedNode.value = PRESERVE_NULL;