fix: use appends param to load association data (#3282)

* fix: use appends param to load association data

* chore: update yarn.lock

* test: add test

* test: remove the 'BUG:' text

* test: fix 'window is not defined'

* test: increase timeout
This commit is contained in:
Zeke Zhang 2023-12-29 11:34:27 +08:00 committed by GitHub
parent 0c12fbce29
commit c6915c69f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1641 additions and 26 deletions

View File

@ -305,7 +305,8 @@ export const BlockProvider = (props: {
}) => { }) => {
const { collection, association, name } = props; const { collection, association, name } = props;
const resource = useResource(props); const resource = useResource(props);
const { appends, updateAssociationValues } = useAssociationNames(); const { getAssociationAppends } = useAssociationNames();
const { appends, updateAssociationValues } = getAssociationAppends();
const params = useMemo(() => { const params = useMemo(() => {
if (!props.params?.['appends']) { if (!props.params?.['appends']) {
return { ...props.params, appends }; return { ...props.params, appends };

View File

@ -1295,11 +1295,11 @@ export function getAssociationPath(str) {
} }
export const useAssociationNames = () => { export const useAssociationNames = () => {
let updateAssociationValues = new Set([]);
let appends = new Set([]);
const { getCollectionJoinField, getCollection } = useCollectionManager(); const { getCollectionJoinField, getCollection } = useCollectionManager();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const updateAssociationValues = new Set([]); const _getAssociationAppends = (schema, str) => {
const appends = new Set([]);
const getAssociationAppends = (schema, str) => {
schema.reduceProperties((pre, s) => { schema.reduceProperties((pre, s) => {
const prefix = pre || str; const prefix = pre || str;
const collectionField = s['x-collection-field'] && getCollectionJoinField(s['x-collection-field']); const collectionField = s['x-collection-field'] && getCollectionJoinField(s['x-collection-field']);
@ -1338,7 +1338,7 @@ export const useAssociationNames = () => {
if (['Nester', 'SubTable', 'PopoverNester'].includes(s['x-component-props']?.mode)) { if (['Nester', 'SubTable', 'PopoverNester'].includes(s['x-component-props']?.mode)) {
updateAssociationValues.add(path); updateAssociationValues.add(path);
const bufPrefix = prefix && prefix !== '' ? prefix + '.' + s.name : s.name; const bufPrefix = prefix && prefix !== '' ? prefix + '.' + s.name : s.name;
getAssociationAppends(s, bufPrefix); _getAssociationAppends(s, bufPrefix);
} }
} else if ( } else if (
![ ![
@ -1354,14 +1354,20 @@ export const useAssociationNames = () => {
'TableField', 'TableField',
].includes(s['x-component']) ].includes(s['x-component'])
) { ) {
getAssociationAppends(s, str); _getAssociationAppends(s, str);
} }
}, str); }, str);
}; };
getAssociationAppends(fieldSchema, ''); const getAssociationAppends = () => {
updateAssociationValues = new Set([]);
appends = new Set([]);
_getAssociationAppends(fieldSchema, '');
return { appends: [...appends], updateAssociationValues: [...updateAssociationValues] }; return { appends: [...appends], updateAssociationValues: [...updateAssociationValues] };
}; };
return { getAssociationAppends };
};
function getTargetField(obj) { function getTargetField(obj) {
function getAllKeys(obj) { function getAllKeys(obj) {
const keys = []; const keys = [];

View File

@ -6,7 +6,7 @@ import {
oneTableSubtableWithMultiLevelAssociationFields, oneTableSubtableWithMultiLevelAssociationFields,
test, test,
} from '@nocobase/test/e2e'; } from '@nocobase/test/e2e';
import { T2200, T2614, T2615 } from './templatesOfBug'; import { T2200, T2614, T2615, T2845 } from './templatesOfBug';
test.describe('display association fields', () => { test.describe('display association fields', () => {
test('form: should display correctly', async ({ page, mockPage, mockRecord }) => { test('form: should display correctly', async ({ page, mockPage, mockRecord }) => {
@ -50,7 +50,7 @@ test.describe('display association fields', () => {
}); });
// https://nocobase.height.app/T-2615 // https://nocobase.height.app/T-2615
test('BUG: should load association data', async ({ page, mockPage, mockRecord }) => { test('should load association data', async ({ page, mockPage, mockRecord }) => {
const nocoPage = await mockPage(T2615).waitForInit(); const nocoPage = await mockPage(T2615).waitForInit();
await mockRecord('T2615'); await mockRecord('T2615');
await nocoPage.goto(); await nocoPage.goto();
@ -87,8 +87,57 @@ test.describe('display association fields', () => {
); );
}); });
// https://nocobase.height.app/T-2845
test('should load association data of subform', async ({ page, mockPage, mockRecord }) => {
const nocoPage = await mockPage(T2845).waitForInit();
// 和 T2615 使用一样的数据表结构
const record = await mockRecord('T2615');
await nocoPage.goto();
// 1. 新增表单中应该显示关系字段的数据
await page.getByRole('button', { name: 'Add new' }).click();
await page
.getByLabel('block-item-CollectionField-T2615-form-T2615.m2o-m2o')
.getByTestId('select-object-single')
.click();
await page.getByRole('option', { name: String(record.m2o.id) }).click();
await expect(page.getByLabel('block-item-CollectionField-T2615Target2-form-T2615Target2.id-ID')).toHaveText(
`ID:${record.m2o.m2oOfTarget1.id}`,
);
await expect(page.getByLabel('block-item-CollectionField-T2615Target2-form-T2615Target2.m2oOfTarget2-')).toHaveText(
`m2oOfTarget2:${record.m2o.m2oOfTarget1.m2oOfTarget2.id}`,
);
// 关闭弹窗
await page.getByLabel('drawer-Action.Container-T2615-Add record-mask').click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
// 2. 编辑表单中应该显示关系字段的数据
await page.getByLabel(`action-Action.Link-Edit record-update-T2615-table-${record.id - 1}`).click();
await expect(page.getByLabel('block-item-CollectionField-T2615Target2-form-T2615Target2.id-ID')).toHaveText(
`ID:${record.m2o.m2oOfTarget1.id}`,
);
await expect(page.getByLabel('block-item-CollectionField-T2615Target2-form-T2615Target2.m2oOfTarget2-')).toHaveText(
`m2oOfTarget2:${record.m2o.m2oOfTarget1.m2oOfTarget2.id}`,
);
await page.getByLabel('drawer-Action.Container-T2615-Edit record-mask').click();
// 3. 详情中应该显示关系字段的数据
await page.getByLabel(`action-Action.Link-View record-view-T2615-table-${record.id - 1}`).click();
await expect(page.getByLabel('block-item-CollectionField-T2615-form-T2615.m2o-m2o')).toHaveText(
`m2o:${record.m2o.id}`,
);
await expect(page.getByLabel('block-item-CollectionField-T2615Target2-form-T2615Target2.id-ID')).toHaveText(
`ID:${record.m2o.m2oOfTarget1.id}`,
);
await expect(page.getByLabel('block-item-CollectionField-T2615Target2-form-T2615Target2.m2oOfTarget2-')).toHaveText(
`m2oOfTarget2:${record.m2o.m2oOfTarget1.m2oOfTarget2.id}`,
);
});
// https://nocobase.height.app/T-2614 // https://nocobase.height.app/T-2614
test('BUG: should load association data in subform', async ({ page, mockPage, mockRecord }) => { test('should load association data in subform', async ({ page, mockPage, mockRecord }) => {
const nocoPage = await mockPage(T2614).waitForInit(); const nocoPage = await mockPage(T2614).waitForInit();
await mockRecord('T2614'); await mockRecord('T2614');
await nocoPage.goto(); await nocoPage.goto();
@ -198,7 +247,7 @@ test.describe('association fields', () => {
}); });
// fix https://nocobase.height.app/T-2200 // fix https://nocobase.height.app/T-2200
test('BUG: should be possible to change the value of the association field normally', async ({ page, mockPage }) => { test('should be possible to change the value of the association field normally', async ({ page, mockPage }) => {
await mockPage(T2200).goto(); await mockPage(T2200).goto();
await page.getByLabel('action-Action.Link-Edit-update-users-table-0').click(); await page.getByLabel('action-Action.Link-Edit-update-users-table-0').click();

View File

@ -212,7 +212,7 @@ test.describe('creation form block schema settings', () => {
}); });
// fix https://nocobase.height.app/T-2165 // fix https://nocobase.height.app/T-2165
test('BUG: variable labels should be displayed normally', async ({ page, mockPage }) => { test('variable labels should be displayed normally', async ({ page, mockPage }) => {
await mockPage(T2165).goto(); await mockPage(T2165).goto();
await page.getByLabel('block-item-CardItem-users-form').hover(); await page.getByLabel('block-item-CardItem-users-form').hover();
@ -628,7 +628,7 @@ test.describe('creation form block schema settings', () => {
}); });
// fix https://nocobase.height.app/T-2174 // fix https://nocobase.height.app/T-2174
test('BUG: should show default value option', async ({ page, mockPage, mockRecord }) => { test('should show default value option', async ({ page, mockPage, mockRecord }) => {
const nocoPage = await mockPage(T2174).waitForInit(); const nocoPage = await mockPage(T2174).waitForInit();
await mockRecord('test2174'); await mockRecord('test2174');
await nocoPage.goto(); await nocoPage.goto();

View File

@ -2,7 +2,7 @@ import { expect, test } from '@nocobase/test/e2e';
import { T2187 } from '../templatesOfBug'; import { T2187 } from '../templatesOfBug';
// fix https://nocobase.height.app/T-2187 // fix https://nocobase.height.app/T-2187
test('BUG: in the Duplicate mode, the Roles field should not have a value after clicking it because it is not selected', async ({ test('in the Duplicate mode, the Roles field should not have a value after clicking it because it is not selected', async ({
page, page,
mockPage, mockPage,
}) => { }) => {

View File

@ -2,7 +2,7 @@ import { expect, test } from '@nocobase/test/e2e';
import { T2183, T2186 } from '../templatesOfBug'; import { T2183, T2186 } from '../templatesOfBug';
// fix https://nocobase.height.app/T-2183 // fix https://nocobase.height.app/T-2183
test('BUG: should save conditions', async ({ page, mockPage }) => { test('should save conditions', async ({ page, mockPage }) => {
await mockPage(T2183).goto(); await mockPage(T2183).goto();
await page.getByLabel('action-Filter.Action-Filter-filter-users-table').click(); await page.getByLabel('action-Filter.Action-Filter-filter-users-table').click();
await page.getByText('Add condition', { exact: true }).click(); await page.getByText('Add condition', { exact: true }).click();
@ -19,7 +19,7 @@ test('BUG: should save conditions', async ({ page, mockPage }) => {
}); });
// fix https://nocobase.height.app/T-2186 // fix https://nocobase.height.app/T-2186
test('BUG: the input box displayed should correspond to the field type', async ({ page, mockPage }) => { test('the input box displayed should correspond to the field type', async ({ page, mockPage }) => {
await mockPage(T2186).goto(); await mockPage(T2186).goto();
await page.getByLabel('action-Filter.Action-Filter-filter-users-table').click(); await page.getByLabel('action-Filter.Action-Filter-filter-users-table').click();

View File

@ -3,6 +3,7 @@ import { useField, useFieldSchema, useForm } from '@formily/react';
import { nextTick } from '@nocobase/utils/client'; import { nextTick } from '@nocobase/utils/client';
import _ from 'lodash'; import _ from 'lodash';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { useAssociationNames } from '../../../../block-provider';
import { useCollection, useCollectionManager } from '../../../../collection-manager'; import { useCollection, useCollectionManager } from '../../../../collection-manager';
import { useFlag } from '../../../../flag-provider'; import { useFlag } from '../../../../flag-provider';
import { useVariables } from '../../../../variables'; import { useVariables } from '../../../../variables';
@ -25,6 +26,7 @@ const useLazyLoadDisplayAssociationFieldsOfForm = () => {
const field = useField<Field>(); const field = useField<Field>();
const { formValue: subFormValue } = useSubFormValue(); const { formValue: subFormValue } = useSubFormValue();
const { isInSubForm, isInSubTable } = useFlag() || {}; const { isInSubForm, isInSubTable } = useFlag() || {};
const { getAssociationAppends } = useAssociationNames();
const schemaName = fieldSchema.name.toString(); const schemaName = fieldSchema.name.toString();
const formValue = _.cloneDeep(isInSubForm || isInSubTable ? subFormValue : form.values); const formValue = _.cloneDeep(isInSubForm || isInSubTable ? subFormValue : form.values);
@ -66,8 +68,10 @@ const useLazyLoadDisplayAssociationFieldsOfForm = () => {
return; return;
} }
const { appends } = getAssociationAppends();
variables variables
.parseVariable(variableString, formVariable) .parseVariable(variableString, formVariable, { appends })
.then((value) => { .then((value) => {
nextTick(() => { nextTick(() => {
const result = transformVariableValue(value, { targetCollectionField: collectionFieldRef.current }); const result = transformVariableValue(value, { targetCollectionField: collectionFieldRef.current });

View File

@ -51,7 +51,14 @@ const VariablesProvider = ({ children }) => {
* 3. `key` `key` * 3. `key` `key`
*/ */
const getValue = useCallback( const getValue = useCallback(
async (variablePath: string, localVariables?: VariableOption[]) => { async (
variablePath: string,
localVariables?: VariableOption[],
options?: {
/** 第一次请求时,需要包含的关系字段 */
appends?: string[];
},
) => {
const list = variablePath.split('.'); const list = variablePath.split('.');
const variableName = list[0]; const variableName = list[0];
const _variableToCollectionName = mergeVariableToCollectionNameWithLocalVariables( const _variableToCollectionName = mergeVariableToCollectionNameWithLocalVariables(
@ -85,6 +92,9 @@ const VariablesProvider = ({ children }) => {
const result = api const result = api
.request({ .request({
url, url,
params: {
appends: options?.appends,
},
}) })
.then((data) => { .then((data) => {
clearRequested(url); clearRequested(url);
@ -106,6 +116,9 @@ const VariablesProvider = ({ children }) => {
} else { } else {
const waitForData = api.request({ const waitForData = api.request({
url, url,
params: {
appends: options?.appends,
},
}); });
stashRequested(url, waitForData); stashRequested(url, waitForData);
data = await waitForData; data = await waitForData;
@ -180,7 +193,14 @@ const VariablesProvider = ({ children }) => {
* @param localVariables * @param localVariables
* @returns * @returns
*/ */
async (str: string, localVariables?: VariableOption | VariableOption[]) => { async (
str: string,
localVariables?: VariableOption | VariableOption[],
options?: {
/** 第一次请求时,需要包含的关系字段 */
appends?: string[];
},
) => {
if (!isVariable(str)) { if (!isVariable(str)) {
return str; return str;
} }
@ -190,7 +210,7 @@ const VariablesProvider = ({ children }) => {
} }
const path = getPath(str); const path = getPath(str);
const value = await getValue(path, localVariables as VariableOption[]); const value = await getValue(path, localVariables as VariableOption[], options);
return uniq(filterEmptyValues(value)); return uniq(filterEmptyValues(value));
}, },

View File

@ -29,7 +29,14 @@ export interface VariablesContextType {
* console.log(value); // test * console.log(value); // test
* ``` * ```
*/ */
parseVariable: (str: string, localVariable?: VariableOption | VariableOption[]) => Promise<any>; parseVariable: (
str: string,
localVariable?: VariableOption | VariableOption[],
options?: {
/** 第一次请求时,需要包含的关系字段 */
appends?: string[];
},
) => Promise<any>;
/** /**
* *
* @param variableOption * @param variableOption

View File

@ -34,6 +34,10 @@ Object.defineProperty(window, 'matchMedia', {
const { getComputedStyle } = window; const { getComputedStyle } = window;
window.getComputedStyle = (elt) => getComputedStyle(elt); window.getComputedStyle = (elt) => getComputedStyle(elt);
// 解决 https://github.com/nocobase/nocobase/actions/runs/7353181446/job/20018831007?pr=3282
// 该错误是发生在测试环境之后的,应该是存在异步代码没有 await 导致的,但是不知道是哪里的问题,所以先这样处理
global.window = window;
/** /**
* TypeError: range.getBoundingClientRect is not a function * TypeError: range.getBoundingClientRect is not a function
* https://github.com/jsdom/jsdom/issues/3002 * https://github.com/jsdom/jsdom/issues/3002

View File

@ -71,6 +71,7 @@ test.describe('form item & create form', () => {
}); });
test('pattern', async ({ page, mockPage }) => { test('pattern', async ({ page, mockPage }) => {
test.slow();
await testPattern({ await testPattern({
page, page,
gotoPage: () => gotoPage: () =>

View File

@ -1,4 +1,3 @@
import React, { useRef, useMemo, useContext } from 'react';
import { createForm } from '@formily/core'; import { createForm } from '@formily/core';
import { useField } from '@formily/react'; import { useField } from '@formily/react';
import { import {
@ -9,8 +8,9 @@ import {
useAPIClient, useAPIClient,
useAssociationNames, useAssociationNames,
} from '@nocobase/client'; } from '@nocobase/client';
import { parse } from '@nocobase/utils/client';
import { useFlowContext } from '@nocobase/plugin-workflow/client'; import { useFlowContext } from '@nocobase/plugin-workflow/client';
import { parse } from '@nocobase/utils/client';
import React, { useContext, useMemo, useRef } from 'react';
function useFlowContextData(dataSource) { function useFlowContextData(dataSource) {
const { execution, nodes } = useFlowContext(); const { execution, nodes } = useFlowContext();
@ -37,7 +37,8 @@ function useFlowContextData(dataSource) {
export function DetailsBlockProvider(props) { export function DetailsBlockProvider(props) {
const field = useField(); const field = useField();
const formBlockRef = useRef(null); const formBlockRef = useRef(null);
const { appends, updateAssociationValues } = useAssociationNames(); const { getAssociationAppends } = useAssociationNames();
const { appends, updateAssociationValues } = getAssociationAppends();
const values = useFlowContextData(props.dataSource); const values = useFlowContextData(props.dataSource);
const form = useMemo( const form = useMemo(

View File

@ -19,7 +19,8 @@ export function FormBlockProvider(props) {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const field = useField(); const field = useField();
const formBlockRef = useRef(null); const formBlockRef = useRef(null);
const { appends, updateAssociationValues } = useAssociationNames(); const { getAssociationAppends } = useAssociationNames();
const { appends, updateAssociationValues } = getAssociationAppends();
const [formKey] = Object.keys(fieldSchema.toJSON().properties ?? {}); const [formKey] = Object.keys(fieldSchema.toJSON().properties ?? {});
const values = userJob?.result?.[formKey]; const values = userJob?.result?.[formKey];

View File

@ -4215,6 +4215,11 @@
dependencies: dependencies:
eslint-scope "5.1.1" eslint-scope "5.1.1"
"@nocobase/plugin-workflow-test@>=0.17.0-alpha.3":
version "0.17.0-alpha.7"
resolved "https://registry.yarnpkg.com/@nocobase/plugin-workflow-test/-/plugin-workflow-test-0.17.0-alpha.7.tgz#219a3a1e91e51bec08b1adbb1c4c5c9c7184ecce"
integrity sha512-krZlo1xDM66spbQG6jAYSraxazuxCuA2rD9TxptFQXjkAMTGkuDilJ+edMj6p866N3nyzvFh8lZ2/XY1HUC7Xw==
"@node-saml/node-saml@^4.0.2": "@node-saml/node-saml@^4.0.2":
version "4.0.5" version "4.0.5"
resolved "https://registry.npmmirror.com/@node-saml/node-saml/-/node-saml-4.0.5.tgz#039e387095b54639b06df62b1b4a6d8941c6d907" resolved "https://registry.npmmirror.com/@node-saml/node-saml/-/node-saml-4.0.5.tgz#039e387095b54639b06df62b1b4a6d8941c6d907"