fix(plugin-file-manager): hide attachment from v2 add field (#10018)

* fix(plugin-file-manager): hide attachment from v2 add field

* chore: rerun ci for attachment v2 PR
This commit is contained in:
Katherine
2026-07-08 09:31:54 +08:00
committed by GitHub
parent e9b321d579
commit 9148e5cb5f
8 changed files with 75 additions and 27 deletions
@@ -115,6 +115,7 @@ export abstract class CollectionFieldInterface {
titleUsable?: boolean;
usePathOptions?(field: any): any;
hidden?: boolean;
creatable?: boolean;
addComponentOption(componentOption: CollectionFieldInterfaceComponentOption) {
if (!this.componentOptions) {
@@ -31,6 +31,7 @@ interface FieldInterfaceLike {
title?: ReactNode;
group?: string;
order?: number;
creatable?: boolean;
isAssociation?: boolean;
supportDataSourceType?: string[];
notSupportDataSourceType?: string[];
@@ -101,23 +101,23 @@ function filterFieldInterfacesByTemplate(
fieldInterfaces: FieldInterfaceOption[],
collection: Record<string, any>,
ctx: any,
mode: 'create' | 'edit',
databaseDialect?: string,
) {
if (mode !== 'create') {
return fieldInterfaces;
}
const plugin = ctx.app.pm.get(PluginDataSourceManagerClientV2);
const template = plugin?.getCollectionTemplate?.(collection.template || 'general');
const templateFieldInterfaces = filterFieldInterfacesByCollectionTemplate<FieldInterfaceOption>(
fieldInterfaces,
template,
collection,
{
databaseDialect,
},
);
return filterCreateFieldInterfacesByCollectionTemplate(templateFieldInterfaces, template);
return filterFieldInterfacesByCollectionTemplate<FieldInterfaceOption>(fieldInterfaces, template, collection, {
databaseDialect,
});
}
function filterCreateFieldInterfacesByTemplate(
fieldInterfaces: FieldInterfaceOption[],
collection: Record<string, any>,
ctx: any,
) {
const plugin = ctx.app.pm.get(PluginDataSourceManagerClientV2);
const template = plugin?.getCollectionTemplate?.(collection.template || 'general');
return filterCreateFieldInterfacesByCollectionTemplate(fieldInterfaces, template);
}
function normalizeListResponse(response: any) {
@@ -1712,19 +1712,22 @@ export function FieldForm(props: FieldFormProps) {
const lastInitialValuesKeyRef = useRef<string>();
const dataSource = ctx.dataSourceManager.getDataSource(props.dataSourceKey);
const databaseDialect = getAppInfoDatabaseDialect(appInfo);
const fieldInterfaces = useMemo(
const fieldInterfaces = useMemo(() => {
const allFieldInterfaces = getFieldInterfaces(ctx, dataSource?.options?.type);
if (props.mode !== 'create') {
return allFieldInterfaces;
}
return filterFieldInterfacesByTemplate(allFieldInterfaces, props.collection, ctx, databaseDialect);
}, [databaseDialect, ctx, dataSource?.options?.type, props.collection, props.mode]);
const creatableFieldInterfaces = useMemo(
() =>
filterFieldInterfacesByTemplate(
getFieldInterfaces(ctx, dataSource?.options?.type),
props.collection,
ctx,
props.mode,
databaseDialect,
),
[databaseDialect, ctx, dataSource?.options?.type, props.collection, props.mode],
props.mode === 'create'
? filterCreateFieldInterfacesByTemplate(fieldInterfaces, props.collection, ctx)
: fieldInterfaces,
[ctx, fieldInterfaces, props.collection, props.mode],
);
const [interfaceName, setInterfaceName] = useState(
props.field?.interface || props.interfaceName || fieldInterfaces[0]?.name,
props.field?.interface || props.interfaceName || creatableFieldInterfaces[0]?.name,
);
const fieldInterface = useMemo(
() => fieldInterfaces.find((item) => item.name === interfaceName),
@@ -1910,10 +1913,10 @@ export function FieldForm(props: FieldFormProps) {
setInterfaceName(props.interfaceName);
return;
}
if (!interfaceName && fieldInterfaces[0]?.name) {
setInterfaceName(fieldInterfaces[0].name);
if (!interfaceName && creatableFieldInterfaces[0]?.name) {
setInterfaceName(creatableFieldInterfaces[0].name);
}
}, [fieldInterfaces, interfaceName, props.interfaceName]);
}, [creatableFieldInterfaces, interfaceName, props.interfaceName]);
const handleInterfaceChange = useCallback(
(nextInterface: string) => {
@@ -2072,7 +2075,7 @@ export function FieldForm(props: FieldFormProps) {
{!props.interfaceName && props.mode === 'create' ? (
<Form.Item name="interface" label={t('Field interface')} rules={[{ required: true }]}>
<Select
options={fieldInterfaces.map((item) => ({
options={creatableFieldInterfaces.map((item) => ({
value: item.name,
label: compileLegacyTemplate(item.title || item.name, t),
}))}
@@ -54,6 +54,7 @@ type FieldInterfaceOption = {
name: string;
title?: React.ReactNode;
label?: React.ReactNode;
creatable?: boolean;
group?: string;
order?: number;
hidden?: boolean;
@@ -0,0 +1,36 @@
/**
* 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 { describe, expect, it } from 'vitest';
import { filterCreateFieldInterfacesByCollectionTemplate } from '../collectionTemplateFieldInterfaces';
describe('filterCreateFieldInterfacesByCollectionTemplate', () => {
it('excludes field interfaces marked as not creatable', () => {
const fieldInterfaces = [{ name: 'input' }, { name: 'attachment', creatable: false }];
expect(
filterCreateFieldInterfacesByCollectionTemplate(fieldInterfaces, undefined).map((item) => item.name),
).toEqual(['input']);
});
it('does not allow template includes to restore not creatable field interfaces', () => {
const fieldInterfaces = [{ name: 'input' }, { name: 'attachment', creatable: false }];
const template = {
fieldInterfaces: {
create: {
include: ['attachment'],
},
},
};
expect(filterCreateFieldInterfacesByCollectionTemplate(fieldInterfaces, template).map((item) => item.name)).toEqual(
[],
);
});
});
@@ -21,6 +21,7 @@ type FieldInterfacePolicy = {
};
type FieldInterfaceLike = {
creatable?: boolean;
group?: string;
name: string;
};
@@ -74,6 +75,9 @@ export function filterCreateFieldInterfacesByCollectionTemplate<T extends FieldI
const hasIncludes = Array.isArray(include) && include.length > 0;
return fieldInterfaces.filter((fieldInterface) => {
if (fieldInterface.creatable === false) {
return false;
}
if (hasIncludes) {
return isIncludedFieldInterface(include, fieldInterface.name);
}
@@ -25,6 +25,7 @@ describe('AttachmentFieldInterface', () => {
expect(fieldInterface).toBeInstanceOf(AttachmentFieldInterface);
expect(fieldInterface.group).toBe('media');
expect(fieldInterface.creatable).toBe(false);
expect(fieldInterface.default).toMatchObject({
interface: 'attachment',
type: 'belongsToMany',
@@ -17,6 +17,7 @@ export class AttachmentFieldInterface extends CollectionFieldInterface {
group = 'media';
title = tExpr('Attachment');
isAssociation = true;
creatable = false;
default = {
interface: 'attachment',
type: 'belongsToMany',