mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
Merge branch 'develop' of github.com:nocobase/nocobase into feat-main-datasource-mssql
This commit is contained in:
commit
46a77d265b
24
CHANGELOG.md
24
CHANGELOG.md
@ -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
|
||||
|
@ -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
|
||||
|
||||
### 🐛 修复
|
||||
|
@ -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');
|
||||
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 () {
|
||||
|
@ -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"
|
||||
|
@ -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] } : {}),
|
||||
},
|
||||
);
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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');
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -113,7 +113,6 @@ const InnerAttachmentUrl = (props) => {
|
||||
if (underFilter) {
|
||||
return <Input {...props} />;
|
||||
}
|
||||
console.log(collectionField);
|
||||
return (
|
||||
<div style={{ width: '100%', overflow: 'auto' }}>
|
||||
<AssociationField.FileSelector
|
||||
|
@ -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 property,url 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;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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() {}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user