DEV Community

Explorer
Explorer

Posted on

πŸ”„ Process Migration Between App Versions in Joget

🧭 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_id and from_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_migrated field with the total migrated count.
  • πŸ’Ύ Finally, uses the WorkflowFormBinder to 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);
Enter fullscreen mode Exit fullscreen mode


`


πŸ’Ό 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.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.