|
|
@@ -0,0 +1,106 @@
|
|
|
+/**
|
|
|
+ * @node wait-for-approval
|
|
|
+ * @name Wait for Approval
|
|
|
+ * @category flow-control
|
|
|
+ * @version 1.0.0
|
|
|
+ * @description Pause the execution until someone answers, then continue with what they sent
|
|
|
+ * @icon user-check
|
|
|
+ */
|
|
|
+
|
|
|
+const configSchema = {
|
|
|
+ type: 'object',
|
|
|
+ properties: {
|
|
|
+ reason: {
|
|
|
+ type: 'string',
|
|
|
+ title: 'Reason',
|
|
|
+ description: 'What the approver is being asked to decide. Supports {{variable}} interpolation',
|
|
|
+ format: 'textarea'
|
|
|
+ },
|
|
|
+ expiresIn: {
|
|
|
+ type: 'number',
|
|
|
+ title: 'Expires In (hours)',
|
|
|
+ description: 'How long the request stays answerable. 0 means it never expires, though the execution is still capped at 30 days',
|
|
|
+ default: 24
|
|
|
+ },
|
|
|
+ fields: {
|
|
|
+ type: 'array',
|
|
|
+ title: 'Fields To Collect',
|
|
|
+ description: 'Extra values the approver is asked for, sent back on the answer',
|
|
|
+ items: {
|
|
|
+ type: 'object',
|
|
|
+ properties: {
|
|
|
+ name: { type: 'string', title: 'Name' },
|
|
|
+ title: { type: 'string', title: 'Label' },
|
|
|
+ type: { type: 'string', title: 'Type', enum: ['string', 'number', 'boolean'], default: 'string' }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const inputSchema = {
|
|
|
+ type: 'object',
|
|
|
+ properties: {
|
|
|
+ data: { type: 'any' }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const outputSchema = {
|
|
|
+ type: 'object',
|
|
|
+ properties: {
|
|
|
+ token: { type: 'string', description: 'Token that must be presented to answer this request' },
|
|
|
+ reason: { type: 'string' },
|
|
|
+ expiresAt: { type: 'number', description: 'Milliseconds since the epoch, 0 when it never expires' },
|
|
|
+ approved: { type: 'boolean', description: 'Set on the answer, after the execution resumes' },
|
|
|
+ answeredBy: { type: 'string', description: 'Set on the answer' },
|
|
|
+ answeredAt: { type: 'number', description: 'Set on the answer' }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const MAX_EXPIRY_HOURS = 30 * 24;
|
|
|
+
|
|
|
+async function execute(config, input, context) {
|
|
|
+ // Loop iterations keep their state in engine locals that the stored
|
|
|
+ // execution does not describe, so a pause inside one could be recorded but
|
|
|
+ // never resumed. Failing here is far better than accepting an approval that
|
|
|
+ // can never be answered.
|
|
|
+ if (input && (input.index !== undefined || input.currentIndex !== undefined)) {
|
|
|
+ throw new Error('Wait for Approval: this node cannot be used inside a Loop body, ' +
|
|
|
+ 'because a paused loop iteration cannot be resumed. Collect the items first, ' +
|
|
|
+ 'approve once, then loop');
|
|
|
+ }
|
|
|
+
|
|
|
+ const hours = config.expiresIn === undefined || config.expiresIn === null || config.expiresIn === ''
|
|
|
+ ? 24
|
|
|
+ : Number(config.expiresIn);
|
|
|
+ if (isNaN(hours) || hours < 0) {
|
|
|
+ throw new Error('Wait for Approval: expiresIn must be a number of hours, got "' +
|
|
|
+ config.expiresIn + '"');
|
|
|
+ }
|
|
|
+ if (hours > MAX_EXPIRY_HOURS) {
|
|
|
+ throw new Error('Wait for Approval: expiresIn is capped at ' + MAX_EXPIRY_HOURS +
|
|
|
+ ' hours. An approval nobody answers should eventually stop occupying the queue');
|
|
|
+ }
|
|
|
+
|
|
|
+ const token = smartbotic.utils.uuid();
|
|
|
+ const expiresAt = hours === 0 ? 0 : Date.now() + Math.round(hours * 60 * 60 * 1000);
|
|
|
+ const reason = config.reason ? String(config.reason) : 'Approval required';
|
|
|
+ const fields = Array.isArray(config.fields) ? config.fields : [];
|
|
|
+
|
|
|
+ smartbotic.log.info('Wait for Approval: pausing for "' + reason + '", token ' + token);
|
|
|
+
|
|
|
+ return {
|
|
|
+ token: token,
|
|
|
+ reason: reason,
|
|
|
+ expiresAt: expiresAt,
|
|
|
+ fields: fields,
|
|
|
+ _pause: {
|
|
|
+ token: token,
|
|
|
+ reason: reason,
|
|
|
+ expiresAt: expiresAt,
|
|
|
+ fields: fields
|
|
|
+ }
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+module.exports = { configSchema, inputSchema, outputSchema, execute };
|