diff --git a/packages/core/client/src/schema-component/antd/variable/TextArea.tsx b/packages/core/client/src/schema-component/antd/variable/TextArea.tsx
index d5a663de16..92d95c0633 100644
--- a/packages/core/client/src/schema-component/antd/variable/TextArea.tsx
+++ b/packages/core/client/src/schema-component/antd/variable/TextArea.tsx
@@ -385,17 +385,15 @@ export function TextArea(props) {
componentCls,
hashId,
css`
- &.ant-input-group.ant-input-group-compact {
- display: flex;
- .ant-input {
- flex-grow: 1;
- min-width: 200px;
- }
- .ant-input-disabled {
- .ant-tag {
- color: #bfbfbf;
- border-color: #d9d9d9;
- }
+ display: flex;
+ .ant-input {
+ flex-grow: 1;
+ min-width: 200px;
+ }
+ .ant-input-disabled {
+ .ant-tag {
+ color: #bfbfbf;
+ border-color: #d9d9d9;
}
}
diff --git a/packages/plugins/@nocobase/plugin-workflow-request/src/client/RequestInstruction.tsx b/packages/plugins/@nocobase/plugin-workflow-request/src/client/RequestInstruction.tsx
index 784da4a239..b6d28a49d6 100644
--- a/packages/plugins/@nocobase/plugin-workflow-request/src/client/RequestInstruction.tsx
+++ b/packages/plugins/@nocobase/plugin-workflow-request/src/client/RequestInstruction.tsx
@@ -6,18 +6,109 @@
* 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 React, { useState } from 'react';
+import { onFieldValueChange } from '@formily/core';
+import { uid } from '@formily/shared';
+import { useForm, useField, useFormEffects } from '@formily/react';
import { ArrayItems } from '@formily/antd-v5';
import {
Instruction,
- WorkflowVariableInput,
WorkflowVariableJSON,
WorkflowVariableTextArea,
defaultFieldNames,
} from '@nocobase/plugin-workflow/client';
import { NAMESPACE } from '../locale';
+import { SchemaComponent } from '@nocobase/client';
+
+const BodySchema = {
+ 'application/json': {
+ type: 'void',
+ properties: {
+ data: {
+ type: 'object',
+ 'x-decorator': 'FormItem',
+ 'x-decorator-props': {},
+ 'x-component': 'WorkflowVariableJSON',
+ 'x-component-props': {
+ changeOnSelect: true,
+ autoSize: {
+ minRows: 10,
+ },
+ placeholder: `{{t("Input request data", { ns: "${NAMESPACE}" })}}`,
+ },
+ },
+ },
+ },
+ 'application/x-www-form-urlencoded': {
+ type: 'void',
+ properties: {
+ data: {
+ type: 'array',
+ 'x-decorator': 'FormItem',
+ 'x-decorator-props': {},
+ 'x-component': 'ArrayItems',
+ items: {
+ type: 'object',
+ properties: {
+ space: {
+ type: 'void',
+ 'x-component': 'Space',
+ properties: {
+ name: {
+ type: 'string',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ 'x-component-props': {
+ placeholder: `{{t("Name")}}`,
+ },
+ },
+ value: {
+ type: 'string',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'WorkflowVariableTextArea',
+ 'x-component-props': {
+ useTypedConstant: true,
+ },
+ },
+ remove: {
+ type: 'void',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'ArrayItems.Remove',
+ },
+ },
+ },
+ },
+ },
+ properties: {
+ add: {
+ type: 'void',
+ title: `{{t("Add key-value pairs", { ns: "${NAMESPACE}" })}}`,
+ 'x-component': 'ArrayItems.Addition',
+ },
+ },
+ },
+ },
+ },
+};
+
+function BodyComponent(props) {
+ const f = useField();
+ const { values, setValuesIn, clearFormGraph } = useForm();
+ const { contentType } = values;
+ const [schema, setSchema] = useState(BodySchema[contentType]);
+
+ useFormEffects(() => {
+ onFieldValueChange('contentType', (field) => {
+ clearFormGraph(`${f.address}.*`);
+ setSchema({ ...BodySchema[field.value], name: uid() });
+ setValuesIn('data', null);
+ });
+ });
+
+ return ;
+}
export default class extends Instruction {
title = `{{t("HTTP request", { ns: "${NAMESPACE}" })}}`;
@@ -56,12 +147,26 @@ export default class extends Instruction {
placeholder: 'https://www.nocobase.com',
},
},
+ contentType: {
+ type: 'string',
+ title: `{{t("Content-Type", { ns: "${NAMESPACE}" })}}`,
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Select',
+ 'x-component-props': {
+ allowClear: false,
+ },
+ enum: [
+ { label: 'application/json', value: 'application/json' },
+ { label: 'application/x-www-form-urlencoded', value: 'application/x-www-form-urlencoded' },
+ ],
+ default: 'application/json',
+ },
headers: {
type: 'array',
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
title: `{{t("Headers", { ns: "${NAMESPACE}" })}}`,
- description: `{{t('"Content-Type" only support "application/json", and no need to specify', { ns: "${NAMESPACE}" })}}`,
+ description: `{{t('"Content-Type" will be ignored from headers.', { ns: "${NAMESPACE}" })}}`,
items: {
type: 'object',
properties: {
@@ -80,7 +185,7 @@ export default class extends Instruction {
value: {
type: 'string',
'x-decorator': 'FormItem',
- 'x-component': 'WorkflowVariableInput',
+ 'x-component': 'WorkflowVariableTextArea',
'x-component-props': {
useTypedConstant: true,
},
@@ -125,7 +230,7 @@ export default class extends Instruction {
value: {
type: 'string',
'x-decorator': 'FormItem',
- 'x-component': 'WorkflowVariableInput',
+ 'x-component': 'WorkflowVariableTextArea',
'x-component-props': {
useTypedConstant: true,
},
@@ -148,19 +253,19 @@ export default class extends Instruction {
},
},
data: {
- type: 'string',
+ type: 'void',
title: `{{t("Body", { ns: "${NAMESPACE}" })}}`,
'x-decorator': 'FormItem',
'x-decorator-props': {},
- 'x-component': 'WorkflowVariableJSON',
- 'x-component-props': {
- changeOnSelect: true,
- autoSize: {
- minRows: 10,
- },
- placeholder: `{{t("Input request data", { ns: "${NAMESPACE}" })}}`,
- },
- description: `{{t("Only support standard JSON data", { ns: "${NAMESPACE}" })}}`,
+ 'x-component': 'BodyComponent',
+ // 'x-component-props': {
+ // changeOnSelect: true,
+ // autoSize: {
+ // minRows: 10,
+ // },
+ // placeholder: `{{t("Input request data", { ns: "${NAMESPACE}" })}}`,
+ // },
+ // description: `{{t("Only support standard JSON data", { ns: "${NAMESPACE}" })}}`,
},
timeout: {
type: 'number',
@@ -184,7 +289,7 @@ export default class extends Instruction {
};
components = {
ArrayItems,
- WorkflowVariableInput,
+ BodyComponent,
WorkflowVariableTextArea,
WorkflowVariableJSON,
};
diff --git a/packages/plugins/@nocobase/plugin-workflow-request/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-workflow-request/src/locale/zh-CN.json
index 480125f88b..3568dae965 100644
--- a/packages/plugins/@nocobase/plugin-workflow-request/src/locale/zh-CN.json
+++ b/packages/plugins/@nocobase/plugin-workflow-request/src/locale/zh-CN.json
@@ -9,12 +9,13 @@
"Add parameter": "添加参数",
"Body": "请求体",
"Use variable": "使用变量",
+ "Add key-value pairs": "添加键值对",
"Format": "格式化",
"Insert": "插入",
"Timeout config": "超时设置",
"ms": "毫秒",
"Input request data": "输入请求数据",
"Only support standard JSON data": "仅支持标准 JSON 数据",
- "\"Content-Type\" only support \"application/json\", and no need to specify": "\"Content-Type\" 请求头仅支持 \"application/json\",无需填写",
+ "\"Content-Type\" will be ignored from headers.": "请求头中配置的 \"Content-Type\" 将被忽略。",
"Ignore failed request and continue workflow": "忽略失败的请求并继续工作流"
}
diff --git a/packages/plugins/@nocobase/plugin-workflow-request/src/server/RequestInstruction.ts b/packages/plugins/@nocobase/plugin-workflow-request/src/server/RequestInstruction.ts
index 07a627da32..64d1af3639 100644
--- a/packages/plugins/@nocobase/plugin-workflow-request/src/server/RequestInstruction.ts
+++ b/packages/plugins/@nocobase/plugin-workflow-request/src/server/RequestInstruction.ts
@@ -18,14 +18,29 @@ export interface Header {
export type RequestConfig = Pick & {
headers: Array;
+ contentType: string;
ignoreFail: boolean;
};
+const ContentTypeTransformers = {
+ 'application/json'(data) {
+ return data;
+ },
+ 'application/x-www-form-urlencoded'(data: { name: string; value: string }[]) {
+ return new URLSearchParams(
+ data.filter(({ name, value }) => name && typeof value !== 'undefined').map(({ name, value }) => [name, value]),
+ ).toString();
+ },
+};
+
async function request(config) {
// default headers
- const { url, method = 'POST', data, timeout = 5000 } = config;
+ const { url, method = 'POST', contentType = 'application/json', data, timeout = 5000 } = config;
const headers = (config.headers ?? []).reduce((result, header) => {
if (header.name.toLowerCase() === 'content-type') {
+ // header.value = ['application/json', 'application/x-www-form-urlencoded'].includes(header.value)
+ // ? header.value
+ // : 'application/json';
return result;
}
return Object.assign(result, { [header.name]: header.value });
@@ -36,15 +51,19 @@ async function request(config) {
);
// TODO(feat): only support JSON type for now, should support others in future
- headers['Content-Type'] = 'application/json';
+ headers['Content-Type'] = contentType;
return axios.request({
url,
method,
headers,
params,
- data,
timeout,
+ ...(method.toLowerCase() !== 'get' && data != null
+ ? {
+ data: ContentTypeTransformers[contentType](data),
+ }
+ : {}),
});
}
diff --git a/packages/plugins/@nocobase/plugin-workflow-request/src/server/__tests__/instruction.test.ts b/packages/plugins/@nocobase/plugin-workflow-request/src/server/__tests__/instruction.test.ts
index 52afd5f2f8..239c2cbd11 100644
--- a/packages/plugins/@nocobase/plugin-workflow-request/src/server/__tests__/instruction.test.ts
+++ b/packages/plugins/@nocobase/plugin-workflow-request/src/server/__tests__/instruction.test.ts
@@ -58,7 +58,7 @@ class MockAPI {
await sleep(100);
ctx.body = {
meta: { title: ctx.query.title },
- data: { title: ctx.request.body['title'] },
+ data: ctx.request.body,
};
}
await next();
@@ -316,6 +316,54 @@ describe('workflow > instructions > request', () => {
});
});
+ describe('contentType', () => {
+ it('no contentType as "application/json"', async () => {
+ const n1 = await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_DATA,
+ method: 'POST',
+ data: { a: '{{$context.data.title}}' },
+ },
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ expect(execution.status).toEqual(EXECUTION_STATUS.RESOLVED);
+ const [job] = await execution.getJobs();
+ expect(job.status).toEqual(JOB_STATUS.RESOLVED);
+ expect(job.result.data).toEqual({ a: 't1' });
+ });
+
+ it('contentType as "application/x-www-form-urlencoded"', async () => {
+ const n1 = await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_DATA,
+ method: 'POST',
+ data: [
+ { name: 'a', value: '{{$context.data.title}}' },
+ { name: 'a', value: '&=1' },
+ ],
+ contentType: 'application/x-www-form-urlencoded',
+ },
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ expect(execution.status).toEqual(EXECUTION_STATUS.RESOLVED);
+ const [job] = await execution.getJobs();
+ expect(job.status).toEqual(JOB_STATUS.RESOLVED);
+ expect(job.result.data).toEqual({ a: ['t1', '&=1'] });
+ });
+ });
+
describe('request db resource', () => {
it('request db resource', async () => {
const user = await db.getRepository('users').create({});