|
@@ -1,6 +1,7 @@
|
|
|
#include "workflow_engine.hpp"
|
|
#include "workflow_engine.hpp"
|
|
|
#include "common/uuid.hpp"
|
|
#include "common/uuid.hpp"
|
|
|
#include "common/time_utils.hpp"
|
|
#include "common/time_utils.hpp"
|
|
|
|
|
+#include "common/config_defaults.hpp"
|
|
|
#include "logging/logger.hpp"
|
|
#include "logging/logger.hpp"
|
|
|
#include <algorithm>
|
|
#include <algorithm>
|
|
|
#include <functional>
|
|
#include <functional>
|
|
@@ -648,7 +649,13 @@ Result<ExecutionResult> WorkflowEngine::execute(const Workflow& workflow,
|
|
|
// Evaluate expressions in node config
|
|
// Evaluate expressions in node config
|
|
|
WorkflowNode evaluated_node = *node;
|
|
WorkflowNode evaluated_node = *node;
|
|
|
t_disabled_reference_error.clear();
|
|
t_disabled_reference_error.clear();
|
|
|
- evaluated_node.config = evaluateExpressions(node->config, input, result.node_results, workflow);
|
|
|
|
|
|
|
+ nlohmann::json defaulted_config = node->config;
|
|
|
|
|
+ auto node_def_for_defaults = registry_.getNode(node->type);
|
|
|
|
|
+ if (node_def_for_defaults) {
|
|
|
|
|
+ defaulted_config = smartbotic::common::applyConfigDefaults(
|
|
|
|
|
+ node->config, node_def_for_defaults->config_schema);
|
|
|
|
|
+ }
|
|
|
|
|
+ evaluated_node.config = evaluateExpressions(defaulted_config, input, result.node_results, workflow);
|
|
|
|
|
|
|
|
if (!t_disabled_reference_error.empty()) {
|
|
if (!t_disabled_reference_error.empty()) {
|
|
|
node_result.node_id = node_id;
|
|
node_result.node_id = node_id;
|
|
@@ -659,7 +666,8 @@ Result<ExecutionResult> WorkflowEngine::execute(const Workflow& workflow,
|
|
|
node_result.started_at = TimeUtils::nowMs();
|
|
node_result.started_at = TimeUtils::nowMs();
|
|
|
node_result.finished_at = node_result.started_at;
|
|
node_result.finished_at = node_result.started_at;
|
|
|
} else {
|
|
} else {
|
|
|
- node_result = executeNode(evaluated_node, input, result.execution_id, workflow);
|
|
|
|
|
|
|
+ node_result = executeNode(evaluated_node, input, result.execution_id, workflow,
|
|
|
|
|
+ node_def_for_defaults);
|
|
|
}
|
|
}
|
|
|
result.node_results[node_id] = node_result;
|
|
result.node_results[node_id] = node_result;
|
|
|
|
|
|
|
@@ -1112,15 +1120,22 @@ std::vector<std::string> WorkflowEngine::topologicalSort(
|
|
|
NodeExecutionResult WorkflowEngine::executeNode(const WorkflowNode& node,
|
|
NodeExecutionResult WorkflowEngine::executeNode(const WorkflowNode& node,
|
|
|
const nlohmann::json& input,
|
|
const nlohmann::json& input,
|
|
|
const std::string& execution_id,
|
|
const std::string& execution_id,
|
|
|
- const Workflow& workflow) {
|
|
|
|
|
|
|
+ const Workflow& workflow,
|
|
|
|
|
+ const std::optional<NodeDefinition>& prefetched_node_def) {
|
|
|
NodeExecutionResult result;
|
|
NodeExecutionResult result;
|
|
|
result.node_id = node.id;
|
|
result.node_id = node.id;
|
|
|
result.input = input;
|
|
result.input = input;
|
|
|
result.started_at = TimeUtils::nowMs();
|
|
result.started_at = TimeUtils::nowMs();
|
|
|
result.status = NodeStatus::Running;
|
|
result.status = NodeStatus::Running;
|
|
|
|
|
|
|
|
- // Get node definition
|
|
|
|
|
- auto node_def = registry_.getNode(node.type);
|
|
|
|
|
|
|
+ // Get node definition - reuse the caller's lookup if it already made one
|
|
|
|
|
+ // (it did, to apply config defaults before expression evaluation) rather
|
|
|
|
|
+ // than looking it up, and copying its JavaScript source, a second time.
|
|
|
|
|
+ std::optional<NodeDefinition> looked_up_node_def;
|
|
|
|
|
+ if (!prefetched_node_def) {
|
|
|
|
|
+ looked_up_node_def = registry_.getNode(node.type);
|
|
|
|
|
+ }
|
|
|
|
|
+ const std::optional<NodeDefinition>& node_def = prefetched_node_def ? prefetched_node_def : looked_up_node_def;
|
|
|
if (!node_def) {
|
|
if (!node_def) {
|
|
|
result.status = NodeStatus::Failed;
|
|
result.status = NodeStatus::Failed;
|
|
|
result.error = "Node type not found: " + node.type;
|
|
result.error = "Node type not found: " + node.type;
|
|
@@ -2032,10 +2047,19 @@ bool WorkflowEngine::executeLoopBody(
|
|
|
merged_results[key] = value;
|
|
merged_results[key] = value;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Evaluate expressions in body node config before execution
|
|
|
|
|
|
|
+ // Evaluate expressions in body node config before execution. Apply
|
|
|
|
|
+ // config defaults first, same as the main walk does, so a loop
|
|
|
|
|
+ // body node also gets its schema defaults on an already-saved
|
|
|
|
|
+ // workflow rather than only on nodes outside a loop.
|
|
|
WorkflowNode evaluated_body_node = *body_node;
|
|
WorkflowNode evaluated_body_node = *body_node;
|
|
|
t_disabled_reference_error.clear();
|
|
t_disabled_reference_error.clear();
|
|
|
- evaluated_body_node.config = evaluateExpressions(body_node->config, node_input, merged_results, workflow);
|
|
|
|
|
|
|
+ nlohmann::json defaulted_body_config = body_node->config;
|
|
|
|
|
+ auto body_node_def_for_defaults = registry_.getNode(body_node->type);
|
|
|
|
|
+ if (body_node_def_for_defaults) {
|
|
|
|
|
+ defaulted_body_config = smartbotic::common::applyConfigDefaults(
|
|
|
|
|
+ body_node->config, body_node_def_for_defaults->config_schema);
|
|
|
|
|
+ }
|
|
|
|
|
+ evaluated_body_node.config = evaluateExpressions(defaulted_body_config, node_input, merged_results, workflow);
|
|
|
|
|
|
|
|
NodeExecutionResult body_result;
|
|
NodeExecutionResult body_result;
|
|
|
if (!t_disabled_reference_error.empty()) {
|
|
if (!t_disabled_reference_error.empty()) {
|
|
@@ -2047,7 +2071,8 @@ bool WorkflowEngine::executeLoopBody(
|
|
|
body_result.started_at = TimeUtils::nowMs();
|
|
body_result.started_at = TimeUtils::nowMs();
|
|
|
body_result.finished_at = body_result.started_at;
|
|
body_result.finished_at = body_result.started_at;
|
|
|
} else {
|
|
} else {
|
|
|
- body_result = executeNode(evaluated_body_node, node_input, result.execution_id, workflow);
|
|
|
|
|
|
|
+ body_result = executeNode(evaluated_body_node, node_input, result.execution_id, workflow,
|
|
|
|
|
+ body_node_def_for_defaults);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
rejectPauseInLoopBody(body_result);
|
|
rejectPauseInLoopBody(body_result);
|