mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 06:32:51 +00:00
refactor(variable): support default value setting (#4583)
* refactor(variable): support default value setting * chore: add e2e test for URL search params variable * fix: resolve field linkage errors * chore: fix unit test * test: association select data scope linkage --------- Co-authored-by: katherinehhh <katherine_15995@163.com>
This commit is contained in:
parent
17edad6014
commit
837f4f4158
@ -63,5 +63,25 @@ test.describe('Link', () => {
|
||||
await expect(page.getByRole('button', { name: users[0].username, exact: true })).not.toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'nocobase', exact: true })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: users[1].username, exact: true })).toBeVisible();
|
||||
|
||||
// 5. Change the operator of the data scope from "is not" to "is"
|
||||
await page.getByLabel('block-item-CardItem-users-').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:table-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Set the data scope' }).click();
|
||||
await page.getByTestId('select-filter-operator').click();
|
||||
await page.getByRole('option', { name: 'is', exact: true }).click();
|
||||
await page.getByLabel('variable-button').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'URL search params right' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'id', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
await expect(page.getByRole('button', { name: users[0].username, exact: true })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'nocobase', exact: true })).not.toBeVisible();
|
||||
await expect(page.getByRole('button', { name: users[1].username, exact: true })).not.toBeVisible();
|
||||
|
||||
// 6. Re-enter the page (to eliminate the query string in the URL), at this time the value of the variable is undefined, and all data should be displayed
|
||||
await nocoPage.goto();
|
||||
await expect(page.getByRole('button', { name: users[0].username, exact: true })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'nocobase', exact: true })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: users[1].username, exact: true })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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 { expect, test } from '@nocobase/test/e2e';
|
||||
import { associationSelectDataScope } from './templatesOfBug';
|
||||
|
||||
test.describe('AssociationSelect ', () => {
|
||||
test('data scope linkage with other association select field', async ({ page, mockPage, mockRecord }) => {
|
||||
await mockPage(associationSelectDataScope).goto();
|
||||
await mockRecord('school', { id: 1 });
|
||||
await mockRecord('class', {
|
||||
school: { id: 1 },
|
||||
});
|
||||
const [request] = await Promise.all([
|
||||
page.waitForRequest((request) => request.url().includes('api/class:list')),
|
||||
page.getByLabel('block-item-CollectionField-student-form-student.class-class').click(),
|
||||
]);
|
||||
const requestUrl = request.url();
|
||||
const queryParams = new URLSearchParams(new URL(requestUrl).search);
|
||||
const filter = queryParams.get('filter');
|
||||
//请求参数符合预期
|
||||
expect(JSON.parse(filter)).toEqual({ $and: [{ school: { id: { $eq: null } } }] });
|
||||
// 选择数据后联动
|
||||
await page.getByLabel('block-item-CollectionField-student-form-student.school-school').click();
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-student-form-student.school-school').click();
|
||||
await page.getByRole('option', { name: '1' }).click();
|
||||
|
||||
const [request1] = await Promise.all([
|
||||
page.waitForRequest((request) => request.url().includes('api/class:list')),
|
||||
page.getByLabel('block-item-CollectionField-student-form-student.class-class').click(),
|
||||
]);
|
||||
const requestUrl1 = request1.url();
|
||||
const queryParams1 = new URLSearchParams(new URL(requestUrl1).search);
|
||||
const filter1 = queryParams1.get('filter');
|
||||
//请求参数符合预期
|
||||
await expect(JSON.parse(filter1)).toEqual({ $and: [{ school: { id: { $eq: 1 } } }] });
|
||||
});
|
||||
});
|
@ -0,0 +1,458 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const associationSelectDataScope = {
|
||||
pageSchema: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Page',
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
properties: {
|
||||
i9pflzjyioi: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'page:addBlock',
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
properties: {
|
||||
'4ai4zgcz8ee': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
properties: {
|
||||
vpldrh6hgmf: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
properties: {
|
||||
'5ieufmio6lw': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-acl-action-props': {
|
||||
skipScopeCheck: true,
|
||||
},
|
||||
'x-acl-action': 'student:create',
|
||||
'x-decorator': 'FormBlockProvider',
|
||||
'x-use-decorator-props': 'useCreateFormBlockDecoratorProps',
|
||||
'x-decorator-props': {
|
||||
dataSource: 'main',
|
||||
collection: 'student',
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': 'blockSettings:createForm',
|
||||
'x-component': 'CardItem',
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
properties: {
|
||||
'5husg9t3vv9': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'FormV2',
|
||||
'x-use-component-props': 'useCreateFormBlockProps',
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
properties: {
|
||||
grid: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'form:configureFields',
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
properties: {
|
||||
tl52qwh5qb2: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
properties: {
|
||||
k0bea3ifdvt: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
properties: {
|
||||
school: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'string',
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'student.school',
|
||||
'x-component-props': {
|
||||
fieldNames: {
|
||||
value: 'id',
|
||||
label: 'id',
|
||||
},
|
||||
},
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
'x-uid': '4aqvcj92dmg',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '3menjy0e1ej',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'oowsi97eacq',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
whxgd4trw89: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
properties: {
|
||||
'2lbxox3r15o': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
properties: {
|
||||
class: {
|
||||
'x-uid': '1gwunmk7b3c',
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'string',
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'student.class',
|
||||
'x-component-props': {
|
||||
fieldNames: {
|
||||
value: 'id',
|
||||
label: 'id',
|
||||
},
|
||||
service: {
|
||||
params: {
|
||||
filter: {
|
||||
$and: [
|
||||
{
|
||||
school: {
|
||||
id: {
|
||||
$eq: '{{$nForm.school.id}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'ntz289r21df',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'cavrlbod03e',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
'x-uid': 'k7qwqqhdx60',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
'1kqjiuyj895': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-initializer': 'createForm:configureActions',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
layout: 'one-column',
|
||||
style: {
|
||||
marginTop: 'var(--nb-spacing)',
|
||||
},
|
||||
},
|
||||
'x-app-version': '1.0.0-alpha.17',
|
||||
'x-uid': 'cwurx7plt95',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
'x-uid': '306cgan6w47',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'wy8gkqcnr6b',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'd30aarrzirt',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'pqdwwjn7y28',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'u3r6l5ed5c8',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '42t4voitjmc',
|
||||
'x-async': true,
|
||||
'x-index': 1,
|
||||
},
|
||||
collections: [
|
||||
{
|
||||
name: 'school',
|
||||
fields: [
|
||||
{
|
||||
key: 'ze26tawdlux',
|
||||
name: 'id',
|
||||
type: 'bigInt',
|
||||
interface: 'integer',
|
||||
description: null,
|
||||
collectionName: 'school',
|
||||
parentKey: null,
|
||||
reverseKey: null,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
title: '{{t("ID")}}',
|
||||
'x-component': 'InputNumber',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
],
|
||||
filterTargetKey: 'id',
|
||||
},
|
||||
{
|
||||
name: 'class',
|
||||
fields: [
|
||||
{
|
||||
key: 'rkb2b156str',
|
||||
name: 'id',
|
||||
type: 'bigInt',
|
||||
interface: 'integer',
|
||||
description: null,
|
||||
collectionName: 'class',
|
||||
parentKey: null,
|
||||
reverseKey: null,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
title: '{{t("ID")}}',
|
||||
'x-component': 'InputNumber',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 's804a0a6hb4',
|
||||
name: 'f_nwk9mip9y1k',
|
||||
type: 'bigInt',
|
||||
interface: 'integer',
|
||||
description: null,
|
||||
collectionName: 'class',
|
||||
parentKey: null,
|
||||
reverseKey: null,
|
||||
isForeignKey: true,
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
title: 'f_nwk9mip9y1k',
|
||||
'x-component': 'InputNumber',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'btl687vmwyo',
|
||||
name: 'f_ih087vrmag4',
|
||||
type: 'bigInt',
|
||||
interface: 'integer',
|
||||
description: null,
|
||||
collectionName: 'class',
|
||||
parentKey: null,
|
||||
reverseKey: null,
|
||||
isForeignKey: true,
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
title: 'f_ih087vrmag4',
|
||||
'x-component': 'InputNumber',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'a35wf3880bb',
|
||||
name: 'f_uwcl0rf78mn',
|
||||
type: 'bigInt',
|
||||
interface: 'integer',
|
||||
description: null,
|
||||
collectionName: 'class',
|
||||
parentKey: null,
|
||||
reverseKey: null,
|
||||
isForeignKey: true,
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
title: 'f_uwcl0rf78mn',
|
||||
'x-component': 'InputNumber',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
key: 'nt7i4vmih24',
|
||||
name: 'school',
|
||||
type: 'belongsTo',
|
||||
interface: 'm2o',
|
||||
description: null,
|
||||
collectionName: 'class',
|
||||
parentKey: null,
|
||||
reverseKey: null,
|
||||
foreignKey: 'f_uwcl0rf78mn',
|
||||
onDelete: 'SET NULL',
|
||||
uiSchema: {
|
||||
'x-component': 'AssociationField',
|
||||
'x-component-props': {
|
||||
multiple: false,
|
||||
},
|
||||
title: 'school',
|
||||
},
|
||||
target: 'school',
|
||||
targetKey: 'id',
|
||||
},
|
||||
],
|
||||
filterTargetKey: 'id',
|
||||
},
|
||||
{
|
||||
name: 'student',
|
||||
|
||||
fields: [
|
||||
{
|
||||
key: 'pjz61nq67ym',
|
||||
name: 'id',
|
||||
type: 'bigInt',
|
||||
interface: 'integer',
|
||||
description: null,
|
||||
collectionName: 'student',
|
||||
parentKey: null,
|
||||
reverseKey: null,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
title: '{{t("ID")}}',
|
||||
'x-component': 'InputNumber',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'jrt23ehbhwn',
|
||||
name: 'f_firb6d8f8jq',
|
||||
type: 'bigInt',
|
||||
interface: 'integer',
|
||||
description: null,
|
||||
collectionName: 'student',
|
||||
parentKey: null,
|
||||
reverseKey: null,
|
||||
isForeignKey: true,
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
title: 'f_firb6d8f8jq',
|
||||
'x-component': 'InputNumber',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '9hydd4b1out',
|
||||
name: 'f_7kxab0celw3',
|
||||
type: 'bigInt',
|
||||
interface: 'integer',
|
||||
description: null,
|
||||
collectionName: 'student',
|
||||
parentKey: null,
|
||||
reverseKey: null,
|
||||
isForeignKey: true,
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
title: 'f_7kxab0celw3',
|
||||
'x-component': 'InputNumber',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'xwpw9a9f7y6',
|
||||
name: 'class',
|
||||
type: 'belongsTo',
|
||||
interface: 'm2o',
|
||||
description: null,
|
||||
collectionName: 'student',
|
||||
parentKey: null,
|
||||
reverseKey: null,
|
||||
foreignKey: 'f_firb6d8f8jq',
|
||||
onDelete: 'SET NULL',
|
||||
uiSchema: {
|
||||
'x-component': 'AssociationField',
|
||||
'x-component-props': {
|
||||
multiple: false,
|
||||
},
|
||||
title: 'class',
|
||||
},
|
||||
target: 'class',
|
||||
targetKey: 'id',
|
||||
},
|
||||
{
|
||||
key: 'by6lqjkl50g',
|
||||
name: 'school',
|
||||
type: 'belongsTo',
|
||||
interface: 'm2o',
|
||||
description: null,
|
||||
collectionName: 'student',
|
||||
parentKey: null,
|
||||
reverseKey: null,
|
||||
foreignKey: 'f_7kxab0celw3',
|
||||
onDelete: 'SET NULL',
|
||||
uiSchema: {
|
||||
'x-component': 'AssociationField',
|
||||
'x-component-props': {
|
||||
multiple: false,
|
||||
},
|
||||
title: 'school',
|
||||
},
|
||||
target: 'school',
|
||||
targetKey: 'id',
|
||||
},
|
||||
],
|
||||
|
||||
filterTargetKey: 'id',
|
||||
},
|
||||
],
|
||||
};
|
@ -14,7 +14,8 @@ import { flatten, getValuesByPath } from '@nocobase/utils/client';
|
||||
import _, { isString } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useCollection_deprecated, useCollectionManager_deprecated } from '../../../collection-manager';
|
||||
import { useCollectionManager_deprecated, useCollection_deprecated } from '../../../collection-manager';
|
||||
import { Collection } from '../../../data-source';
|
||||
import { isInFilterFormBlock } from '../../../filter-provider';
|
||||
import { mergeFilter } from '../../../filter-provider/utils';
|
||||
import { useRecord } from '../../../record-provider';
|
||||
@ -25,7 +26,6 @@ import { getVariableName } from '../../../variables/utils/getVariableName';
|
||||
import { isVariable } from '../../../variables/utils/isVariable';
|
||||
import { useDesignable } from '../../hooks';
|
||||
import { AssociationFieldContext } from './context';
|
||||
import { Collection } from '../../../data-source';
|
||||
|
||||
export const useInsertSchema = (component) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
|
@ -99,6 +99,12 @@ export const useURLSearchParamsVariable = (props: any = {}) => {
|
||||
urlSearchParamsSettings,
|
||||
/** 变量值 */
|
||||
urlSearchParamsCtx,
|
||||
/**
|
||||
* 这里是用于当通过该变量解析出来的值是一个 undefined 时,最终应该返回的值。
|
||||
* 默认返回的是 null,这样会导致数据范围中的 filter 条件不会被清除掉,而 URL search params 变量的值为空时,应该清除掉 filter 条件,
|
||||
* 所以这里把 defaultValue 设置为 undefined,这样在解析出来的值是 undefined 时,会返回 undefined,从而清除掉 filter 条件。
|
||||
*/
|
||||
defaultValue: undefined,
|
||||
shouldDisplay: !isVariableParsedInOtherContext,
|
||||
};
|
||||
};
|
||||
|
@ -28,27 +28,24 @@ import { uniq } from './utils/uniq';
|
||||
export const VariablesContext = createContext<VariablesContextType>(null);
|
||||
VariablesContext.displayName = 'VariablesContext';
|
||||
|
||||
const variableToCollectionName: Record<
|
||||
string,
|
||||
{
|
||||
collectionName?: string;
|
||||
dataSource?: string;
|
||||
}
|
||||
> = {};
|
||||
const variablesStore: Record<string, VariableOption> = {};
|
||||
|
||||
const getFieldPath = (variablePath: string, variableToCollectionName: Record<string, any>) => {
|
||||
const getFieldPath = (variablePath: string, variablesStore: Record<string, VariableOption>) => {
|
||||
let dataSource;
|
||||
let variableOption: VariableOption;
|
||||
const list = variablePath.split('.');
|
||||
const result = list.map((item) => {
|
||||
if (variableToCollectionName[item]) {
|
||||
dataSource = variableToCollectionName[item].dataSource;
|
||||
return variableToCollectionName[item].collectionName;
|
||||
if (variablesStore[item]) {
|
||||
dataSource = variablesStore[item].dataSource;
|
||||
variableOption = variablesStore[item];
|
||||
return variablesStore[item].collectionName;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
return {
|
||||
fieldPath: result.join('.'),
|
||||
dataSource,
|
||||
variableOption,
|
||||
};
|
||||
};
|
||||
|
||||
@ -83,12 +80,9 @@ const VariablesProvider = ({ children }) => {
|
||||
) => {
|
||||
const list = variablePath.split('.');
|
||||
const variableName = list[0];
|
||||
const _variableToCollectionName = mergeVariableToCollectionNameWithLocalVariables(
|
||||
variableToCollectionName,
|
||||
localVariables,
|
||||
);
|
||||
const _variableToCollectionName = mergeVariableToCollectionNameWithLocalVariables(variablesStore, localVariables);
|
||||
let current = mergeCtxWithLocalVariables(ctxRef.current, localVariables);
|
||||
const { fieldPath, dataSource } = getFieldPath(variableName, _variableToCollectionName);
|
||||
const { fieldPath, dataSource, variableOption } = getFieldPath(variableName, _variableToCollectionName);
|
||||
let collectionName = fieldPath;
|
||||
|
||||
if (!(variableName in current)) {
|
||||
@ -97,7 +91,7 @@ const VariablesProvider = ({ children }) => {
|
||||
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
if (current == null) {
|
||||
return current;
|
||||
return current === undefined ? variableOption.defaultValue : current;
|
||||
}
|
||||
|
||||
const key = list[index];
|
||||
@ -165,7 +159,8 @@ const VariablesProvider = ({ children }) => {
|
||||
}
|
||||
}
|
||||
|
||||
return compile(_.isFunction(current) ? current() : current);
|
||||
const result = compile(_.isFunction(current) ? current() : current);
|
||||
return result === undefined ? variableOption.defaultValue : result;
|
||||
},
|
||||
[getCollectionJoinField],
|
||||
);
|
||||
@ -185,12 +180,10 @@ const VariablesProvider = ({ children }) => {
|
||||
[variableOption.name]: variableOption.ctx,
|
||||
};
|
||||
});
|
||||
if (variableOption.collectionName) {
|
||||
variableToCollectionName[variableOption.name] = {
|
||||
collectionName: variableOption.collectionName,
|
||||
dataSource: variableOption.dataSource,
|
||||
};
|
||||
}
|
||||
variablesStore[variableOption.name] = {
|
||||
...variableOption,
|
||||
defaultValue: _.has(variableOption, 'defaultValue') ? variableOption.defaultValue : null,
|
||||
};
|
||||
},
|
||||
[setCtx],
|
||||
);
|
||||
@ -200,13 +193,8 @@ const VariablesProvider = ({ children }) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { collectionName, dataSource } = variableToCollectionName[variableName] || {};
|
||||
|
||||
return {
|
||||
name: variableName,
|
||||
ctx: ctxRef.current[variableName],
|
||||
collectionName,
|
||||
dataSource,
|
||||
...variablesStore[variableName],
|
||||
};
|
||||
}, []);
|
||||
|
||||
@ -217,7 +205,7 @@ const VariablesProvider = ({ children }) => {
|
||||
delete next[variableName];
|
||||
return next;
|
||||
});
|
||||
delete variableToCollectionName[variableName];
|
||||
delete variablesStore[variableName];
|
||||
},
|
||||
[setCtx],
|
||||
);
|
||||
@ -264,7 +252,7 @@ const VariablesProvider = ({ children }) => {
|
||||
}
|
||||
|
||||
const _variableToCollectionName = mergeVariableToCollectionNameWithLocalVariables(
|
||||
variableToCollectionName,
|
||||
variablesStore,
|
||||
localVariables as VariableOption[],
|
||||
);
|
||||
const path = getPath(variableString);
|
||||
@ -285,7 +273,10 @@ const VariablesProvider = ({ children }) => {
|
||||
|
||||
useEffect(() => {
|
||||
builtinVariables.forEach((variableOption) => {
|
||||
registerVariable(variableOption);
|
||||
registerVariable({
|
||||
...variableOption,
|
||||
defaultValue: _.has(variableOption, 'defaultValue') ? variableOption.defaultValue : null,
|
||||
});
|
||||
});
|
||||
}, [builtinVariables, registerVariable]);
|
||||
|
||||
@ -339,25 +330,17 @@ function mergeCtxWithLocalVariables(ctx: Record<string, any>, localVariables?: V
|
||||
}
|
||||
|
||||
function mergeVariableToCollectionNameWithLocalVariables(
|
||||
variableToCollectionName: Record<
|
||||
string,
|
||||
{
|
||||
collectionName?: string;
|
||||
dataSource?: string;
|
||||
}
|
||||
>,
|
||||
variablesStore: Record<string, VariableOption>,
|
||||
localVariables?: VariableOption[],
|
||||
) {
|
||||
variableToCollectionName = { ...variableToCollectionName };
|
||||
variablesStore = { ...variablesStore };
|
||||
|
||||
localVariables?.forEach((item) => {
|
||||
if (item.collectionName) {
|
||||
variableToCollectionName[item.name] = {
|
||||
collectionName: item.collectionName,
|
||||
dataSource: item.dataSource,
|
||||
};
|
||||
}
|
||||
variablesStore[item.name] = {
|
||||
...item,
|
||||
defaultValue: _.has(item, 'defaultValue') ? item.defaultValue : null,
|
||||
};
|
||||
});
|
||||
|
||||
return variableToCollectionName;
|
||||
return variablesStore;
|
||||
}
|
||||
|
@ -545,6 +545,7 @@ describe('useVariables', () => {
|
||||
ctx: {
|
||||
name: 'new variable',
|
||||
},
|
||||
defaultValue: null,
|
||||
});
|
||||
});
|
||||
|
||||
@ -567,6 +568,46 @@ describe('useVariables', () => {
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $new.noExist }}')).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('$new.noExist with default value', async () => {
|
||||
const { result } = renderHook(() => useVariables(), {
|
||||
wrapper: Providers,
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
result.current.registerVariable({
|
||||
name: '$new',
|
||||
ctx: {
|
||||
name: 'new variable',
|
||||
},
|
||||
defaultValue: 'default value',
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $new.noExist }}')).toBe('default value');
|
||||
});
|
||||
});
|
||||
|
||||
it('$new.noExist with undefined default value', async () => {
|
||||
const { result } = renderHook(() => useVariables(), {
|
||||
wrapper: Providers,
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
result.current.registerVariable({
|
||||
name: '$new',
|
||||
ctx: {
|
||||
name: 'new variable',
|
||||
},
|
||||
defaultValue: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $new.noExist }}')).toBe(undefined);
|
||||
});
|
||||
@ -679,11 +720,27 @@ describe('useVariables', () => {
|
||||
belongsToField: null,
|
||||
},
|
||||
collectionName: 'some',
|
||||
defaultValue: 'default value',
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
// 因为 $some 的 ctx 没有 id 所以无法获取关系字段的数据
|
||||
expect(await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}')).toBe(undefined);
|
||||
// 只有解析后的值是 undefined 才会使用默认值
|
||||
expect(await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}')).toBe(null);
|
||||
});
|
||||
|
||||
// 会覆盖之前的 $some
|
||||
result.current.registerVariable({
|
||||
name: '$some',
|
||||
ctx: {
|
||||
name: 'new variable',
|
||||
},
|
||||
collectionName: 'some',
|
||||
defaultValue: 'default value',
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
// 解析后的值是 undefined 所以会返回上面设置的默认值
|
||||
expect(await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}')).toBe('default value');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -23,7 +23,7 @@ const useBuiltInVariables = () => {
|
||||
const { currentUserCtx } = useCurrentUserVariable();
|
||||
const { currentRoleCtx } = useCurrentRoleVariable();
|
||||
const { datetimeCtx } = useDatetimeVariable();
|
||||
const { urlSearchParamsCtx, name: urlSearchParamsName } = useURLSearchParamsVariable();
|
||||
const { urlSearchParamsCtx, name: urlSearchParamsName, defaultValue } = useURLSearchParamsVariable();
|
||||
const builtinVariables: VariableOption[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@ -80,9 +80,10 @@ const useBuiltInVariables = () => {
|
||||
{
|
||||
name: urlSearchParamsName,
|
||||
ctx: urlSearchParamsCtx,
|
||||
defaultValue,
|
||||
},
|
||||
];
|
||||
}, [currentRoleCtx, currentUserCtx, datetimeCtx, urlSearchParamsCtx, urlSearchParamsName]);
|
||||
}, [currentRoleCtx, currentUserCtx, datetimeCtx, defaultValue, urlSearchParamsCtx, urlSearchParamsName]);
|
||||
|
||||
return { builtinVariables };
|
||||
};
|
||||
|
@ -88,4 +88,11 @@ export interface VariableOption {
|
||||
collectionName?: string;
|
||||
/** 数据表所对应的数据源 */
|
||||
dataSource?: string;
|
||||
/**
|
||||
* @default null
|
||||
* 表示当变量解析出来的值是一个 undefined 时,最终应该返回的值。
|
||||
* 默认是 null,这样可以保证数据范围中的 filter 条件不会被清除掉,
|
||||
* 如果想让数据范围中的 filter 条件被清除掉,可以设置 defaultValue 为 undefined。
|
||||
*/
|
||||
defaultValue?: any;
|
||||
}
|
||||
|
@ -90,6 +90,14 @@ describe('getValuesByPath', () => {
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
it('when return is null', () => {
|
||||
const obj = {
|
||||
a: { b: null },
|
||||
};
|
||||
const result = getValuesByPath(obj, 'a.b');
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return empty array when obj key value is undefined', () => {
|
||||
const obj = {
|
||||
a: undefined,
|
||||
|
@ -38,15 +38,15 @@ export const getValuesByPath = (obj: object, path: string, defaultValue?: any) =
|
||||
}
|
||||
}
|
||||
|
||||
result = result.filter((item) => item != null);
|
||||
result = result.filter((item) => item !== undefined);
|
||||
|
||||
if (result.length === 0) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (shouldReturnArray) {
|
||||
return result;
|
||||
return result.filter((item) => item !== null);
|
||||
}
|
||||
|
||||
return result.length === 1 ? result[0] : result;
|
||||
return result[0];
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user