workflow-input.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. /**
  2. * @node workflow-input
  3. * @name Workflow Input
  4. * @category triggers
  5. * @version 1.0.0
  6. * @description Start a workflow that another workflow calls, and say what it expects to be given
  7. * @trigger
  8. * @icon log-in
  9. */
  10. const configSchema = {
  11. type: 'object',
  12. properties: {
  13. fields: {
  14. type: 'array',
  15. title: 'Expected Input',
  16. description: 'What the calling workflow must or may pass. A caller that leaves out a required one is refused here rather than failing somewhere deeper',
  17. items: {
  18. type: 'object',
  19. properties: {
  20. name: { type: 'string', title: 'Name', description: 'Key the caller passes, such as city' },
  21. required: { type: 'boolean', title: 'Required', description: 'Refuse the call when this is missing', default: true },
  22. description: { type: 'string', title: 'Description', description: 'What the caller should put here' },
  23. defaultValue: { type: 'string', title: 'Default', description: 'Used when an optional key is not passed' }
  24. }
  25. }
  26. },
  27. allowExtra: {
  28. type: 'boolean',
  29. title: 'Allow Anything Else',
  30. description: 'Pass through keys that are not listed above. Turn off to keep the contract exact and catch a caller sending the wrong thing',
  31. default: true
  32. }
  33. }
  34. };
  35. const inputSchema = { type: 'object', properties: { data: { type: 'any' } } };
  36. const outputSchema = {
  37. type: 'object',
  38. properties: {
  39. calledBy: { type: 'string', description: 'Execution id of the workflow that called this one, empty when run directly' }
  40. }
  41. };
  42. async function execute(config, input, context) {
  43. // The caller's input arrives as the trigger data, the same way a webhook
  44. // body does. Internal keys the engine put there are not part of it.
  45. const given = (input && typeof input === 'object') ? input : {};
  46. const fields = config.fields || [];
  47. const result = {};
  48. const missing = [];
  49. for (let i = 0; i < fields.length; i++) {
  50. const field = fields[i] || {};
  51. const name = String(field.name || '').trim();
  52. if (!name) continue;
  53. const has = Object.prototype.hasOwnProperty.call(given, name) &&
  54. given[name] !== undefined && given[name] !== null && given[name] !== '';
  55. if (has) {
  56. result[name] = given[name];
  57. } else if (field.required !== false) {
  58. missing.push(name);
  59. } else if (field.defaultValue !== undefined && field.defaultValue !== '') {
  60. result[name] = field.defaultValue;
  61. }
  62. }
  63. // Refusing here, naming every missing key at once, beats failing three
  64. // nodes later on an undefined that has to be traced back.
  65. if (missing.length > 0) {
  66. throw new Error('Workflow Input: the caller did not pass ' + missing.join(', ') +
  67. '. ' + (missing.length === 1 ? 'It is' : 'They are') + ' marked required on this node');
  68. }
  69. if (config.allowExtra !== false) {
  70. const keys = Object.keys(given);
  71. for (let i = 0; i < keys.length; i++) {
  72. const key = keys[i];
  73. // Engine bookkeeping, not part of what the caller sent.
  74. if (key.charAt(0) === '_') continue;
  75. if (!Object.prototype.hasOwnProperty.call(result, key)) {
  76. result[key] = given[key];
  77. }
  78. }
  79. }
  80. result.calledBy = (given && given._calledBy) || '';
  81. smartbotic.log.info('Workflow Input: accepted ' +
  82. Object.keys(result).filter(function (k) { return k !== 'calledBy'; }).length + ' value(s)');
  83. return result;
  84. }
  85. module.exports = { configSchema, inputSchema, outputSchema, execute };