@nocobase/plugin-workflow-request 0.18.0-alpha.9 → 0.19.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  module.exports = {
2
2
  "@formily/antd-v5": "1.1.9",
3
- "@nocobase/plugin-workflow": "0.18.0-alpha.9",
4
- "@nocobase/client": "0.18.0-alpha.9",
3
+ "@nocobase/plugin-workflow": "0.19.0-alpha.10",
4
+ "@nocobase/client": "0.19.0-alpha.10",
5
5
  "react-i18next": "11.18.6",
6
- "@nocobase/server": "0.18.0-alpha.9",
6
+ "@nocobase/server": "0.19.0-alpha.10",
7
7
  "axios": "0.26.1"
8
8
  };
@@ -0,0 +1,22 @@
1
+ {
2
+ "HTTP request": "HTTP 요청",
3
+ "Send HTTP request to a URL. You can use the variables in the upstream nodes as request headers, parameters and request body.":
4
+ "URL에 HTTP 요청을 보냅니다. 상류 노드의 변수를 요청 헤더, 매개변수 및 요청 본문으로 사용할 수 있습니다.",
5
+ "HTTP method": "HTTP 메서드",
6
+ "URL": "주소",
7
+ "Headers": "헤더",
8
+ "Add request header": "요청 헤더 추가",
9
+ "Parameters": "매개변수",
10
+ "Add parameter": "매개변수 추가",
11
+ "Body": "본문",
12
+ "Use variable": "변수 사용",
13
+ "Format": "형식",
14
+ "Insert": "삽입",
15
+ "Timeout config": "시간 초과 설정",
16
+ "ms": "밀리초",
17
+ "Input request data": "요청 데이터 입력",
18
+ "Only support standard JSON data": "표준 JSON 데이터만 지원합니다",
19
+ "\"Content-Type\" only support \"application/json\", and no need to specify":
20
+ "\"Content-Type\" 헤더는 \"application/json\"만 지원하며 지정할 필요가 없습니다",
21
+ "Ignore failed request and continue workflow": "실패한 요청을 무시하고 워크플로를 계속합니다"
22
+ }
@@ -56,12 +56,29 @@ async function request(config) {
56
56
  }
57
57
  class RequestInstruction_default extends import_plugin_workflow.Instruction {
58
58
  async run(node, prevJob, processor) {
59
+ const config = processor.getParsedValue(node.config, node.id);
60
+ const { workflow } = processor.execution;
61
+ const sync = this.workflow.isWorkflowSync(workflow);
62
+ if (sync) {
63
+ try {
64
+ const response = await request(config);
65
+ return {
66
+ status: import_plugin_workflow.JOB_STATUS.RESOLVED,
67
+ result: response.data
68
+ };
69
+ } catch (error) {
70
+ return {
71
+ status: import_plugin_workflow.JOB_STATUS.FAILED,
72
+ result: error.isAxiosError ? error.toJSON() : error.message
73
+ };
74
+ }
75
+ }
59
76
  const job = await processor.saveJob({
60
77
  status: import_plugin_workflow.JOB_STATUS.PENDING,
61
78
  nodeId: node.id,
79
+ nodeKey: node.key,
62
80
  upstreamId: (prevJob == null ? void 0 : prevJob.id) ?? null
63
81
  });
64
- const config = processor.getParsedValue(node.config, node.id);
65
82
  request(config).then((response) => {
66
83
  job.set({
67
84
  status: import_plugin_workflow.JOB_STATUS.RESOLVED,
package/package.json CHANGED
@@ -4,9 +4,11 @@
4
4
  "displayName.zh-CN": "工作流:HTTP 请求节点",
5
5
  "description": "Send HTTP requests to any HTTP service for data interaction in workflow.",
6
6
  "description.zh-CN": "可用于在工作流中向任意 HTTP 服务发送请求,进行数据交互。",
7
- "version": "0.18.0-alpha.9",
7
+ "version": "0.19.0-alpha.10",
8
8
  "license": "AGPL-3.0",
9
9
  "main": "./dist/server/index.js",
10
+ "homepage": "https://docs.nocobase.com/plugins/workflow-request",
11
+ "homepage.zh-CN": "https://docs-cn.nocobase.com/plugins/workflow-request",
10
12
  "devDependencies": {
11
13
  "antd": "5.x",
12
14
  "react": "18.x",
@@ -19,5 +21,8 @@
19
21
  "@nocobase/server": "0.x",
20
22
  "@nocobase/test": "0.x"
21
23
  },
22
- "gitHead": "34ca0df4eede2e83fc86297b0fe19eba970e2b1b"
24
+ "gitHead": "d09d81eba67339da36bcec27939a85b35d180770",
25
+ "keywords": [
26
+ "Workflow"
27
+ ]
23
28
  }
@@ -0,0 +1,22 @@
1
+ {
2
+ "HTTP request": "HTTP 요청",
3
+ "Send HTTP request to a URL. You can use the variables in the upstream nodes as request headers, parameters and request body.":
4
+ "URL에 HTTP 요청을 보냅니다. 상류 노드의 변수를 요청 헤더, 매개변수 및 요청 본문으로 사용할 수 있습니다.",
5
+ "HTTP method": "HTTP 메서드",
6
+ "URL": "주소",
7
+ "Headers": "헤더",
8
+ "Add request header": "요청 헤더 추가",
9
+ "Parameters": "매개변수",
10
+ "Add parameter": "매개변수 추가",
11
+ "Body": "본문",
12
+ "Use variable": "변수 사용",
13
+ "Format": "형식",
14
+ "Insert": "삽입",
15
+ "Timeout config": "시간 초과 설정",
16
+ "ms": "밀리초",
17
+ "Input request data": "요청 데이터 입력",
18
+ "Only support standard JSON data": "표준 JSON 데이터만 지원합니다",
19
+ "\"Content-Type\" only support \"application/json\", and no need to specify":
20
+ "\"Content-Type\" 헤더는 \"application/json\"만 지원하며 지정할 필요가 없습니다",
21
+ "Ignore failed request and continue workflow": "실패한 요청을 무시하고 워크플로를 계속합니다"
22
+ }
@@ -41,14 +41,33 @@ async function request(config) {
41
41
 
42
42
  export default class extends Instruction {
43
43
  async run(node: FlowNodeModel, prevJob, processor: Processor) {
44
+ const config = processor.getParsedValue(node.config, node.id) as RequestConfig;
45
+
46
+ const { workflow } = processor.execution;
47
+ const sync = this.workflow.isWorkflowSync(workflow);
48
+
49
+ if (sync) {
50
+ try {
51
+ const response = await request(config);
52
+ return {
53
+ status: JOB_STATUS.RESOLVED,
54
+ result: response.data,
55
+ };
56
+ } catch (error) {
57
+ return {
58
+ status: JOB_STATUS.FAILED,
59
+ result: error.isAxiosError ? error.toJSON() : error.message,
60
+ };
61
+ }
62
+ }
63
+
44
64
  const job = await processor.saveJob({
45
65
  status: JOB_STATUS.PENDING,
46
66
  nodeId: node.id,
67
+ nodeKey: node.key,
47
68
  upstreamId: prevJob?.id ?? null,
48
69
  });
49
70
 
50
- const config = processor.getParsedValue(node.config, node.id) as RequestConfig;
51
-
52
71
  // eslint-disable-next-line promise/catch-or-return
53
72
  request(config)
54
73
  .then((response) => {
@@ -1,41 +1,42 @@
1
+ import { Server } from 'http';
1
2
  import jwt from 'jsonwebtoken';
3
+ import Koa from 'koa';
4
+ import bodyParser from 'koa-bodyparser';
2
5
 
3
- import { Gateway } from '@nocobase/server';
4
6
  import Database from '@nocobase/database';
5
7
  import { MockServer } from '@nocobase/test';
6
8
 
7
- import { EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow';
9
+ import PluginWorkflow, { Processor, EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow';
8
10
  import { getApp, sleep } from '@nocobase/plugin-workflow-test';
9
11
 
10
- import Plugin from '..';
11
12
  import { RequestConfig } from '../RequestInstruction';
12
13
 
13
14
  const HOST = 'localhost';
14
- const PORT = 12345;
15
15
 
16
- const URL_DATA = `http://${HOST}:${PORT}/api/data`;
17
- const URL_400 = `http://${HOST}:${PORT}/api/400`;
18
- const URL_TIMEOUT = `http://${HOST}:${PORT}/api/timeout`;
19
-
20
- describe('workflow > instructions > request', () => {
21
- let app: MockServer;
22
- let db: Database;
23
- let PostRepo;
24
- let PostCollection;
25
- let ReplyRepo;
26
- let WorkflowModel;
27
- let workflow;
28
-
29
- beforeEach(async () => {
30
- app = await getApp({
31
- plugins: ['users', 'auth', Plugin],
32
- resourcer: {
33
- prefix: '/api',
34
- },
35
- autoStart: false,
36
- });
37
-
38
- app.use(async (ctx, next) => {
16
+ function getRandomPort() {
17
+ const minPort = 1024;
18
+ const maxPort = 49151;
19
+ return Math.floor(Math.random() * (maxPort - minPort + 1)) + minPort;
20
+ }
21
+
22
+ class MockAPI {
23
+ app: Koa;
24
+ server: Server;
25
+ port: number;
26
+ get URL_DATA() {
27
+ return `http://${HOST}:${this.port}/api/data`;
28
+ }
29
+ get URL_400() {
30
+ return `http://${HOST}:${this.port}/api/400`;
31
+ }
32
+ get URL_TIMEOUT() {
33
+ return `http://${HOST}:${this.port}/api/timeout`;
34
+ }
35
+ constructor() {
36
+ this.app = new Koa();
37
+ this.app.use(bodyParser());
38
+
39
+ this.app.use(async (ctx, next) => {
39
40
  if (ctx.path === '/api/400') {
40
41
  return ctx.throw(400);
41
42
  }
@@ -46,7 +47,6 @@ describe('workflow > instructions > request', () => {
46
47
  }
47
48
  if (ctx.path === '/api/data') {
48
49
  await sleep(100);
49
- ctx.withoutDataWrapping = true;
50
50
  ctx.body = {
51
51
  meta: { title: ctx.query.title },
52
52
  data: { title: ctx.request.body['title'] },
@@ -54,13 +54,45 @@ describe('workflow > instructions > request', () => {
54
54
  }
55
55
  await next();
56
56
  });
57
+ }
57
58
 
58
- Gateway.getInstance().start({
59
- port: PORT,
60
- host: HOST,
59
+ async start() {
60
+ return new Promise((resolve) => {
61
+ this.server = this.app.listen(0, () => {
62
+ this.port = this.server.address()['port'];
63
+ resolve(true);
64
+ });
65
+ });
66
+ }
67
+
68
+ async close() {
69
+ return new Promise((resolve) => {
70
+ this.server.close(() => {
71
+ resolve(true);
72
+ });
61
73
  });
74
+ }
75
+ }
76
+
77
+ describe('workflow > instructions > request', () => {
78
+ let app: MockServer;
79
+ let db: Database;
80
+ let PostRepo;
81
+ let PostCollection;
82
+ let ReplyRepo;
83
+ let WorkflowModel;
84
+ let workflow;
85
+ let api: MockAPI;
62
86
 
63
- await app.start();
87
+ beforeEach(async () => {
88
+ api = new MockAPI();
89
+ api.start();
90
+ app = await getApp({
91
+ resourcer: {
92
+ prefix: '/api',
93
+ },
94
+ plugins: ['users', 'auth', 'workflow-request'],
95
+ });
64
96
 
65
97
  db = app.db;
66
98
  WorkflowModel = db.getCollection('workflows').model;
@@ -78,14 +110,17 @@ describe('workflow > instructions > request', () => {
78
110
  });
79
111
  });
80
112
 
81
- afterEach(() => app.destroy());
113
+ afterEach(async () => {
114
+ await api.close();
115
+ await app.destroy();
116
+ });
82
117
 
83
118
  describe('request static app routes', () => {
84
119
  it('get data', async () => {
85
120
  await workflow.createNode({
86
121
  type: 'request',
87
122
  config: {
88
- url: URL_DATA,
123
+ url: api.URL_DATA,
89
124
  method: 'GET',
90
125
  } as RequestConfig,
91
126
  });
@@ -105,7 +140,7 @@ describe('workflow > instructions > request', () => {
105
140
  await workflow.createNode({
106
141
  type: 'request',
107
142
  config: {
108
- url: URL_TIMEOUT,
143
+ url: api.URL_TIMEOUT,
109
144
  method: 'GET',
110
145
  timeout: 250,
111
146
  } as RequestConfig,
@@ -134,7 +169,7 @@ describe('workflow > instructions > request', () => {
134
169
  await workflow.createNode({
135
170
  type: 'request',
136
171
  config: {
137
- url: URL_TIMEOUT,
172
+ url: api.URL_TIMEOUT,
138
173
  method: 'GET',
139
174
  timeout: 250,
140
175
  ignoreFail: true,
@@ -160,7 +195,7 @@ describe('workflow > instructions > request', () => {
160
195
  await workflow.createNode({
161
196
  type: 'request',
162
197
  config: {
163
- url: URL_400,
198
+ url: api.URL_400,
164
199
  method: 'GET',
165
200
  ignoreFail: false,
166
201
  } as RequestConfig,
@@ -180,7 +215,7 @@ describe('workflow > instructions > request', () => {
180
215
  await workflow.createNode({
181
216
  type: 'request',
182
217
  config: {
183
- url: URL_400,
218
+ url: api.URL_400,
184
219
  method: 'GET',
185
220
  timeout: 1000,
186
221
  ignoreFail: true,
@@ -201,7 +236,7 @@ describe('workflow > instructions > request', () => {
201
236
  const n1 = await workflow.createNode({
202
237
  type: 'request',
203
238
  config: {
204
- url: URL_DATA,
239
+ url: api.URL_DATA,
205
240
  method: 'POST',
206
241
  data: { title: '{{$context.data.title}}' },
207
242
  } as RequestConfig,
@@ -222,7 +257,7 @@ describe('workflow > instructions > request', () => {
222
257
  const n1 = await workflow.createNode({
223
258
  type: 'request',
224
259
  config: {
225
- url: URL_DATA,
260
+ url: api.URL_DATA,
226
261
  method: 'POST',
227
262
  data: { title: '{{$context.data.title}}' },
228
263
  } as RequestConfig,
@@ -254,7 +289,7 @@ describe('workflow > instructions > request', () => {
254
289
  upstreamId: n1.id,
255
290
  branchIndex: 0,
256
291
  config: {
257
- url: URL_DATA,
292
+ url: api.URL_DATA,
258
293
  method: 'GET',
259
294
  },
260
295
  });
@@ -286,10 +321,14 @@ describe('workflow > instructions > request', () => {
286
321
  },
287
322
  );
288
323
 
324
+ const server = app.listen(12346, () => {});
325
+
326
+ await sleep(1000);
327
+
289
328
  const n1 = await workflow.createNode({
290
329
  type: 'request',
291
330
  config: {
292
- url: `http://localhost:${PORT}/api/categories`,
331
+ url: `http://localhost:12346/api/categories`,
293
332
  method: 'POST',
294
333
  headers: [{ name: 'Authorization', value: `Bearer ${token}` }],
295
334
  } as RequestConfig,
@@ -306,6 +345,35 @@ describe('workflow > instructions > request', () => {
306
345
  const [job] = await execution.getJobs();
307
346
  expect(job.status).toBe(JOB_STATUS.RESOLVED);
308
347
  expect(job.result.data).toMatchObject({});
348
+
349
+ server.close();
350
+ });
351
+ });
352
+
353
+ describe('sync request', () => {
354
+ it('sync trigger', async () => {
355
+ const syncFlow = await WorkflowModel.create({
356
+ type: 'syncTrigger',
357
+ enabled: true,
358
+ });
359
+ await syncFlow.createNode({
360
+ type: 'request',
361
+ config: {
362
+ url: api.URL_DATA,
363
+ method: 'GET',
364
+ } as RequestConfig,
365
+ });
366
+
367
+ const workflowPlugin = app.pm.get(PluginWorkflow) as PluginWorkflow;
368
+ const processor = (await workflowPlugin.trigger(syncFlow, { data: { title: 't1' } })) as Processor;
369
+
370
+ const [execution] = await syncFlow.getExecutions();
371
+ expect(processor.execution.id).toEqual(execution.id);
372
+ expect(processor.execution.status).toEqual(execution.status);
373
+ expect(execution.status).toEqual(EXECUTION_STATUS.RESOLVED);
374
+ const [job] = await execution.getJobs();
375
+ expect(job.status).toEqual(JOB_STATUS.RESOLVED);
376
+ expect(job.result).toEqual({ meta: {}, data: {} });
309
377
  });
310
378
  });
311
379
  });