fix(Details): fix refresh issue (#5826)

* fix(Details): fix refresh issue

* test(e2e): add test
This commit is contained in:
Zeke Zhang 2024-12-08 06:30:53 +08:00 committed by GitHub
parent 419086115b
commit f61494e951
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 549 additions and 65 deletions

View File

@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { useFieldSchema } from '@formily/react';
import { observer, useFieldSchema } from '@formily/react';
import { toArr } from '@formily/shared';
import React, { Fragment, useRef } from 'react';
import { useDesignable } from '../../';
@ -33,72 +33,77 @@ const toValue = (value, placeholder) => {
return value;
};
const ButtonTabList: React.FC<ButtonListProps> = (props) => {
const fieldSchema = useFieldSchema();
const { enableLink, tagColorField } = fieldSchema['x-component-props'];
const fieldNames = useFieldNames({ fieldNames: props.fieldNames });
const insertViewer = useInsertSchema('Viewer');
const { options: collectionField } = useAssociationFieldContext();
const compile = useCompile();
const { designable } = useDesignable();
const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label');
const { snapshot } = useActionContext();
const ellipsisWithTooltipRef = useRef<IEllipsisWithTooltipRef>();
const { getCollection } = useCollectionManager_deprecated();
const targetCollection = getCollection(collectionField?.target);
const isTreeCollection = targetCollection?.template === 'tree';
const { openPopup } = usePopupUtils();
const recordData = useCollectionRecordData();
const ButtonTabList: React.FC<ButtonListProps> = observer(
(props) => {
const fieldSchema = useFieldSchema();
const { enableLink, tagColorField } = fieldSchema['x-component-props'];
const fieldNames = useFieldNames({ fieldNames: props.fieldNames });
const insertViewer = useInsertSchema('Viewer');
const { options: collectionField } = useAssociationFieldContext();
const compile = useCompile();
const { designable } = useDesignable();
const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label');
const { snapshot } = useActionContext();
const ellipsisWithTooltipRef = useRef<IEllipsisWithTooltipRef>();
const { getCollection } = useCollectionManager_deprecated();
const targetCollection = getCollection(collectionField?.target);
const isTreeCollection = targetCollection?.template === 'tree';
const { openPopup } = usePopupUtils();
const recordData = useCollectionRecordData();
const renderRecords = () =>
toArr(props.value).map((record, index, arr) => {
const value = record?.[fieldNames?.label || 'label'];
const label = isTreeCollection
? transformNestedData(record)
.map((o) => o?.[fieldNames?.label || 'label'])
.join(' / ')
: isObject(value)
? JSON.stringify(value)
: value;
const val = toValue(compile(label), 'N/A');
const text = getTabFormatValue(compile(labelUiSchema), val, record[tagColorField]);
return (
<Fragment key={`${record?.[fieldNames.value]}_${index}`}>
<span>
{snapshot ? (
text
) : enableLink !== false ? (
<a
onMouseEnter={() => {
props.setBtnHover(true);
}}
onClick={(e) => {
props.setBtnHover(true);
e.stopPropagation();
e.preventDefault();
if (designable) {
insertViewer(schema.Viewer);
}
openPopup({
recordData: record,
parentRecordData: recordData,
});
ellipsisWithTooltipRef?.current?.setPopoverVisible(false);
}}
>
{text}
</a>
) : (
text
)}
</span>
{index < arr.length - 1 ? <span style={{ marginRight: 4, color: '#aaa' }}>,</span> : null}
</Fragment>
);
});
const renderRecords = () =>
toArr(props.value).map((record, index, arr) => {
const value = record?.[fieldNames?.label || 'label'];
const label = isTreeCollection
? transformNestedData(record)
.map((o) => o?.[fieldNames?.label || 'label'])
.join(' / ')
: isObject(value)
? JSON.stringify(value)
: value;
const val = toValue(compile(label), 'N/A');
const text = getTabFormatValue(compile(labelUiSchema), val, record[tagColorField]);
return (
<Fragment key={`${record?.[fieldNames.value]}_${index}`}>
<span>
{snapshot ? (
text
) : enableLink !== false ? (
<a
onMouseEnter={() => {
props.setBtnHover(true);
}}
onClick={(e) => {
props.setBtnHover(true);
e.stopPropagation();
e.preventDefault();
if (designable) {
insertViewer(schema.Viewer);
}
openPopup({
recordData: record,
parentRecordData: recordData,
});
ellipsisWithTooltipRef?.current?.setPopoverVisible(false);
}}
>
{text}
</a>
) : (
text
)}
</span>
{index < arr.length - 1 ? <span style={{ marginRight: 4, color: '#aaa' }}>,</span> : null}
</Fragment>
);
});
return <>{renderRecords()}</>;
};
return <>{renderRecords()}</>;
},
{
displayName: 'ButtonTabList',
},
);
export const ReadPrettyInternalTag: React.FC = (props: any) => {
return <ReadPrettyInternalViewer {...props} ButtonList={ButtonTabList} />;

View File

@ -0,0 +1,31 @@
/**
* 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 { shouldRefreshBlockImmediatelyAfterDataChanges } from './template';
test.describe('tag in details block', () => {
test('should refresh block immediately after data changes', async ({ page, mockPage, mockRecord }) => {
const nocoPage = await mockPage(shouldRefreshBlockImmediatelyAfterDataChanges).waitForInit();
await mockRecord('test', { manyToOne: 'admin' });
await nocoPage.goto();
// 1. 一开始字段值显示的是 admin
await expect(page.getByLabel('block-item-CollectionField-').getByText('admin')).toBeVisible();
// 2. 修改数据后,字段值应立即刷新
await page.getByLabel('action-Action-Edit-update-').click();
await page.getByTestId('select-object-single').click();
await page.getByRole('option', { name: 'member' }).click();
await page.getByLabel('action-Action-Submit-submit-').click();
await page.waitForTimeout(1000);
await expect(page.getByLabel('block-item-CollectionField-').getByText('member')).toBeVisible();
});
});

View File

@ -0,0 +1,448 @@
/**
* 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 shouldRefreshBlockImmediatelyAfterDataChanges = {
collections: [
{
name: 'test',
fields: [
{
name: 'manyToOne',
interface: 'm2o',
uiSchema: {
'x-component': 'AssociationField',
'x-component-props': {
multiple: false,
},
title: 'manyToOne',
},
target: 'roles',
targetKey: 'name',
},
],
},
],
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
'x-app-version': '1.6.0-alpha.2',
properties: {
r86ehhl96yp: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
'x-app-version': '1.6.0-alpha.2',
properties: {
'7mc3c7g2ztk': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.0-alpha.2',
properties: {
zsdtdzso928: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.6.0-alpha.2',
properties: {
qx97se01bc9: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-acl-action': 'test:view',
'x-decorator': 'DetailsBlockProvider',
'x-use-decorator-props': 'useDetailsWithPaginationDecoratorProps',
'x-decorator-props': {
dataSource: 'main',
collection: 'test',
readPretty: true,
action: 'list',
params: {
pageSize: 1,
},
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:detailsWithPagination',
'x-component': 'CardItem',
'x-app-version': '1.6.0-alpha.2',
properties: {
'5bw8wirh3ii': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Details',
'x-read-pretty': true,
'x-use-component-props': 'useDetailsWithPaginationProps',
'x-app-version': '1.6.0-alpha.2',
properties: {
ouvwqvlrs0c: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'details:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 24,
},
},
'x-app-version': '1.6.0-alpha.2',
properties: {
q7ovvwwcw7f: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Edit") }}',
'x-action': 'update',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:edit',
'x-component': 'Action',
'x-component-props': {
openMode: 'drawer',
icon: 'EditOutlined',
type: 'primary',
},
'x-action-context': {
dataSource: 'main',
collection: 'test',
},
'x-decorator': 'ACLActionProvider',
'x-app-version': '1.6.0-alpha.2',
properties: {
drawer: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Edit record") }}',
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
},
'x-app-version': '1.6.0-alpha.2',
properties: {
tabs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'popup:addTab',
'x-app-version': '1.6.0-alpha.2',
properties: {
tab1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Edit")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
'x-app-version': '1.6.0-alpha.2',
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:common:addBlock',
'x-app-version': '1.6.0-alpha.2',
properties: {
sude45f40gf: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.0-alpha.2',
properties: {
ah0uni8z97n: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.6.0-alpha.2',
properties: {
kc0w161zdlt: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-acl-action-props': {
skipScopeCheck: false,
},
'x-acl-action': 'test:update',
'x-decorator': 'FormBlockProvider',
'x-use-decorator-props': 'useEditFormBlockDecoratorProps',
'x-decorator-props': {
action: 'get',
dataSource: 'main',
collection: 'test',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:editForm',
'x-component': 'CardItem',
'x-app-version': '1.6.0-alpha.2',
properties: {
xhnj06ts6qh: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'FormV2',
'x-use-component-props': 'useEditFormBlockProps',
'x-app-version': '1.6.0-alpha.2',
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'form:configureFields',
'x-app-version': '1.6.0-alpha.2',
properties: {
hxmmg8dre28: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.0-alpha.2',
properties: {
eohagwrc2g9: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.6.0-alpha.2',
properties: {
manyToOne: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'string',
'x-toolbar': 'FormItemSchemaToolbar',
'x-settings': 'fieldSettings:FormItem',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'test.manyToOne',
'x-component-props': {
fieldNames: {
value: 'name',
label: 'name',
},
},
'x-app-version': '1.6.0-alpha.2',
'x-uid': 'b6jge6icuzy',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'f3t4dx8n0g7',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'kqqx2s52nvb',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '61qq15cmpm0',
'x-async': false,
'x-index': 1,
},
'798dy80zzpf': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'editForm:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
},
'x-app-version': '1.6.0-alpha.2',
properties: {
n6p8c9gaol3: {
_isJSONSchemaObject: true,
version: '2.0',
title: '{{ t("Submit") }}',
'x-action': 'submit',
'x-component': 'Action',
'x-use-component-props': 'useUpdateActionProps',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:updateSubmit',
'x-component-props': {
type: 'primary',
htmlType: 'submit',
},
'x-action-settings': {
triggerWorkflows: [],
},
type: 'void',
'x-app-version': '1.6.0-alpha.2',
'x-uid': 'ykons2vq6xq',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'xaavi2ewome',
'x-async': false,
'x-index': 2,
},
},
'x-uid': '059x400jqrs',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '6ntyxjo1ixr',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'l384eqm0848',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'xxsxrdn4oad',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'rg85la28jjx',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'pxoxj6frur4',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'vdutlbkiqi4',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'x53treofune',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'epym7rg939z',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '7nw6uf8r2xm',
'x-async': false,
'x-index': 1,
},
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'details:configureFields',
'x-app-version': '1.6.0-alpha.2',
properties: {
dbgh1fs4wg1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.0-alpha.2',
properties: {
wpgxiamxdt3: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.6.0-alpha.2',
properties: {
manyToOne: {
'x-uid': '45xqlylaeie',
_isJSONSchemaObject: true,
version: '2.0',
type: 'string',
'x-toolbar': 'FormItemSchemaToolbar',
'x-settings': 'fieldSettings:FormItem',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'test.manyToOne',
'x-component-props': {
fieldNames: {
value: 'name',
label: 'name',
},
mode: 'Tag',
},
'x-app-version': '1.6.0-alpha.2',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'cv8tffpkdkb',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'fzm9py3yc7r',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '817m2t37tnq',
'x-async': false,
'x-index': 2,
},
pagination: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Pagination',
'x-use-component-props': 'useDetailsPaginationProps',
'x-app-version': '1.6.0-alpha.2',
'x-uid': 'ukp3q4sxgnb',
'x-async': false,
'x-index': 3,
},
},
'x-uid': '6mjcax9yfg9',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '539xf5hfzax',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'wsk1gdy6leb',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'a0uip1nhxx5',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '3wmg81ih321',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '4glrwic98l2',
'x-async': true,
'x-index': 1,
},
};