Automation - #2812
Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
| */ | ||
| public final class AppEngineNodeExecutor implements IAutomationNodeExecutor { | ||
|
|
||
| @Override |
There was a problem hiding this comment.
lets make sure to use comments where we can -- and to log following
attaching what i have in my review agent for loggign
Maher's Specific Code Patterns
Logging (His Most Enforced Pattern)
Maher has mass-converted logging across the entire codebase. His rules:
// ❌ NEVER — string concatenation in log statements
classLogger.error("Failed to upload file: " + filePath, e);
classLogger.info("Sync completed for: " + storagePath);
// ✅ ALWAYS — SLF4J {} placeholder notation
classLogger.error("Failed to upload file: {}", filePath, e);
classLogger.info("Sync completed for: {}", storagePath);Critical detail: When logging exceptions, the exception object goes as the LAST argument after all placeholders:
// ✅ Correct — exception is last arg, not in placeholder
classLogger.error("Error processing {} for user {}", engineId, userId, e);
// ❌ Wrong — exception consumed by placeholder
classLogger.error("Error processing {} for user {} with error {}", engineId, userId, e);No System.out.println — ever. Use classLogger.
Conditional log messages — for ternary log statements, Maher prefers if/else when the messages are substantially different:
// ✅ Maher's preferred style for divergent messages
if (uploadedFiles.isEmpty()) {
classLogger.info("No files were uploaded.");
} else {
classLogger.info("Successfully uploaded files: {}", uploadedFiles);
}
// ✅ Acceptable for simple variant messages
classLogger.info(found ? "Sync completed for: {}" : "No files found for: {}", storagePath);Exception Handling
// ✅ Throw SemossPixelException for user-facing errors in reactors
throw new SemossPixelException("Engine does not exist or user does not have access");
// ✅ IllegalArgumentException for validation errors
throw new IllegalArgumentException("Must pass in the project id");
// ✅ RuntimeException wrapping cause for infrastructure errors
throw new RuntimeException("Invalid S3 endpoint URI: " + this.endpoint, e);
// ❌ Don't throw errors for recoverable situations — return gracefully
// Instead of: throw new SemossPixelException("No MCP tools found");
// Do: return new NounMetadata(new ArrayList<>(), PixelDataType.MAP);Key pattern from PR #2319: Instead of throwing an error, return no tools if engine MCP is not enabled. Maher prefers graceful degradation over hard failures when the situation is recoverable.
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU General Public License for more details. |
There was a problem hiding this comment.
similarly lets add proper java docs, will send you my agent file to run a local review
| throw new IllegalArgumentException("Database-engine node \"" + nodeLabel + "\": engine not found: " + resolvedEngineId); | ||
| } | ||
|
|
||
| if ("write".equals(operation)) { |
There was a problem hiding this comment.
I know this contradicts what i said on the call -- but i think for databases we should use the reactor or the some util class they do for SqlQueryReactor
There was a problem hiding this comment.
What is the main difference between this and getautomation
There was a problem hiding this comment.
not sure if this file or list runs
but we need to scope the timezone to the users location on return
might just be a bug on my local
| @SuppressWarnings("unchecked") | ||
| Map<String, Object> graph = (Map<String, Object>) doc.get("graph"); | ||
| @SuppressWarnings("unchecked") | ||
| List<Map<String, Object>> nodes = (List<Map<String, Object>>) graph.get("nodes"); |
There was a problem hiding this comment.
do we still need edges, graphs and stuff since we moved to form
| } | ||
| } | ||
|
|
||
| private Map<String, Object> executeSingleNode(String runId, String projectId, Map<String, Object> node, |
There was a problem hiding this comment.
we should make a utiility for this file -- its really large otherwise
we should try to make the reactors as lightweight as we can
|
Lets also add a readme to the folders and a agent.md (similar to the one i sent in the chat) |
Description
Adds backend functionality to our Automation feature - a new
AUTOMATIONproject type that allows users (currently only admins) to build and execute multi-step data pipelines composed of engine calls and control nodes (database, model, vector, storage, function, app, trigger, and wait). Pipelines are authored in the frontend editor and stored asautomation.jsonin the project's app's assets folder. Execution runs asynchronously in a thread pool with per-run heartbeat, cancellation support, and the full run/node history persisting in the Scheduler DB.Changes Made
New project type
AUTOMATIONadded toIProjectenumCore automation package (
prerna.reactor.automation)AutomationConstants— all column names, table names, status values, and node type strings in one placeAutomationDatabaseUtility— creates and manages three Scheduler DB tables on startup:AUTOMATION_RUNS,AUTOMATION_NODE_OUTPUTS,AUTOMATION_ACTIVE_RUN; handles heartbeat updates, stale-run interruption on server restart, and cancellation flagAutomationExecutionUtils— shared Gson instance, output-transform pipeline (raw, rows-as-objects, first-row, column, JSONPath), output preview truncation, and${var}template substitutionAutomationRunEngine— orchestrates a full pipeline run: claims the active-run slot (one run per project at a time),seeds the variable scope, walks nodes in order, and runs a background heartbeat thread
AutomationCancelledException— distinct unchecked exception for clean mid-node cancellation without threading a flag through every call frameNode executors (
nodes/)IAutomationNodeExecutor— interface each node type implementsAutomationNodeExecutors— static registry mapping node type → executorDatabaseEngineNodeExecutor,ModelEngineNodeExecutor,VectorEngineNodeExecutor,StorageEngineNodeExecutor,FunctionEngineNodeExecutor,AppEngineNodeExecutor,WaitNodeExecutorPixel reactors
TriggerAutomationReactor— kicks off an async run via a bounded thread pool (2–20 threads); returns{runId}immediately
RunAutomationNodeReactor— executes a single named node in isolation (used for the FE's per-step test/preview)GetAutomationReactor— readsautomation.jsonfrom project storageSaveAutomationReactor— writesautomation.jsonto project storageGetAutomationConfigReactor— readsautomation-config.json(SMSS key-value config) with sensitive values maskedSaveAutomationConfigReactor— writesautomation-config.jsonwith sensitive flag supportGetAutomationRunReactor— returns a single run's summary + per-node resultsListAutomationRunsReactor— returns paginated run history for a projectCancelAutomationRunReactor— sets the cancel flag; the running thread detects it and terminates cleanlyPixelExecutionUtils— shared helper for executing a Pixel string inside a project insight with a timeoutStartup integration
SMSSWebWatcher— callsAutomationDatabaseUtility.initialize()on server start to create tables and sweep anyRUNNING rows left from a previous crash to INTERRUPTED
SchedulerOwlCreator— registers all new reactorsHow to Test
AutomationDatabaseUtility.initialize()in the logsrunIdimmediately and pollsGetAutomationRununtil the run reachesSUCCESS or FAILED
AUTOMATION_RUNSandAUTOMATION_NODE_OUTPUTSwith correct statuses and durationsNotes
AUTOMATION_ACTIVE_RUNtable enforces one concurrent run per project via a PK onPROJECT_ID; attempting to trigger a second run while one is active returns an errorAUTOMATION_NODE_OUTPUTS; the***mask is applied at read time inGetAutomationConfigReactor