mirror of
https://github.com/nocobase/nocobase.git
synced 2026-09-24 16:02:20 +08:00
Merge branch 'next' into develop
This commit is contained in:
@@ -9,9 +9,11 @@
|
||||
|
||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import classNames from 'classnames';
|
||||
import { lazy } from '../../../lazy-helper';
|
||||
import { isVariable } from '../../../variables/utils/isVariable';
|
||||
import { Input, ReadPretty as InputReadPretty } from '../input';
|
||||
import { ReadPretty as InputReadPretty } from '../input';
|
||||
import { useStyles } from './style';
|
||||
|
||||
const ReactQuill = lazy(() => import('react-quill'));
|
||||
@@ -37,9 +39,23 @@ export const RichText = connect(
|
||||
];
|
||||
const { value, defaultValue, onChange, disabled, modules: propsModules, formats: propsFormats } = props;
|
||||
const resultValue = isVariable(value || defaultValue) ? undefined : value || '';
|
||||
const quillDisabled = css`
|
||||
.ql-container.ql-disabled {
|
||||
background-color: #f5f5f5; /* 灰色背景 */
|
||||
color: #999;
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
border: 1px solid #d9d9d9; /* 模拟 input 的禁用边框 */
|
||||
border-radius: 6px;
|
||||
}
|
||||
`;
|
||||
|
||||
return wrapSSR(
|
||||
<ReactQuill
|
||||
className={`${componentCls} ${hashId}`}
|
||||
className={classNames(componentCls, hashId, quillDisabled, {
|
||||
'is-disabled': disabled,
|
||||
})}
|
||||
modules={propsModules || modules}
|
||||
formats={propsFormats || formats}
|
||||
value={resultValue}
|
||||
|
||||
@@ -13,6 +13,7 @@ import path from 'path';
|
||||
import fs from 'fs/promises';
|
||||
|
||||
import Application from './application';
|
||||
import { SystemLogger } from '@nocobase/logger';
|
||||
import { sleep } from '@nocobase/utils';
|
||||
|
||||
export const QUEUE_DEFAULT_INTERVAL = 250;
|
||||
@@ -86,9 +87,10 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
||||
if (!this.connected) {
|
||||
return;
|
||||
}
|
||||
const { logger } = this.options;
|
||||
const event = this.events.get(channel);
|
||||
if (!event) {
|
||||
console.warn(`memory queue (${channel}) not found, skipping...`);
|
||||
logger.warn(`memory queue (${channel}) not found, skipping...`);
|
||||
return;
|
||||
}
|
||||
if (!event.idle()) {
|
||||
@@ -98,12 +100,12 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
||||
const reading = this.reading.get(channel) || [];
|
||||
const count = (event.concurrency || QUEUE_DEFAULT_CONCURRENCY) - reading.length;
|
||||
if (count <= 0) {
|
||||
console.debug(
|
||||
`memory queue (${channel}) is already reading as max concurrency (${reading.length}), waiting last reading to end...`,
|
||||
);
|
||||
// logger.debug(
|
||||
// `memory queue (${channel}) is already reading as max concurrency (${reading.length}), waiting last reading to end...`,
|
||||
// );
|
||||
return;
|
||||
}
|
||||
console.debug(`reading more from queue (${channel}), count: ${count}`);
|
||||
logger.debug(`reading more from queue (${channel}), count: ${count}`);
|
||||
this.read(channel, count).forEach((promise) => {
|
||||
reading.push(promise);
|
||||
// eslint-disable-next-line promise/catch-or-return
|
||||
@@ -117,7 +119,7 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
||||
this.reading.set(channel, reading);
|
||||
};
|
||||
|
||||
constructor(private options: { appName: string }) {
|
||||
constructor(private options: { appName: string; logger: SystemLogger }) {
|
||||
this.emitter.setMaxListeners(0);
|
||||
}
|
||||
|
||||
@@ -132,20 +134,21 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
||||
async loadFromStorage() {
|
||||
let queues = {};
|
||||
let exists = false;
|
||||
const { logger } = this.options;
|
||||
try {
|
||||
await fs.stat(this.storagePath);
|
||||
exists = true;
|
||||
} catch (ex) {
|
||||
console.info(`memory queue storage file not found, skip`);
|
||||
logger.info(`memory queue storage file not found, skip`);
|
||||
}
|
||||
if (exists) {
|
||||
try {
|
||||
const queueJson = await fs.readFile(this.storagePath);
|
||||
queues = JSON.parse(queueJson.toString());
|
||||
console.debug('memory queue loaded from storage', queues);
|
||||
logger.debug('memory queue loaded from storage', queues);
|
||||
await fs.unlink(this.storagePath);
|
||||
} catch (ex) {
|
||||
console.error('failed to load queue from storage', ex);
|
||||
logger.error('failed to load queue from storage', ex);
|
||||
}
|
||||
}
|
||||
this.queues = new Map(Object.entries(queues));
|
||||
@@ -159,12 +162,13 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const { logger } = this.options;
|
||||
if (Object.keys(queues).length) {
|
||||
await fs.mkdir(path.dirname(this.storagePath), { recursive: true });
|
||||
await fs.writeFile(this.storagePath, JSON.stringify(queues));
|
||||
console.debug('memory queue saved to storage', queues);
|
||||
logger.debug('memory queue saved to storage', queues);
|
||||
} else {
|
||||
console.debug('memory queue empty, no need to save to storage');
|
||||
logger.debug('memory queue empty, no need to save to storage');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,14 +199,15 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
||||
if (!this.connected) {
|
||||
return;
|
||||
}
|
||||
const { logger } = this.options;
|
||||
this.connected = false;
|
||||
if (this.processing) {
|
||||
console.info('memory queue waiting for processing job...');
|
||||
logger.info('memory queue waiting for processing job...');
|
||||
await this.processing;
|
||||
console.info('memory queue job cleaned');
|
||||
logger.info('memory queue job cleaned');
|
||||
}
|
||||
|
||||
console.log('memory queue gracefully shutting down...');
|
||||
logger.info('memory queue gracefully shutting down...');
|
||||
await this.saveToStorage();
|
||||
}
|
||||
|
||||
@@ -242,7 +247,8 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
||||
const queue = this.queues.get(channel);
|
||||
const message = { id: randomUUID(), content, options };
|
||||
queue.push(message);
|
||||
console.debug(`memory queue (${channel}) published message`, content);
|
||||
const { logger } = this.options;
|
||||
logger.debug(`memory queue (${channel}) published message`, content);
|
||||
|
||||
setImmediate(() => {
|
||||
this.emitter.emit(channel, channel);
|
||||
@@ -272,8 +278,9 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
||||
if (!queue?.length) {
|
||||
return [];
|
||||
}
|
||||
const { logger } = this.options;
|
||||
const messages = queue.slice(0, n);
|
||||
console.debug(`memory queue (${channel}) read ${messages.length} messages`, messages);
|
||||
logger.debug(`memory queue (${channel}) read ${messages.length} messages`, messages);
|
||||
queue.splice(0, messages.length);
|
||||
const batch = messages.map(({ id, ...message }) => this.process(channel, { id, message }));
|
||||
return batch;
|
||||
@@ -282,7 +289,8 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
||||
async process(channel: string, { id, message }) {
|
||||
const event = this.events.get(channel);
|
||||
const { content, options: { timeout = QUEUE_DEFAULT_ACK_TIMEOUT, maxRetries = 0, retried = 0 } = {} } = message;
|
||||
console.debug(`memory queue (${channel}) processing message (${id})...`, content);
|
||||
const { logger } = this.options;
|
||||
logger.debug(`memory queue (${channel}) processing message (${id})...`, content);
|
||||
return (async () =>
|
||||
event.process(content, {
|
||||
id,
|
||||
@@ -290,12 +298,12 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
||||
signal: AbortSignal.timeout(timeout),
|
||||
}))()
|
||||
.then(() => {
|
||||
console.debug(`memory queue (${channel}) consumed message (${id})`);
|
||||
logger.debug(`memory queue (${channel}) consumed message (${id})`);
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (maxRetries > 0 && retried < maxRetries) {
|
||||
const currentRetry = retried + 1;
|
||||
console.warn(
|
||||
logger.warn(
|
||||
`memory queue (${channel}) consum message (${id}) failed, retrying (${currentRetry} / ${maxRetries})...`,
|
||||
ex,
|
||||
);
|
||||
@@ -303,7 +311,7 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
||||
this.publish(channel, content, { timeout, maxRetries, retried: currentRetry, timestamp: Date.now() });
|
||||
}, 500);
|
||||
} else {
|
||||
console.error(ex);
|
||||
logger.error(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -322,7 +330,7 @@ export class EventQueue {
|
||||
protected options: EventQueueOptions = {},
|
||||
) {
|
||||
if (app.serving()) {
|
||||
this.setAdapter(new MemoryEventQueueAdapter({ appName: this.app.name }));
|
||||
this.setAdapter(new MemoryEventQueueAdapter({ appName: this.app.name, logger: this.app.logger }));
|
||||
|
||||
app.on('afterStart', async () => {
|
||||
await this.connect();
|
||||
|
||||
+3
-1
@@ -38,6 +38,7 @@ const MobileDateTimePicker = connect(
|
||||
timeFormat = 'HH:mm',
|
||||
showTime = false,
|
||||
picker,
|
||||
disabled,
|
||||
...rest
|
||||
} = props;
|
||||
const [visible, setVisible] = useState(false);
|
||||
@@ -73,11 +74,12 @@ const MobileDateTimePicker = connect(
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<div onClick={() => setVisible(true)}>
|
||||
<div onClick={() => !disabled && setVisible(true)}>
|
||||
<NBDatePicker
|
||||
onClick={() => setVisible(true)}
|
||||
value={value}
|
||||
picker={picker}
|
||||
disabled={disabled}
|
||||
{...rest}
|
||||
popupStyle={{ display: 'none' }}
|
||||
style={{ pointerEvents: 'none', width: '100%' }}
|
||||
|
||||
+3
-2
@@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
const MobilePicker = connect(
|
||||
(props) => {
|
||||
const { value, onChange, options = [], mode } = props;
|
||||
const { value, onChange, options = [], mode, disabled } = props;
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [selected, setSelected] = useState(value || []);
|
||||
@@ -38,12 +38,13 @@ const MobilePicker = connect(
|
||||
|
||||
return (
|
||||
<>
|
||||
<div onClick={() => setVisible(true)}>
|
||||
<div onClick={() => !disabled && setVisible(true)}>
|
||||
<Select
|
||||
placeholder={t('Select')}
|
||||
value={value}
|
||||
dropdownStyle={{ display: 'none' }}
|
||||
multiple={mode === 'multiple'}
|
||||
disabled={disabled}
|
||||
onClear={() => {
|
||||
setVisible(false);
|
||||
onChange(null);
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('workflow > cluster', () => {
|
||||
sharedQueues = new Map();
|
||||
for (const node of cluster.nodes) {
|
||||
await node.eventQueue.close();
|
||||
const adapter = new MockMemoryEventQueueAdapter({ appName: node.name });
|
||||
const adapter = new MockMemoryEventQueueAdapter({ appName: node.name, logger: node.logger });
|
||||
adapter.setQueues(sharedQueues);
|
||||
node.eventQueue.setAdapter(adapter);
|
||||
await node.eventQueue.connect();
|
||||
|
||||
Reference in New Issue
Block a user