fix(defaultValue): ignores variable values that do not match the current field (#5122)

* refactor(variable): refactor parseVariable to support return more context

* fix(defaultValue): ignore no matching value

* fix: make unit tests pass

* chore: fix unit test
This commit is contained in:
Zeke Zhang 2024-08-28 11:53:52 +08:00 committed by GitHub
parent 77281af61d
commit 67d2f85a99
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 177 additions and 81 deletions

View File

@ -24,7 +24,7 @@ describe('parseVariablesAndChangeParamsToQueryString', () => {
{ name: 'param3', value: 'value3' },
];
const variables: any = {
parseVariable: vi.fn().mockResolvedValue('parsedValue'),
parseVariable: vi.fn().mockResolvedValue({ value: 'parsedValue' }),
};
const localVariables: any = [
{ name: '$var1', ctx: { value: 'localValue1' } },

View File

@ -163,9 +163,9 @@ export function useCollectValuesToSubmit() {
}
if (isVariable(value)) {
const result = await variables?.parseVariable(value, localVariables);
if (result) {
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
assignedValues[key] = value;
@ -324,9 +324,9 @@ export const useAssociationCreateActionProps = () => {
}
if (isVariable(value)) {
const result = await variables?.parseVariable(value, localVariables);
if (result) {
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
assignedValues[key] = value;
@ -582,9 +582,9 @@ export const useCustomizeUpdateActionProps = () => {
}
if (isVariable(value)) {
const result = await variables?.parseVariable(value, localVariables);
if (result) {
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
assignedValues[key] = value;
@ -680,9 +680,9 @@ export const useCustomizeBulkUpdateActionProps = () => {
}
if (isVariable(value)) {
const result = await variables?.parseVariable(value, localVariables);
if (result) {
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
assignedValues[key] = value;
@ -888,9 +888,9 @@ export const useUpdateActionProps = () => {
}
if (isVariable(value)) {
const result = await variables?.parseVariable(value, localVariables);
if (result) {
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
assignedValues[key] = value;
@ -1661,8 +1661,8 @@ export async function parseVariablesAndChangeParamsToQueryString({
searchParams.map(async ({ name, value }) => {
if (typeof value === 'string') {
if (isVariable(value)) {
const result = await variables.parseVariable(value, localVariables);
return { name, value: result };
const { value: parsedValue } = (await variables.parseVariable(value, localVariables)) || {};
return { name, value: parsedValue };
}
const result = await replaceVariableValue(value, variables, localVariables);
return { name, value: result };

View File

@ -9,6 +9,7 @@
import { Field } from '@formily/core';
import { useField, useFieldSchema, useForm } from '@formily/react';
import { untracked } from '@formily/reactive';
import { nextTick } from '@nocobase/utils/client';
import _ from 'lodash';
import { useEffect, useMemo, useRef } from 'react';
@ -20,7 +21,6 @@ import { useVariables } from '../../../../variables';
import { transformVariableValue } from '../../../../variables/utils/transformVariableValue';
import { useSubFormValue } from '../../association-field/hooks';
import { isDisplayField } from '../utils';
import { untracked } from '@formily/reactive';
/**
* Form
@ -97,7 +97,7 @@ const useLazyLoadDisplayAssociationFieldsOfForm = () => {
variables
.parseVariable(variableString, formVariable, { appends })
.then((value) => {
.then(({ value }) => {
nextTick(() => {
const result = transformVariableValue(value, { targetCollectionField: collectionFieldRef.current });
// fix https://nocobase.height.app/T-2608

View File

@ -95,7 +95,18 @@ const useParseDefaultValue = () => {
}
}
const value = transformVariableValue(await variables.parseVariable(fieldSchema.default, localVariables), {
const { value: parsedValue, collectionName: collectionNameOfVariable } = await variables.parseVariable(
fieldSchema.default,
localVariables,
);
// fix https://tasks.aliyun.nocobase.com/admin/ugmnj2ycfgg/popups/1qlw5c38t3b/puid/dz42x7ffr7i/filterbytk/199
if (collectionField.target && collectionField.target !== collectionNameOfVariable) {
field.loading = false;
return;
}
const value = transformVariableValue(parsedValue, {
targetCollectionField: collectionField,
});

View File

@ -156,14 +156,14 @@ export async function replaceVariables(
}
const waitForParsing = value.match(REGEX_OF_VARIABLE_IN_EXPRESSION)?.map(async (item) => {
const result = await variables.parseVariable(item, localVariables);
const { value: parsedValue } = await variables.parseVariable(item, localVariables);
// 在开头加 `_` 是为了保证 id 不能以数字开头,否则在解析表达式的时候(不是解析变量)会报错
const id = `_${uid()}`;
scope[id] = result;
scope[id] = parsedValue;
store[item] = id;
return result;
return parsedValue;
});
if (waitForParsing) {

View File

@ -7,13 +7,13 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import _, { every, findIndex, some } from 'lodash';
import Handlebars from 'handlebars';
import _, { every, findIndex, some } from 'lodash';
import { replaceVariableValue } from '../../../block-provider/hooks';
import { VariableOption, VariablesContextType } from '../../../variables/types';
import { isVariable } from '../../../variables/utils/isVariable';
import { transformVariableValue } from '../../../variables/utils/transformVariableValue';
import { getJsonLogic } from '../../common/utils/logic';
import { replaceVariableValue } from '../../../block-provider/hooks';
type VariablesCtx = {
/** 当前登录的用户 */
@ -102,12 +102,14 @@ export const conditionAnalyses = async ({
}
const targetVariableName = targetFieldToVariableString(getTargetField(condition));
const targetValue = variables.parseVariable(targetVariableName, localVariables, {
const targetValue = variables
.parseVariable(targetVariableName, localVariables, {
doNotRequest: true,
});
})
.then(({ value }) => value);
const parsingResult = isVariable(jsonlogic?.value)
? [variables.parseVariable(jsonlogic?.value, localVariables), targetValue]
? [variables.parseVariable(jsonlogic?.value, localVariables).then(({ value }) => value), targetValue]
: [jsonlogic?.value, targetValue];
try {

View File

@ -55,7 +55,7 @@ const useParseDataScopeFilter = ({ exclude = defaultExclude }: Props = {}) => {
if (exclude.includes(getVariableName(value))) {
return value;
}
const result = variables?.parseVariable(value, localVariables);
const result = variables?.parseVariable(value, localVariables).then(({ value }) => value);
return result;
},
});

View File

@ -69,7 +69,7 @@ const VariablesProvider = ({ children }) => {
* 2. `key` `key` api `ctx`
* 3. `key` `key`
*/
const getValue = useCallback(
const getResult = useCallback(
async (
variablePath: string,
localVariables?: VariableOption[],
@ -87,13 +87,23 @@ const VariablesProvider = ({ children }) => {
const { fieldPath, dataSource, variableOption } = getFieldPath(variableName, _variableToCollectionName);
let collectionName = fieldPath;
const { fieldPath: fieldPathOfVariable } = getFieldPath(variablePath, _variableToCollectionName);
const collectionNameOfVariable =
list.length === 1
? variableOption.collectionName
: getCollectionJoinField(fieldPathOfVariable, dataSource)?.target;
if (!(variableName in current)) {
throw new Error(`VariablesProvider: ${variableName} is not found`);
}
for (let index = 0; index < list.length; index++) {
if (current == null) {
return current === undefined ? variableOption.defaultValue : current;
return {
value: current === undefined ? variableOption.defaultValue : current,
dataSource,
collectionName: collectionNameOfVariable,
};
}
const key = list[index];
@ -171,8 +181,12 @@ const VariablesProvider = ({ children }) => {
}
}
const result = compile(_.isFunction(current) ? current() : current);
return result === undefined ? variableOption.defaultValue : result;
const _value = compile(_.isFunction(current) ? current() : current);
return {
value: _value === undefined ? variableOption.defaultValue : _value,
dataSource,
collectionName: collectionNameOfVariable,
};
},
[getCollectionJoinField],
);
@ -248,11 +262,14 @@ const VariablesProvider = ({ children }) => {
}
const path = getPath(str);
const value = await getValue(path, localVariables as VariableOption[], options);
const result = await getResult(path, localVariables as VariableOption[], options);
return uniq(filterEmptyValues(value));
return {
...result,
value: uniq(filterEmptyValues(result.value)),
};
},
[getValue],
[getResult],
);
const getCollectionField = useCallback(

View File

@ -276,7 +276,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.nickname }}')).toBe('test');
expect(await result.current.parseVariable('{{ $user.nickname }}').then(({ value }) => value)).toBe('test');
});
});
@ -286,7 +286,9 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.nickname }}')).toBe('from request');
expect(await result.current.parseVariable('{{ $user.nickname }}').then(({ value }) => value)).toBe(
'from request',
);
});
});
@ -296,7 +298,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.belongsToField }}')).toEqual({
expect(await result.current.parseVariable('{{ $user.belongsToField }}').then(({ value }) => value)).toEqual({
id: 0,
name: '$user.belongsToField',
});
@ -309,9 +311,11 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.belongsToField }}', undefined, { doNotRequest: true })).toBe(
null,
);
expect(
await result.current
.parseVariable('{{ $user.belongsToField }}', undefined, { doNotRequest: true })
.then(({ value }) => value),
).toBe(null);
});
});
@ -321,7 +325,9 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.belongsToField.name }}')).toBe('$user.belongsToField');
expect(await result.current.parseVariable('{{ $user.belongsToField.name }}').then(({ value }) => value)).toBe(
'$user.belongsToField',
);
});
});
@ -331,7 +337,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.hasManyField }}')).toEqual([
expect(await result.current.parseVariable('{{ $user.hasManyField }}').then(({ value }) => value)).toEqual([
{
id: 0,
name: '$user.hasManyField',
@ -340,7 +346,9 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.hasManyField.name }}')).toEqual(['$user.hasManyField']);
expect(await result.current.parseVariable('{{ $user.hasManyField.name }}').then(({ value }) => value)).toEqual([
'$user.hasManyField',
]);
});
});
@ -350,7 +358,9 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.hasManyField.hasManyField }}')).toEqual([
expect(
await result.current.parseVariable('{{ $user.hasManyField.hasManyField }}').then(({ value }) => value),
).toEqual([
{
id: 0,
name: '$user.hasManyField.hasManyField',
@ -359,15 +369,34 @@ describe('useVariables', () => {
});
});
it('$user.hasManyField', async () => {
const { result } = renderHook(() => useVariables(), {
wrapper: Providers,
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.hasManyField }}')).toEqual({
collectionName: 'test',
dataSource: 'main',
value: [
{
id: 0,
name: '$user.hasManyField',
},
],
});
});
});
it('$user.hasManyField.hasManyField.name', async () => {
const { result } = renderHook(() => useVariables(), {
wrapper: Providers,
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.hasManyField.hasManyField.name }}')).toEqual([
'$user.hasManyField.hasManyField',
]);
expect(
await result.current.parseVariable('{{ $user.hasManyField.hasManyField.name }}').then(({ value }) => value),
).toEqual(['$user.hasManyField.hasManyField']);
});
});
@ -393,7 +422,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.hasManyField }}')).toEqual([
expect(await result.current.parseVariable('{{ $user.hasManyField }}').then(({ value }) => value)).toEqual([
{
id: 0,
name: '$user.hasManyField',
@ -402,7 +431,9 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.hasManyField.hasManyField }}')).toEqual([
expect(
await result.current.parseVariable('{{ $user.hasManyField.hasManyField }}').then(({ value }) => value),
).toEqual([
{
id: 0,
name: '$user.hasManyField.hasManyField',
@ -417,7 +448,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $user.belongsToManyField }}')).toEqual([
expect(await result.current.parseVariable('{{ $user.belongsToManyField }}').then(({ value }) => value)).toEqual([
{
id: 0,
name: '$user.belongsToManyField',
@ -608,7 +639,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $new.name }}')).toBe('new variable');
expect(await result.current.parseVariable('{{ $new.name }}').then(({ value }) => value)).toBe('new variable');
});
});
@ -627,7 +658,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $new.noExist }}')).toBe(null);
expect(await result.current.parseVariable('{{ $new.noExist }}').then(({ value }) => value)).toBe(null);
});
});
@ -647,7 +678,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $new.noExist }}')).toBe('default value');
expect(await result.current.parseVariable('{{ $new.noExist }}').then(({ value }) => value)).toBe('default value');
});
});
@ -667,7 +698,7 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $new.noExist }}')).toBe(undefined);
expect(await result.current.parseVariable('{{ $new.noExist }}').then(({ value }) => value)).toBe(undefined);
});
});
@ -686,8 +717,30 @@ describe('useVariables', () => {
ctx: {
name: 'local variable',
},
collectionName: 'local',
dataSource: 'local',
}),
).toBe('local variable');
).toEqual({
value: 'local variable',
dataSource: 'local',
});
expect(
await result.current.parseVariable('{{ $local }}', {
name: '$local',
ctx: {
name: 'local variable',
},
collectionName: 'local',
dataSource: 'local',
}),
).toEqual({
value: {
name: 'local variable',
},
collectionName: 'local',
dataSource: 'local',
});
// 由于 $local 是一个局部变量,所以不会被缓存到 ctx 中
expect(result.current.getVariable('$local')).toBe(null);
@ -703,14 +756,16 @@ describe('useVariables', () => {
});
expect(
await result.current.parseVariable('{{ $local.name }}', [
await result.current
.parseVariable('{{ $local.name }}', [
{
name: '$local',
ctx: {
name: 'local variable',
},
},
]),
])
.then(({ value }) => value),
).toBe('local variable');
// 由于 $local 是一个局部变量,所以不会被缓存到 ctx 中
@ -764,7 +819,9 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}')).toEqual({
expect(
await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}').then(({ value }) => value),
).toEqual({
id: 0,
name: '$some.belongsToField.belongsToField',
});
@ -783,7 +840,9 @@ describe('useVariables', () => {
await waitFor(async () => {
// 只有解析后的值是 undefined 才会使用默认值
expect(await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}')).toBe(null);
expect(
await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}').then(({ value }) => value),
).toBe(null);
});
// 会覆盖之前的 $some
@ -798,7 +857,9 @@ describe('useVariables', () => {
await waitFor(async () => {
// 解析后的值是 undefined 所以会返回上面设置的默认值
expect(await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}')).toBe('default value');
expect(
await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}').then(({ value }) => value),
).toBe('default value');
});
});

View File

@ -47,7 +47,14 @@ export interface VariablesContextType {
/** do not request when the association field is empty */
doNotRequest?: boolean;
},
) => Promise<any>;
) => Promise<{
value: any;
/**
*
*/
collectionName?: string;
dataSource?: string;
}>;
/**
*
* @param variableOption

View File

@ -64,7 +64,7 @@ export const useCustomizeBulkUpdateActionProps = () => {
}
if (isVariable(value)) {
const result = await variables?.parseVariable(value, localVariables);
const result = await variables?.parseVariable(value, localVariables).then(({ value }) => value);
if (result) {
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
}

View File

@ -7,10 +7,10 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Schema } from '@formily/react';
import {
Collection,
CollectionFieldInterfaceManager,
CollectionFieldOptions,
CollectionManager,
SchemaInitializerItemType,
i18n,
@ -19,18 +19,16 @@ import {
useDataSourceManager,
useVariables,
} from '@nocobase/client';
import { flatten, parse, unflatten } from '@nocobase/utils/client';
import { useMemoizedFn } from 'ahooks';
import deepmerge from 'deepmerge';
import { default as _, default as lodash } from 'lodash';
import { useCallback, useContext, useMemo } from 'react';
import { ChartDataContext } from '../block/ChartDataProvider';
import { Schema } from '@formily/react';
import { useChartsTranslation } from '../locale';
import { ChartFilterContext } from '../filter/FilterProvider';
import { useMemoizedFn } from 'ahooks';
import { flatten, parse, unflatten } from '@nocobase/utils/client';
import lodash from 'lodash';
import { getFormulaComponent, getValuesByPath } from '../utils';
import deepmerge from 'deepmerge';
import { findSchema, getFilterFieldPrefix, parseFilterFieldName } from '../filter/utils';
import _ from 'lodash';
import { useChartsTranslation } from '../locale';
import { getFormulaComponent, getValuesByPath } from '../utils';
export const useCustomFieldInterface = () => {
const { getInterface } = useCollectionManager_deprecated();
@ -420,7 +418,7 @@ export const useChartFilter = () => {
if (['$user', '$date', '$nDate', '$nRole', '$nFilter'].some((n) => value.includes(n))) {
return value;
}
const result = variables?.parseVariable(value);
const result = variables?.parseVariable(value).then(({ value }) => value);
return result;
},
});