π§ Overview
When upgrading an app in Joget from one version to another, existing running workflow instances might still be tied to the old version.
This BeanShell script automates the migration of running workflow processes from a previous app version (from_version) to a new version (to_version), ensuring smooth continuity without manually aborting or recreating processes.
βοΈ How It Works
Hereβs how this BeanShell script operates inside a Form Store Binder:
- βοΈ Fetches the submitted data (App ID, From Version, To Version, Process ID).
- π Retrieves all running workflow processes for the specified
app_idandfrom_version. - π¦ Loads all available process definitions from the new version (
to_version). - π Compares process definitions between olβ¦
π§ Overview
When upgrading an app in Joget from one version to another, existing running workflow instances might still be tied to the old version.
This BeanShell script automates the migration of running workflow processes from a previous app version (from_version) to a new version (to_version), ensuring smooth continuity without manually aborting or recreating processes.
βοΈ How It Works
Hereβs how this BeanShell script operates inside a Form Store Binder:
-
βοΈ Fetches the submitted data (App ID, From Version, To Version, Process ID).
-
π Retrieves all running workflow processes for the specified
app_idandfrom_version. -
π¦ Loads all available process definitions from the new version (
to_version). -
π Compares process definitions between old and new versions.
-
π§© For matching process definitions:
-
Migrates the workflow instance to the new version.
-
π« For unmatched processes:
-
Aborts them safely to maintain workflow consistency.
-
π§Ύ Logs every action and updates a
num_migratedfield with the total migrated count. -
πΎ Finally, uses the
WorkflowFormBinderto store the updated record.
π§ Full Code
import java.util.Collection;
import java.util.ArrayList;
import org.joget.commons.util.LogUtil;
import org.joget.apps.form.model.Element;
import org.joget.apps.form.model.FormData;
import org.joget.apps.form.model.FormRow;
import org.joget.apps.form.model.FormRowSet;
import org.joget.apps.form.service.FormUtil;
import org.joget.apps.form.model.FormStoreBinder;
import org.joget.plugin.base.PluginManager;
import org.joget.apps.app.model.AppDefinition;
import org.joget.apps.app.model.PackageDefinition;
import org.joget.apps.app.service.AppUtil;
import org.joget.workflow.model.WorkflowProcess;
import org.joget.workflow.model.service.WorkflowManager;
import org.joget.workflow.util.WorkflowUtil;
public FormRowSet execute(element, rows, formData) {
if (rows != null && !rows.isEmpty()) {
WorkflowManager workflowManager = (WorkflowManager) AppUtil.getApplicationContext().getBean("workflowManager");
FormRow row = rows.get(0);
String appId = row.getProperty("app_id");
String fromVersion = row.getProperty("from_version");
String toVersion = row.getProperty("to_version");
String single_processId = row.getProperty("single_processId");
Collection runningProcessList = workflowManager.getRunningProcessList(appId, null, null, fromVersion, null, null, 0, null);
Collection processes = workflowManager.getProcessList(appId, toVersion);
LogUtil.info("BeanShell", "Updating running processes for " + appId + " from " + fromVersion + " to " + toVersion);
if (!runningProcessList.isEmpty() && !processes.isEmpty()) {
Collection newProcessDefIds = new ArrayList();
for (WorkflowProcess process : processes) {
newProcessDefIds.add(process.getId());
}
for (WorkflowProcess process : runningProcessList) {
String processId = null;
try {
processId = process.getInstanceId();
String processDefId = process.getId();
processDefId = processDefId.replace("#" + fromVersion.toString() + "#", "#" + toVersion.toString() + "#");
if (newProcessDefIds.contains(processDefId)) {
workflowManager.processCopyFromInstanceId(processId, processDefId, true);
LogUtil.info("BeanShell", "Migrated process " + processId + ".");
} else {
workflowManager.processAbort(processId);
LogUtil.info("BeanShell", "Process Def ID " + processDefId + " does not exist. Aborted process " + processId + ".");
}
} catch (Exception e) {
LogUtil.error(getClass().getName(), e, "Error updating process " + processId);
}
}
row.setProperty("num_migrated", runningProcessList.size().toString());
LogUtil.info("BeanShell", "Completed updating running processes for " + appId + " from " + fromVersion + " to " + toVersion);
} else {
LogUtil.info("BeanShell", "No running processes to update for " + appId + " from " + fromVersion + " to " + toVersion);
}
PluginManager pluginManager = (PluginManager) AppUtil.getApplicationContext().getBean("pluginManager");
FormStoreBinder binder = (FormStoreBinder) pluginManager.getPlugin("org.joget.apps.form.lib.WorkflowFormBinder");
binder.store(element, rows, formData);
}
return rows;
}
return execute(element, rows, formData);
`
πΌ Example Use Cases
β Migrating all running workflow instances when deploying a new app version. β Keeping workflow states consistent between staging and production environments. β Automating process transitions during app upgrades or refactoring.
π¨ Customization Tips
π‘ Add filtering logic to migrate only specific processes by ID or status.
βοΈ Log more detailed migration statistics (e.g., per-process-type counts).
π Replace the manual from_version / to_version fields with auto-detected app versions using AppDefinition.
π Key Benefits
β Saves hours of manual migration work. β‘ Ensures version continuity without breaking process logic. π§© Automatically cleans up invalid or outdated process instances. π Prevents version mismatches during deployment.
π Security Note
β οΈ Always validate the app_id and version inputs to prevent accidental cross-app migrations.
β οΈ Avoid exposing internal process IDs in UI or logs in production.
β οΈ Consider restricting this scriptβs execution to admin roles only.
π§© Final Thoughts
This script provides a robust way to keep your workflow processes synchronized between Joget app versions β perfect for continuous delivery environments or versioned deployments.