@things-factory/integration-base 8.0.0-beta.8 → 8.0.0

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.
Files changed (160) hide show
  1. package/dist-server/engine/connector/http-connector.js +1 -1
  2. package/dist-server/engine/connector/http-connector.js.map +1 -1
  3. package/dist-server/engine/connector/index.d.ts +0 -1
  4. package/dist-server/engine/connector/index.js +0 -1
  5. package/dist-server/engine/connector/index.js.map +1 -1
  6. package/dist-server/engine/index.d.ts +0 -1
  7. package/dist-server/engine/index.js +0 -1
  8. package/dist-server/engine/index.js.map +1 -1
  9. package/dist-server/engine/task/headless-post.js +33 -19
  10. package/dist-server/engine/task/headless-post.js.map +1 -1
  11. package/dist-server/engine/task/headless-scrap.js +13 -20
  12. package/dist-server/engine/task/headless-scrap.js.map +1 -1
  13. package/dist-server/tsconfig.tsbuildinfo +1 -1
  14. package/package.json +11 -12
  15. package/server/controllers/index.ts +2 -0
  16. package/server/controllers/publish-data.ts +29 -0
  17. package/server/controllers/scenario-controller.ts +156 -0
  18. package/server/engine/analyzer/analyze-integration.ts +115 -0
  19. package/server/engine/connection-manager.ts +239 -0
  20. package/server/engine/connector/echo-back-connector.ts +51 -0
  21. package/server/engine/connector/echo-back-server.ts +72 -0
  22. package/server/engine/connector/graphql-connector.ts +126 -0
  23. package/server/engine/connector/http-connector.ts +65 -0
  24. package/server/engine/connector/index.ts +12 -0
  25. package/server/engine/connector/mqtt-connector.ts +78 -0
  26. package/server/engine/connector/mssql-connector.ts +152 -0
  27. package/server/engine/connector/mysql-connector.ts +94 -0
  28. package/server/engine/connector/operato-connector.ts +264 -0
  29. package/server/engine/connector/oracle-connector.ts +218 -0
  30. package/server/engine/connector/postgresql-connector.ts +152 -0
  31. package/server/engine/connector/proxy-connector.ts +53 -0
  32. package/server/engine/connector/socket-server.ts +86 -0
  33. package/server/engine/connector/sqlite-connector.ts +69 -0
  34. package/server/engine/edge-client.ts +45 -0
  35. package/server/engine/index.ts +10 -0
  36. package/server/engine/pending-queue.ts +97 -0
  37. package/server/engine/scenario-engine.ts +106 -0
  38. package/server/engine/task/book-up-scenario.ts +73 -0
  39. package/server/engine/task/csv-readline.ts +127 -0
  40. package/server/engine/task/data-accessor.ts +36 -0
  41. package/server/engine/task/data-mapper.ts +47 -0
  42. package/server/engine/task/database-query.ts +56 -0
  43. package/server/engine/task/echo-receive.ts +21 -0
  44. package/server/engine/task/echo-send.ts +32 -0
  45. package/server/engine/task/empty-check.ts +38 -0
  46. package/server/engine/task/end.ts +18 -0
  47. package/server/engine/task/floating-point.ts +71 -0
  48. package/server/engine/task/goto.ts +27 -0
  49. package/server/engine/task/graphql-mutate.ts +79 -0
  50. package/server/engine/task/graphql-query.ts +78 -0
  51. package/server/engine/task/headless-post.ts +147 -0
  52. package/server/engine/task/headless-scrap.ts +80 -0
  53. package/server/engine/task/http-get.ts +117 -0
  54. package/server/engine/task/http-post.ts +148 -0
  55. package/server/engine/task/index.ts +45 -0
  56. package/server/engine/task/jsonata.ts +45 -0
  57. package/server/engine/task/local-graphql-mutate.ts +100 -0
  58. package/server/engine/task/local-graphql-query.ts +100 -0
  59. package/server/engine/task/log.ts +78 -0
  60. package/server/engine/task/mqtt-publish.ts +45 -0
  61. package/server/engine/task/mqtt-subscribe.ts +139 -0
  62. package/server/engine/task/mssql-procedure.ts +128 -0
  63. package/server/engine/task/oracle-procedure.ts +124 -0
  64. package/server/engine/task/pick-pending-scenario.ts +80 -0
  65. package/server/engine/task/publish.ts +40 -0
  66. package/server/engine/task/random.ts +53 -0
  67. package/server/engine/task/reset-pending-queue.ts +17 -0
  68. package/server/engine/task/script.ts +63 -0
  69. package/server/engine/task/set-domain.ts +37 -0
  70. package/server/engine/task/sleep.ts +34 -0
  71. package/server/engine/task/socket-listener.ts +96 -0
  72. package/server/engine/task/state-group-read.ts +69 -0
  73. package/server/engine/task/state-read.ts +56 -0
  74. package/server/engine/task/state-write.ts +65 -0
  75. package/server/engine/task/stop-scenario.ts +44 -0
  76. package/server/engine/task/sub-scenario.ts +57 -0
  77. package/server/engine/task/switch-goto.ts +43 -0
  78. package/server/engine/task/switch-range-goto.ts +53 -0
  79. package/server/engine/task/switch-range-scenario.ts +79 -0
  80. package/server/engine/task/switch-range-set.ts +48 -0
  81. package/server/engine/task/switch-scenario.ts +67 -0
  82. package/server/engine/task/switch-set.ts +37 -0
  83. package/server/engine/task/throw.ts +27 -0
  84. package/server/engine/task/utils/headless-pool-for-scenario.ts +71 -0
  85. package/server/engine/task/utils/substitute.ts +44 -0
  86. package/server/engine/task/variables.ts +17 -0
  87. package/server/engine/task-registry.ts +23 -0
  88. package/server/engine/types.ts +114 -0
  89. package/server/index.ts +20 -0
  90. package/server/migrations/index.ts +9 -0
  91. package/server/restful/index.ts +1 -0
  92. package/server/restful/unstable/index.ts +7 -0
  93. package/server/restful/unstable/run-scenario.ts +51 -0
  94. package/server/restful/unstable/scenario-instance.ts +52 -0
  95. package/server/restful/unstable/scenario-instances.ts +80 -0
  96. package/server/restful/unstable/scenario.ts +41 -0
  97. package/server/restful/unstable/scenarios.ts +69 -0
  98. package/server/restful/unstable/start-scenario.ts +33 -0
  99. package/server/restful/unstable/stop-scenario.ts +30 -0
  100. package/server/routers/scenario-schedule-callback-router.ts +69 -0
  101. package/server/routers/scenario-view-router.ts +46 -0
  102. package/server/routes.ts +30 -0
  103. package/server/service/analysis/analysis-query.ts +13 -0
  104. package/server/service/analysis/index.ts +3 -0
  105. package/server/service/connection/connection-mutation.ts +190 -0
  106. package/server/service/connection/connection-query.ts +87 -0
  107. package/server/service/connection/connection-subscription.ts +104 -0
  108. package/server/service/connection/connection-type.ts +288 -0
  109. package/server/service/connection/index.ts +7 -0
  110. package/server/service/connector/connector-query.ts +62 -0
  111. package/server/service/connector/connector-type.ts +29 -0
  112. package/server/service/connector/index.ts +4 -0
  113. package/server/service/index.ts +52 -0
  114. package/server/service/payload-log/index.ts +7 -0
  115. package/server/service/payload-log/payload-log-mutation.ts +151 -0
  116. package/server/service/payload-log/payload-log-query.ts +49 -0
  117. package/server/service/payload-log/payload-log-type.ts +36 -0
  118. package/server/service/payload-log/payload-log.ts +100 -0
  119. package/server/service/property-spec.ts +24 -0
  120. package/server/service/scenario/index.ts +6 -0
  121. package/server/service/scenario/scenario-mutation.ts +396 -0
  122. package/server/service/scenario/scenario-query.ts +109 -0
  123. package/server/service/scenario/scenario-type.ts +78 -0
  124. package/server/service/scenario/scenario.ts +124 -0
  125. package/server/service/scenario-flow/scenario-flow.ts +17 -0
  126. package/server/service/scenario-instance/index.ts +6 -0
  127. package/server/service/scenario-instance/scenario-instance-mutation.ts +44 -0
  128. package/server/service/scenario-instance/scenario-instance-query.ts +42 -0
  129. package/server/service/scenario-instance/scenario-instance-subscription.ts +118 -0
  130. package/server/service/scenario-instance/scenario-instance-type.ts +563 -0
  131. package/server/service/scenario-queue/index.ts +4 -0
  132. package/server/service/scenario-queue/scenario-queue-subscription.ts +55 -0
  133. package/server/service/scenario-queue/scenario-queue-type.ts +27 -0
  134. package/server/service/state-register/data-resolver.ts +56 -0
  135. package/server/service/state-register/index.ts +8 -0
  136. package/server/service/state-register/state-register-mutation.ts +166 -0
  137. package/server/service/state-register/state-register-query.ts +80 -0
  138. package/server/service/state-register/state-register-type.ts +80 -0
  139. package/server/service/state-register/state-register.ts +113 -0
  140. package/server/service/step/index.ts +6 -0
  141. package/server/service/step/step-mutation.ts +52 -0
  142. package/server/service/step/step-query.ts +55 -0
  143. package/server/service/step/step-type.ts +215 -0
  144. package/server/service/task-type/index.ts +4 -0
  145. package/server/service/task-type/task-type-query.ts +95 -0
  146. package/server/service/task-type/task-type-type.ts +29 -0
  147. package/translations/en.json +4 -12
  148. package/translations/ja.json +4 -12
  149. package/translations/ko.json +4 -12
  150. package/translations/ms.json +4 -12
  151. package/translations/zh.json +4 -12
  152. package/dist-server/engine/connector/headless-connector.d.ts +0 -23
  153. package/dist-server/engine/connector/headless-connector.js +0 -357
  154. package/dist-server/engine/connector/headless-connector.js.map +0 -1
  155. package/dist-server/engine/resource-pool/headless-pool.d.ts +0 -1
  156. package/dist-server/engine/resource-pool/headless-pool.js +0 -62
  157. package/dist-server/engine/resource-pool/headless-pool.js.map +0 -1
  158. package/dist-server/engine/resource-pool/index.d.ts +0 -1
  159. package/dist-server/engine/resource-pool/index.js +0 -5
  160. package/dist-server/engine/resource-pool/index.js.map +0 -1
@@ -0,0 +1,563 @@
1
+ import 'winston-daily-rotate-file'
2
+
3
+ import orderBy from 'lodash/orderBy'
4
+ import moment from 'moment-timezone'
5
+ import { Field, Int, ObjectType, registerEnumType } from 'type-graphql'
6
+ import util from 'util'
7
+ import { createLogger, format, transports } from 'winston'
8
+
9
+ import { Domain, pubsub, PubSubLogTransport, ScalarObject } from '@things-factory/shell'
10
+ import { cacheService } from '@things-factory/cache-service'
11
+ import { User } from '@things-factory/auth-base'
12
+ import { sleep } from '@things-factory/utils'
13
+
14
+ import { publishData } from '../../controllers/publish-data'
15
+ import { ConnectionManager } from '../../engine/connection-manager'
16
+ import { TaskRegistry } from '../../engine'
17
+ import { Context } from '../../engine/types'
18
+ import { Step } from '../step/step-type'
19
+ import { handler as edgeHandler } from '../../engine/edge-client'
20
+
21
+ const debug = require('debug')('things-factory:integration-base:scenario-instance')
22
+ const { combine, errors, splat, printf } = format
23
+
24
+ const LOGFORMAT = printf(({ level, message, timestamp, stack }) => {
25
+ return `${timestamp} ${level}: ${stack || message}`
26
+ })
27
+
28
+ function getSystemTimeZone() {
29
+ try {
30
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
31
+ if (!timeZone) {
32
+ throw new Error('Unable to resolve timeZone')
33
+ }
34
+ return timeZone
35
+ } catch (e) {
36
+ console.warn('Failed to get system timeZone, falling back to UTC.', e)
37
+ return 'UTC'
38
+ }
39
+ }
40
+
41
+ const SYSTEM_TZ = getSystemTimeZone()
42
+ const systemTimestamp = format((info, opts: { tz?: string }) => {
43
+ if (opts.tz) info.timestamp = moment().tz(opts.tz).format()
44
+ return info
45
+ })
46
+
47
+ export enum ScenarioInstanceStatus {
48
+ READY = 'READY',
49
+ STARTED = 'STARTED',
50
+ STOPPED = 'STOPPED',
51
+ HALTED = 'HALTED',
52
+ UNLOADED = 'UNLOADED'
53
+ }
54
+
55
+ registerEnumType(ScenarioInstanceStatus, {
56
+ name: 'ScenarioInstanceStatus',
57
+ description: 'state enumeration of a scenario-instance'
58
+ })
59
+
60
+ @ObjectType()
61
+ export class ScenarioInstanceProgress {
62
+ @Field(type => Int)
63
+ rounds: number
64
+
65
+ @Field(type => Int)
66
+ rate: number
67
+
68
+ @Field(type => Int)
69
+ steps: number
70
+
71
+ @Field(type => Int)
72
+ step: number
73
+ }
74
+
75
+ @ObjectType()
76
+ export class ScenarioInstanceState {
77
+ @Field({ nullable: true })
78
+ public domain: Domain
79
+
80
+ @Field({ nullable: true })
81
+ public instanceName: string
82
+
83
+ @Field({ nullable: true })
84
+ public scenarioName: string
85
+
86
+ @Field(type => ScenarioInstanceStatus, { nullable: true })
87
+ public state: ScenarioInstanceStatus
88
+
89
+ @Field(type => ScalarObject, { nullable: true })
90
+ public variables: any
91
+
92
+ @Field(type => ScalarObject, { nullable: true })
93
+ public data: any
94
+
95
+ @Field(type => ScenarioInstanceProgress, { nullable: true })
96
+ public progress: ScenarioInstanceProgress
97
+
98
+ @Field({ nullable: true })
99
+ public message: string
100
+
101
+ @Field({ nullable: true })
102
+ public timestamp: Date
103
+ }
104
+
105
+ @ObjectType()
106
+ export class ScenarioInstanceRunResult {
107
+ @Field({ nullable: true })
108
+ public scenarioName: string
109
+
110
+ @Field({ nullable: true })
111
+ public instanceName: string
112
+
113
+ @Field(type => ScalarObject, { nullable: true })
114
+ public variables: any
115
+
116
+ @Field(type => ScalarObject, { nullable: true })
117
+ public data: any
118
+
119
+ @Field(type => ScalarObject, { nullable: true })
120
+ public result: any
121
+
122
+ @Field({ nullable: true })
123
+ public timestamp: Date
124
+
125
+ @Field({ nullable: true })
126
+ public message: string
127
+
128
+ @Field({ nullable: true })
129
+ public state: ScenarioInstanceStatus
130
+ }
131
+
132
+ @ObjectType()
133
+ export class ScenarioInstance {
134
+ private subScenarioInstances: ScenarioInstance[] = [] // TODO Imple by WeakSet
135
+
136
+ public context: Context
137
+
138
+ @Field({ nullable: true })
139
+ public domain: Domain
140
+
141
+ @Field({ nullable: true })
142
+ public user: User
143
+
144
+ @Field({ nullable: true })
145
+ public scenarioName: string
146
+
147
+ @Field({ nullable: true })
148
+ public instanceName: string
149
+
150
+ @Field({ nullable: true })
151
+ get root(): ScenarioInstance {
152
+ return this.context?.root
153
+ }
154
+
155
+ @Field({ nullable: true })
156
+ get state(): ScenarioInstanceStatus {
157
+ return this.context?.state
158
+ }
159
+
160
+ @Field(type => ScenarioInstanceProgress, { nullable: true })
161
+ get progress(): ScenarioInstanceProgress {
162
+ return this.calcProgress()
163
+ }
164
+
165
+ @Field(type => ScalarObject, { nullable: true })
166
+ get variables(): any {
167
+ return this.context?.variables
168
+ }
169
+
170
+ @Field(type => ScalarObject, { nullable: true })
171
+ get data(): any {
172
+ return this.context?.data
173
+ }
174
+
175
+ @Field(type => ScalarObject, { nullable: true })
176
+ result: any
177
+
178
+ @Field({ nullable: true })
179
+ get timestamp(): Date {
180
+ return new Date()
181
+ }
182
+
183
+ @Field({ nullable: true })
184
+ public message: string
185
+
186
+ private scenarioId: string
187
+ private scenarioTtl: number
188
+ private steps: Step[]
189
+ private rounds: number = 0
190
+
191
+ private lastStep: number = -1
192
+ private nextStep: number = -1
193
+ private disposer: any
194
+ public addSubScenarioInstance(instance: ScenarioInstance): ScenarioInstance[] {
195
+ this.subScenarioInstances.push(instance)
196
+ return this.subScenarioInstances
197
+ }
198
+
199
+ public getSubScenarioInstances(): ScenarioInstance[] {
200
+ return this.subScenarioInstances
201
+ }
202
+
203
+ public async stopSubScenarios() {
204
+ var subScenarioInstances = this.getSubScenarioInstances()
205
+
206
+ var subInstance = subScenarioInstances.pop()
207
+ while (subInstance) {
208
+ await subInstance.dispose()
209
+ subInstance = subScenarioInstances.pop()
210
+ }
211
+ }
212
+
213
+ constructor(
214
+ instanceName,
215
+ {
216
+ id: scenarioId,
217
+ ttl: scenarioTtl,
218
+ name: scenarioName,
219
+ steps,
220
+ domain: scenarioDomain
221
+ }: { id: string; ttl?: number; name: string; steps: Step[]; domain: Domain },
222
+ context?
223
+ ) {
224
+ var { domain, user, lng, unsafeIP, prohibitedPrivileges } = context || {}
225
+ domain ||= scenarioDomain
226
+
227
+ this.scenarioId = scenarioId
228
+ this.scenarioTtl = scenarioTtl
229
+ this.instanceName = instanceName
230
+ this.scenarioName = scenarioName
231
+ this.steps = orderBy(steps || [], step => step.sequence)
232
+ this.domain = domain
233
+ this.user = user
234
+ this.disposer = context?.disposer
235
+
236
+ this.context = {
237
+ domain,
238
+ user,
239
+ lng,
240
+ unsafeIP,
241
+ prohibitedPrivileges,
242
+ logger:
243
+ context?.logger ||
244
+ createLogger({
245
+ format: combine(errors({ stack: true }), systemTimestamp({ tz: SYSTEM_TZ }), splat(), LOGFORMAT),
246
+ transports: [
247
+ new (transports as any).DailyRotateFile({
248
+ filename: `logs/${domain.subdomain}/scenario-${scenarioName}-%DATE%.log`,
249
+ datePattern: 'YYYY-MM-DD-HH',
250
+ zippedArchive: false,
251
+ maxSize: '5m',
252
+ maxFiles: '14d',
253
+ level: 'info'
254
+ }),
255
+ new PubSubLogTransport({
256
+ topic: 'scenario-instance-log',
257
+ source: { domain, scenarioName, instanceName }
258
+ })
259
+ ]
260
+ }),
261
+ publish: context?.publish || this.publishData.bind(this),
262
+ load: context?.load || this.loadSubscenario.bind(this),
263
+ data: context?.data || {},
264
+ variables: context?.variables || {},
265
+ client: context?.client,
266
+ state: ScenarioInstanceStatus.STOPPED,
267
+ root: context?.root || this,
268
+ closures: [],
269
+ checkState(state = ScenarioInstanceStatus.STARTED) {
270
+ return this.state == state
271
+ }
272
+ }
273
+
274
+ this.setState(ScenarioInstanceStatus.READY)
275
+ }
276
+
277
+ async run() {
278
+ var state = this.getState()
279
+ if (state == ScenarioInstanceStatus.STARTED || this.steps.length == 0) {
280
+ return
281
+ }
282
+
283
+ this.setState(ScenarioInstanceStatus.STARTED)
284
+ var context = this.context
285
+
286
+ try {
287
+ while (this.getState() == ScenarioInstanceStatus.STARTED) {
288
+ if (this.nextStep == -1) {
289
+ this.setNextStep(0)
290
+ }
291
+
292
+ if (this.nextStep == 0) {
293
+ this.rounds++
294
+ this.context.logger.info(`Start ${this.rounds} Rounds #######`)
295
+ }
296
+
297
+ var step = this.steps[this.nextStep]
298
+ var next, data
299
+
300
+ if (!step.skip) {
301
+ // @ts-ignore: Initializer provides no value for this binding element and the binding element has no default value.
302
+ var { next, state: stepState, data } = (await this.process(step, context)) || {}
303
+ context.data[step.name] = data
304
+ } else {
305
+ next = ''
306
+ stepState = undefined
307
+ }
308
+
309
+ this.publishState()
310
+
311
+ await sleep(1)
312
+
313
+ if (next) {
314
+ this.setNextStep(
315
+ this.steps.findIndex(step => {
316
+ return step.name == next
317
+ })
318
+ )
319
+ if (this.nextStep == -1) {
320
+ throw 'Not Found Next Step'
321
+ }
322
+ } else if (this.nextStep == this.steps.length - 1) {
323
+ this.setState(ScenarioInstanceStatus.STOPPED)
324
+ } else {
325
+ this.setNextStep(this.nextStep + 1)
326
+ }
327
+
328
+ /* last step 에 의해서 시나리오 state를 변경할 수 있도록 함. */
329
+ if (stepState !== undefined) {
330
+ this.setState(stepState)
331
+ }
332
+ }
333
+ } catch (ex) {
334
+ const message = ex.stack ? ex.stack : ex
335
+ const { scenarioName, domain } = this
336
+
337
+ this.context.logger.error(ex)
338
+
339
+ debug('failed to run ', `[ Domain: ${domain.name}, Scenario: ${scenarioName} ]\n`, ex)
340
+ this.setState(
341
+ ScenarioInstanceStatus.HALTED,
342
+ typeof message == 'object' ? JSON.stringify(message, null, 2) : message
343
+ )
344
+
345
+ throw ex
346
+ }
347
+
348
+ this.result = this.steps
349
+ .filter(step => !!step.result)
350
+ .reduce((sum, step) => {
351
+ sum[step.name] = this.context.data[step.name]
352
+ return sum
353
+ }, {})
354
+
355
+ const { scenarioId, scenarioTtl, variables, message, scenarioName, instanceName, result, domain } = this
356
+ const obj = {
357
+ scenarioName,
358
+ instanceName,
359
+ variables,
360
+ data: this.data,
361
+ result,
362
+ timestamp: new Date(),
363
+ message,
364
+ state: ScenarioInstanceStatus.STOPPED /* redundent, no meaning */
365
+ }
366
+
367
+ if (this.scenarioTtl && this.scenarioId) {
368
+ setTimeout(() => {
369
+ cacheService.setInCache(scenarioId, { domain: domain.id, variables: variables || {} }, obj, scenarioTtl)
370
+ })
371
+ }
372
+
373
+ return obj
374
+ }
375
+
376
+ async loadSubscenario(step, scenarioConfig, context) {
377
+ var { name: stepName, params } = step
378
+ var { preventErrorPropagation } = params || {}
379
+
380
+ debug('load-subscenario', this.instanceName, stepName, scenarioConfig.name)
381
+
382
+ context.data[stepName] = {}
383
+
384
+ let subContext = {
385
+ ...context,
386
+ data: context.data[stepName],
387
+ closures: [],
388
+ state: ScenarioInstanceStatus.READY
389
+ }
390
+
391
+ if (!scenarioConfig.domain) {
392
+ scenarioConfig.domain = context.domain
393
+ }
394
+
395
+ var subScenarioInstance = new ScenarioInstance(`${this.instanceName}$${stepName}`, scenarioConfig, subContext)
396
+ this.addSubScenarioInstance(subScenarioInstance)
397
+ await subScenarioInstance.run()
398
+
399
+ if (!preventErrorPropagation && subScenarioInstance.getState() == ScenarioInstanceStatus.HALTED) {
400
+ throw new Error(`Sub-scenario[${this.instanceName}$${stepName}] is halted.`)
401
+ }
402
+
403
+ return subContext
404
+ }
405
+
406
+ publishData(tag, data) {
407
+ publishData(tag, data, this.context)
408
+ }
409
+
410
+ publishState() {
411
+ const {
412
+ instanceName,
413
+ scenarioName,
414
+ steps,
415
+ domain,
416
+ message,
417
+ context: { data, variables, state }
418
+ } = this
419
+
420
+ pubsub.publish('scenario-instance-state', {
421
+ scenarioInstanceState: {
422
+ domain,
423
+ instanceName,
424
+ scenarioName,
425
+ state,
426
+ variables,
427
+ progress: this.calcProgress(),
428
+ data,
429
+ message,
430
+ timestamp: new Date()
431
+ }
432
+ })
433
+
434
+ this.context.logger.info(this.message)
435
+ }
436
+
437
+ calcProgress(): ScenarioInstanceProgress {
438
+ var steps = this.steps.length
439
+ var step = Math.max(this.lastStep, 0)
440
+
441
+ return {
442
+ rounds: this.rounds,
443
+ rate: steps ? Math.round(100 * (step / steps)) : 0,
444
+ steps,
445
+ step
446
+ }
447
+ }
448
+
449
+ setNextStep(step) {
450
+ this.lastStep = this.nextStep + 1
451
+ this.nextStep = step
452
+ }
453
+
454
+ getState(): ScenarioInstanceStatus {
455
+ return this.context.state
456
+ }
457
+
458
+ setState(state, message?) {
459
+ if (this.context.state == state) {
460
+ return
461
+ }
462
+
463
+ this.message = `${this.instanceName}:[state changed] ${ScenarioInstanceStatus[this.getState()]} => ${ScenarioInstanceStatus[state]}${
464
+ message ? ' caused by ' + util.inspect(message, false, 2, true) : ''
465
+ }`
466
+
467
+ this.context.state = state
468
+
469
+ if (state == ScenarioInstanceStatus.STOPPED || state == ScenarioInstanceStatus.HALTED) {
470
+ this.setNextStep(-1)
471
+ this.setState(ScenarioInstanceStatus.UNLOADED)
472
+ } else if (state == ScenarioInstanceStatus.UNLOADED) {
473
+ this.setNextStep(-1)
474
+ this.dispose()
475
+ }
476
+
477
+ this.publishState()
478
+ }
479
+
480
+ async start() {
481
+ await this.run()
482
+ }
483
+
484
+ stop() {
485
+ if (this.getState() !== ScenarioInstanceStatus.HALTED) {
486
+ this.setState(ScenarioInstanceStatus.STOPPED)
487
+ }
488
+ }
489
+
490
+ unload() {
491
+ this.setState(ScenarioInstanceStatus.UNLOADED)
492
+ }
493
+
494
+ async dispose() {
495
+ await this.stopSubScenarios()
496
+
497
+ this.unload()
498
+
499
+ var closure = this.context?.closures?.pop()
500
+ while (closure) {
501
+ closure.call(this)
502
+ closure = this.context?.closures?.pop()
503
+ }
504
+
505
+ if (this.disposer) {
506
+ await this.disposer.call(this)
507
+ }
508
+
509
+ // {{ CHECKPOINT 본 인스턴스를 위해서 생성된 logger를 닫는다. 사용을 완료하고 닫기위해서 지연해서 수행한다.
510
+ if (this.context?.logger) {
511
+ setTimeout(() => {
512
+ this.context.logger.close()
513
+ }, 300)
514
+ }
515
+ // }}
516
+ }
517
+
518
+ async process(step, context): Promise<{ next: string; state: ScenarioInstanceStatus; data: object }> {
519
+ this.context.logger.info(`Step '${step.name}'(${step.id}) started.`)
520
+
521
+ step = {
522
+ ...step
523
+ } // copy step
524
+
525
+ try {
526
+ step.params = JSON.parse(step.params)
527
+ } catch (ex) {
528
+ this.context.logger.error(`params(${step.params}) parsing error. params must be a JSON.`, ex)
529
+ }
530
+ step.params = step.params || {}
531
+
532
+ const connection =
533
+ step.connection && ConnectionManager.getConnectionInstanceEntityByName(this.domain, step.connection)
534
+
535
+ if (!connection || !connection.edgeId) {
536
+ var handler = TaskRegistry.getTaskHandler(step.task)
537
+ if (!handler) {
538
+ throw new Error(`no task handler for step '${step.name}'(${step.id})`)
539
+ }
540
+
541
+ var retval: any = await handler(step, context)
542
+ } else {
543
+ var retval: any = await edgeHandler(step, context)
544
+ }
545
+
546
+ if (step.log) {
547
+ var { data } = retval || {}
548
+ this.context.logger.info(`returns ${typeof data == 'string' ? data : JSON.stringify(data, null, 2)}`)
549
+ }
550
+
551
+ this.context.logger.info(`Step done.`)
552
+ return retval
553
+ }
554
+ }
555
+
556
+ @ObjectType()
557
+ export class ScenarioInstanceList {
558
+ @Field(type => [ScenarioInstance])
559
+ items: ScenarioInstance[]
560
+
561
+ @Field(type => Int)
562
+ total: number
563
+ }
@@ -0,0 +1,4 @@
1
+ import { ScenarioQueueSubscription } from './scenario-queue-subscription'
2
+
3
+ export const entities = []
4
+ export const resolvers = [ScenarioQueueSubscription]
@@ -0,0 +1,55 @@
1
+ import { Resolver, Subscription, Root, Arg } from 'type-graphql'
2
+ import { ScenarioQueueState } from './scenario-queue-type'
3
+ import { pubsub } from '@things-factory/shell'
4
+ import { filter, pipe } from 'graphql-yoga'
5
+ import { ScenarioEngine } from '../../engine'
6
+
7
+ const debug = require('debug')('things-factory:integration:connection-subscription')
8
+
9
+ @Resolver()
10
+ export class ScenarioQueueSubscription {
11
+ @Subscription({
12
+ subscribe: ({ args, context, info }) => {
13
+ const { domain, user } = context.state
14
+ const subdomain = domain?.subdomain
15
+
16
+ debug('subscribe', subdomain)
17
+
18
+ if (!domain) {
19
+ throw new Error('domain required.')
20
+ }
21
+
22
+ if (!user.domains?.find(d => d.subdomain === subdomain) && !process.superUserGranted(domain, user)) {
23
+ throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`)
24
+ }
25
+
26
+ process.nextTick(async () => {
27
+ var queue = ScenarioEngine.getPendingQueue(domain)
28
+ if (queue) {
29
+ pubsub.publish('scenario-queue-state', {
30
+ scenarioQueueState: {
31
+ domain,
32
+ queue: queue.queue
33
+ }
34
+ })
35
+ }
36
+ })
37
+
38
+ return pipe(
39
+ pubsub.subscribe('scenario-queue-state'),
40
+ filter((payload: { scenarioQueueState: ScenarioQueueState }) => {
41
+ const { domain: pdomain } = payload.scenarioQueueState
42
+
43
+ if (pdomain?.subdomain !== subdomain) {
44
+ return false
45
+ }
46
+
47
+ return true
48
+ })
49
+ )
50
+ }
51
+ })
52
+ scenarioQueueState(@Root() payload: { scenarioQueueState: ScenarioQueueState }): ScenarioQueueState {
53
+ return payload.scenarioQueueState
54
+ }
55
+ }
@@ -0,0 +1,27 @@
1
+ import { Field, Int, ObjectType } from 'type-graphql'
2
+
3
+ import { Domain, ScalarObject } from '@things-factory/shell'
4
+
5
+ @ObjectType()
6
+ export class PendingObject {
7
+ @Field(type => ScalarObject)
8
+ stuff: any
9
+
10
+ @Field()
11
+ due: string
12
+
13
+ @Field(type => Int)
14
+ priority: number
15
+
16
+ @Field({ nullable: true })
17
+ tag: String
18
+ }
19
+
20
+ @ObjectType()
21
+ export class ScenarioQueueState {
22
+ @Field()
23
+ domain: Domain
24
+
25
+ @Field(type => [PendingObject])
26
+ queue: PendingObject[]
27
+ }
@@ -0,0 +1,56 @@
1
+ import { filter, pipe } from 'graphql-yoga'
2
+ import { Arg, Resolver, Root, Subscription } from 'type-graphql'
3
+
4
+ import { pubsub, Data, getRepository } from '@things-factory/shell'
5
+ import { StateRegister } from './state-register'
6
+
7
+ /* 이 Resolver는 @things-factory/shell에서 등록한 DataResolver를 Overide 한 것이다. */
8
+ @Resolver()
9
+ export class DataResolver {
10
+ @Subscription({
11
+ subscribe: ({ args, context, info }) => {
12
+ const { domain, user } = context.state
13
+ const { tag } = args
14
+ const subdomain = domain?.subdomain
15
+
16
+ if (!domain || !tag) {
17
+ throw new Error('domain and tag required')
18
+ }
19
+
20
+ //@ts-ignore
21
+ if (!user.domains?.find(d => d.subdomain === subdomain) && !process.superUserGranted(domain, user)) {
22
+ throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`)
23
+ }
24
+
25
+ process.nextTick(async () => {
26
+ /* state-register에 등록된 태그라면, 현재 상태값을 publish 한다. */
27
+ const { domain } = context.state
28
+
29
+ const state = await getRepository(StateRegister).findOne({
30
+ where: { domain: { id: domain.id }, name: tag }
31
+ })
32
+
33
+ if (state) {
34
+ pubsub.publish('data', {
35
+ data: {
36
+ domain,
37
+ tag,
38
+ data: state.state
39
+ }
40
+ })
41
+ }
42
+ })
43
+
44
+ return pipe(
45
+ pubsub.subscribe('data'),
46
+ filter((payload: { data: Data }) => {
47
+ const { domain: pdomain, tag: ptag, data } = payload.data
48
+ return tag === ptag && subdomain === pdomain?.subdomain
49
+ })
50
+ )
51
+ }
52
+ })
53
+ data(@Root() payload: { data: Data }, @Arg('tag') tag: string): Data {
54
+ return payload.data
55
+ }
56
+ }
@@ -0,0 +1,8 @@
1
+ import { StateRegister } from './state-register'
2
+ import { StateRegisterQuery } from './state-register-query'
3
+ import { StateRegisterMutation } from './state-register-mutation'
4
+ import { DataResolver } from './data-resolver'
5
+
6
+ export const entities = [StateRegister]
7
+ export const resolvers = [StateRegisterQuery, StateRegisterMutation, DataResolver]
8
+ export const subscribers = []