Merge branch 'develop' of github.com:nocobase/nocobase into feat-main-datasource-mssql

This commit is contained in:
aaaaaajie 2025-04-15 13:29:57 +08:00
commit 46a77d265b
22 changed files with 153 additions and 53 deletions

View File

@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v1.6.20](https://github.com/nocobase/nocobase/compare/v1.6.19...v1.6.20) - 2025-04-14
### 🎉 New Features
- **[Departments]** Make Department, Attachment URL, and Workflow response message plugins free ([#6663](https://github.com/nocobase/nocobase/pull/6663)) by @chenos
### 🐛 Bug Fixes
- **[client]**
- The filter form should not display the "Unsaved changes" prompt ([#6657](https://github.com/nocobase/nocobase/pull/6657)) by @zhangzhonghe
- "allow multiple" option not working for relation field ([#6661](https://github.com/nocobase/nocobase/pull/6661)) by @katherinehhh
- In the filter form, when the filter button is clicked, if there are fields that have not passed validation, the filtering is still triggered ([#6659](https://github.com/nocobase/nocobase/pull/6659)) by @zhangzhonghe
- Switching to the group menu should not jump to a page that has already been hidden in menu ([#6654](https://github.com/nocobase/nocobase/pull/6654)) by @zhangzhonghe
- **[File storage: S3(Pro)]**
- Organize language by @jiannx
- Individual baseurl and public settings, improve S3 pro storage config UX by @jiannx
- **[Migration manager]** the skip auto backup option becomes invalid if environment variable popup appears during migration by @gchust
## [v1.6.19](https://github.com/nocobase/nocobase/compare/v1.6.18...v1.6.19) - 2025-04-14
### 🐛 Bug Fixes

View File

@ -5,6 +5,30 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
## [v1.6.20](https://github.com/nocobase/nocobase/compare/v1.6.19...v1.6.20) - 2025-04-14
### 🎉 新特性
- **[部门]** 商业插件部门、附件 URL、工作流响应消息改为免费提供 ([#6663](https://github.com/nocobase/nocobase/pull/6663)) by @chenos
### 🐛 修复
- **[client]**
- 筛选表单不应该显示“未保存修改”提示 ([#6657](https://github.com/nocobase/nocobase/pull/6657)) by @zhangzhonghe
- 筛选表单中关系字段的“允许多选”设置项不生效 ([#6661](https://github.com/nocobase/nocobase/pull/6661)) by @katherinehhh
- 筛选表单中,当点击筛选按钮时,如果有字段未校验通过,依然会触发筛选的问题 ([#6659](https://github.com/nocobase/nocobase/pull/6659)) by @zhangzhonghe
- 切换到分组菜单时,不应该跳转到已经在菜单中被隐藏的页面 ([#6654](https://github.com/nocobase/nocobase/pull/6654)) by @zhangzhonghe
- **[文件存储S3 (Pro)]**
- 整理语言文案 by @jiannx
- baseurl 和 public 设置不再互相关联,改进 S3 pro 存储的配置交互体验 by @jiannx
- **[迁移管理]** 迁移时若弹出环境变量弹窗,跳过自动备份选项会失效 by @gchust
## [v1.6.19](https://github.com/nocobase/nocobase/compare/v1.6.18...v1.6.19) - 2025-04-14
### 🐛 修复

View File

@ -460,8 +460,16 @@ exports.initEnv = function initEnv() {
process.env.SOCKET_PATH = generateGatewayPath();
fs.mkdirpSync(dirname(process.env.SOCKET_PATH), { force: true, recursive: true });
fs.mkdirpSync(process.env.PM2_HOME, { force: true, recursive: true });
const pkgDir = resolve(process.cwd(), 'storage/plugins', '@nocobase/plugin-multi-app-manager');
fs.existsSync(pkgDir) && fs.rmdirSync(pkgDir, { recursive: true, force: true });
const pkgs = [
'@nocobase/plugin-multi-app-manager',
'@nocobase/plugin-departments',
'@nocobase/plugin-field-attachment-url',
'@nocobase/plugin-workflow-response-message',
];
for (const pkg of pkgs) {
const pkgDir = resolve(process.cwd(), 'storage/plugins', pkg);
fs.existsSync(pkgDir) && fs.rmdirSync(pkgDir, { recursive: true, force: true });
}
};
exports.generatePlugins = function () {

View File

@ -54,7 +54,7 @@ describe('CollectionSelect', () => {
role="button"
>
<div
class="css-9mlexe ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-1rquknz"
class="css-a7w9kk ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-1rquknz"
>
<div
class="ant-formily-item-label"
@ -195,7 +195,7 @@ describe('CollectionSelect', () => {
role="button"
>
<div
class="css-9mlexe ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-1rquknz"
class="css-a7w9kk ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-1rquknz"
>
<div
class="ant-formily-item-label"

View File

@ -48,6 +48,7 @@ export type RemoteSelectProps<P = any> = SelectProps<P, any> & {
CustomDropdownRender?: (v: any) => any;
optionFilter?: (option: any) => boolean;
toOptionsItem?: (data) => any;
onSuccess?: (data) => any;
};
const InternalRemoteSelect = withDynamicSchemaProps(
@ -68,6 +69,7 @@ const InternalRemoteSelect = withDynamicSchemaProps(
dataSource: propsDataSource,
toOptionsItem = (value) => value,
popupMatchSelectWidth = false,
onSuccess,
...others
} = props;
const dataSource = useDataSourceKey();
@ -178,6 +180,7 @@ const InternalRemoteSelect = withDynamicSchemaProps(
{
manual,
debounceWait: wait,
onSuccess,
...(service.defaultParams ? { defaultParams: [service.defaultParams] } : {}),
},
);

View File

@ -9,6 +9,7 @@
"peerDependencies": {
"@nocobase/actions": "1.x",
"@nocobase/client": "1.x",
"@nocobase/plugin-user-data-sync": "1.x",
"@nocobase/server": "1.x",
"@nocobase/test": "1.x"
},

View File

@ -27,7 +27,7 @@ describe('actions', () => {
beforeAll(async () => {
app = await createMockServer({
plugins: ['users', 'departments'],
plugins: ['field-sort', 'users', 'departments'],
});
db = app.db;
repo = db.getRepository('departments');

View File

@ -7,9 +7,8 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { UserDataResourceManager } from '@nocobase/plugin-user-data-sync';
import PluginUserDataSyncServer, { UserDataResourceManager } from '@nocobase/plugin-user-data-sync';
import { MockDatabase, MockServer, createMockServer } from '@nocobase/test';
import PluginUserDataSyncServer from '@nocobase/plugin-user-data-sync';
describe('department data sync', async () => {
let app: MockServer;
@ -18,7 +17,7 @@ describe('department data sync', async () => {
beforeEach(async () => {
app = await createMockServer({
plugins: ['user-data-sync', 'users', 'departments'],
plugins: ['field-sort', 'user-data-sync', 'users', 'departments'],
});
db = app.db;
const plugin = app.pm.get('user-data-sync') as PluginUserDataSyncServer;
@ -259,18 +258,16 @@ describe('department data sync', async () => {
expect(department.customField).toBe('testField');
await resourceManager.updateOrCreate({
sourceName: 'test',
dataType: 'user',
matchKey: 'email',
dataType: 'department',
records: [
{
uid: '1',
nickname: 'test',
email: 'test@nocobase.com',
title: 'test',
customField: 'testField2',
},
],
});
const department2 = await db.getRepository('users').findOne({
const department2 = await db.getRepository('departments').findOne({
filter: {
id: department.id,
},

View File

@ -27,7 +27,7 @@ describe('destroy department check', () => {
beforeAll(async () => {
app = await createMockServer({
plugins: ['users', 'departments'],
plugins: ['field-sort', 'users', 'departments'],
});
db = app.db;
repo = db.getRepository('departments');

View File

@ -27,7 +27,7 @@ describe('set department owners', () => {
beforeAll(async () => {
app = await createMockServer({
plugins: ['users', 'departments'],
plugins: ['field-sort', 'users', 'departments'],
});
db = app.db;
repo = db.getRepository('departments');

View File

@ -29,7 +29,7 @@ describe('set departments info', () => {
beforeAll(async () => {
app = await createMockServer({
plugins: ['users', 'departments', 'acl', 'data-source-manager'],
plugins: ['field-sort', 'users', 'departments', 'acl', 'data-source-manager'],
});
db = app.db;
repo = db.getRepository('departments');

View File

@ -27,7 +27,7 @@ describe('set main department', () => {
beforeAll(async () => {
app = await createMockServer({
plugins: ['users', 'departments'],
plugins: ['field-sort', 'users', 'departments'],
});
db = app.db;
repo = db.getRepository('departments');

View File

@ -27,7 +27,7 @@ describe('update department isLeaf', () => {
beforeAll(async () => {
app = await createMockServer({
plugins: ['users', 'departments'],
plugins: ['field-sort', 'users', 'departments'],
});
db = app.db;
repo = db.getRepository('departments');

View File

@ -8,6 +8,7 @@
"description.zh-CN": "支持 URL 格式的附件。",
"peerDependencies": {
"@nocobase/client": "1.x",
"@nocobase/plugin-file-manager": "1.x",
"@nocobase/server": "1.x",
"@nocobase/test": "1.x"
},

View File

@ -113,7 +113,6 @@ const InnerAttachmentUrl = (props) => {
if (underFilter) {
return <Input {...props} />;
}
console.log(collectionField);
return (
<div style={{ width: '100%', overflow: 'auto' }}>
<AssociationField.FileSelector

View File

@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { useFieldSchema } from '@formily/react';
import { useFieldSchema, useField } from '@formily/react';
import { useCollectionField, useDesignable, useRequest } from '@nocobase/client';
import { cloneDeep, uniqBy } from 'lodash';
import { useCallback } from 'react';
@ -63,15 +63,11 @@ export const useInsertSchema = (component) => {
export const useAttachmentTargetProps = () => {
const { t } = useTranslation();
// TODO(refactor): whitelist should be changed to storage propertyurl is signed by plugin-s3-pro, this enmus is from plugin-file-manager
const buildInStorage = ['local', 'ali-oss', 's3', 'tx-cos'];
const field = useField();
return {
service: {
resource: 'collections',
resource: 'collections:listFileCollectionsWithPublicStorage',
params: {
filter: {
'options.template': 'file',
},
paginate: false,
},
},
@ -80,27 +76,9 @@ export const useAttachmentTargetProps = () => {
label: 'title',
value: 'name',
},
mapOptions: (value) => {
if (value.name === 'attachments') {
return {
...value,
title: t('Attachments'),
};
}
return value;
},
toOptionsItem: (data) => {
data.unshift({
name: 'attachments',
title: t('Attachments'),
});
return uniqBy(
data.filter((v) => v.name),
'name',
);
},
optionFilter: (option) => {
return !option.storage || buildInStorage.includes(option.storage);
onSuccess: (data) => {
field.data = field.data || {};
field.data.options = data?.data;
},
};
};

View File

@ -51,12 +51,18 @@ export class AttachmentURLFieldInterface extends CollectionFieldInterface {
...defaultProps,
target: {
required: true,
default: 'attachments',
type: 'string',
title: tStr('Which file collection should it be uploaded to'),
'x-decorator': 'FormItem',
'x-component': 'RemoteSelect',
'x-use-component-props': useAttachmentTargetProps,
'x-reactions': (field) => {
const options = field.data?.options || [];
const hasAttachments = options.some((opt) => opt?.name === 'attachments');
if (hasAttachments) {
!field.initialValue && field.setInitialValue('attachments');
}
},
},
targetKey: {
'x-hidden': true,

View File

@ -8,13 +8,50 @@
*/
import { Plugin } from '@nocobase/server';
import PluginFileManagerServer from '@nocobase/plugin-file-manager';
export class PluginFieldAttachmentUrlServer extends Plugin {
async afterAdd() {}
async beforeLoad() {}
async load() {}
async load() {
this.app.resourceManager.registerActionHandlers({
'collections:listFileCollectionsWithPublicStorage': async (ctx, next) => {
const fileCollections = await this.db.getRepository('collections').find({
filter: {
'options.template': 'file',
},
});
const filePlugin = this.pm.get('file-manager') as PluginFileManagerServer | any;
const options = [];
const fileCollection = this.db.getCollection('attachments');
if (await filePlugin.isPublicAccessStorage(fileCollection?.options?.storage)) {
options.push({
title: '{{t("Attachments")}}',
name: 'attachments',
});
}
for (const fileCollection of fileCollections) {
if (await filePlugin.isPublicAccessStorage(fileCollection?.options?.storage)) {
options.push({
name: fileCollection.name,
title: fileCollection.title,
});
}
}
ctx.body = options;
await next();
},
});
}
async install() {}

View File

@ -313,6 +313,27 @@ export class PluginFileManagerServer extends Plugin {
const storageType = this.storageTypes.get(storage.type);
return new storageType(storage).getFileURL(file, preview ? storage.options.thumbnailRule : '');
}
async isPublicAccessStorage(storageName) {
const storageRepository = this.db.getRepository('storages');
const storages = await storageRepository.findOne({
filter: { default: true },
});
let storage;
if (!storageName) {
storage = storages;
} else {
storage = await storageRepository.findOne({
filter: {
name: storageName,
},
});
}
storage = this.parseStorage(storage);
if (['local', 'ali-oss', 's3', 'tx-cos'].includes(storage.type)) {
return true;
}
return !!storage.options?.public;
}
}
export default PluginFileManagerServer;

View File

@ -10,6 +10,7 @@
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/workflow-response-message",
"peerDependencies": {
"@nocobase/client": "1.x",
"@nocobase/plugin-workflow": "1.x",
"@nocobase/server": "1.x",
"@nocobase/test": "1.x",
"@nocobase/utils": "1.x"

View File

@ -17,13 +17,13 @@
*/
import Database from '@nocobase/database';
import { MockServer } from '@nocobase/test';
import { EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow';
import { getApp } from '@nocobase/plugin-workflow-test';
import { MockServer } from '@nocobase/test';
import Plugin from '..';
describe('workflow > instructions > response-message', () => {
describe.skip('workflow > instructions > response-message', () => {
let app: MockServer;
let db: Database;
let PostRepo;

View File

@ -17,13 +17,13 @@
*/
import Database from '@nocobase/database';
import { MockServer } from '@nocobase/test';
import { EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow';
import { getApp } from '@nocobase/plugin-workflow-test';
import { MockServer } from '@nocobase/test';
import Plugin from '..';
describe('workflow > multiple workflows', () => {
describe.skip('workflow > multiple workflows', () => {
let app: MockServer;
let db: Database;
let PostRepo;