Migrating existing cases after a process change experimental
Use this procedure when a developer modifies a Petriflow process, imports its new version, and needs to apply the changes to cases created from the original version.
Upgrading Netgrif Platform?
If you are upgrading, for example, from Netgrif Platform 1.0.0 to 1.0.1, use the release migration catalog. This page covers a different task: updating existing cases after changing your own Petriflow process.
For specific code covering case data, roles, task forms, workflow state, files, and Elasticsearch, see the recipes by change type.
Before production
First create a restorable backup and run the migration on an up-to-date copy of production data. A migration script may modify MongoDB, Elasticsearch, and storage, and these systems do not share a single transaction.
1. Document the differences between process versions
Compare both process versions and record only the changes that may affect existing cases. For each change, it must be immediately clear what the migration script should do.
| What changed | Original version | New version | What to do with existing cases |
|---|---|---|---|
| Case data | 10 fields do not exist | 10 fields were added | add them to case.dataSet and calculate the business values |
| Field type | amount is text | amount is number | convert stored values and handle invalid input |
| Removed field | legacyCode exists | legacyCode does not exist | decide whether to remove, archive, or retain the value |
| Role | the reviewer role does not exist | the reviewer role exists | rebuild case/task permissions; handle user assignments separately |
| Task form | address uses input | address uses textarea | update component maps and behavior for the affected transitions |
| Workflow state | the token is in p_wait | the place is named p_review | explicitly map activePlaces and rebuild tasks if necessary |
| Search | customerName is not searchable | customerName is searchable | reindex Elasticsearch after changing the MongoDB data |
If a change does not affect the stored data of existing cases, no migration step is needed for it. However, importing the new version alone does not automatically update the existing case.dataSet, activePlaces, tasks, or index.
Before running the migration, also record the exact processIdentifier, the source version and its ObjectId, the target version and its ObjectId, and the number of cases you will migrate. Do not bind the script to ambiguous labels such as latest.
2. Define the order of operations for a single case migration
For each existing case, perform only the required steps, always in this order:
- Verify that the case belongs to the exact source version.
- Migrate the values and runtime dataset.
- Synchronize form metadata, roles, and permissions.
- Map
activePlacesif places or the workflow state changed. - If you are moving the case to the target version, call
migratePetriNet(useCase, targetNet). - If task executability changes, call
reloadTasks(useCase, targetNet). - After the MongoDB phase succeeds, reindex Elasticsearch separately and verify the result.
migratePetriNet and reloadTasks are two separate operations. The first changes only the stored petriNetObjectId. The second sets the runtime PetriNet itself, so the case does not have to point to the target version beforehand. If both operations are performed in one cursor update, change petriNetObjectId before reloading, because reloading may save the case while changing its tasks.
3. Use the script template
In Admin, open Cluster → Migrations (/cluster/migrations) and run the script on the worker that owns the process. Do not specify the target as “newest”- use the exact MongoDB ObjectId of the PetriNet.
def processIdentifier = "process_a"
def sourceNetId = new org.bson.types.ObjectId("SOURCE_PETRINET_OBJECT_ID")
def targetNetId = new org.bson.types.ObjectId("TARGET_PETRINET_OBJECT_ID")
def targetNet = petriNetService.get(targetNetId)
if (targetNet == null || targetNet.identifier != processIdentifier) {
throw new IllegalStateException("Incorrect target PetriNet")
}
migrationHelper.clearErrors()
migrationHelper.withErrorPolicy(
com.netgrif.application.engine.migration.model.MigrationErrorPolicy.continueOnError()
) {
migrationHelper.updateCasesCursor({ useCase ->
cancellable()
if (useCase.petriNetObjectId != sourceNetId) {
return
}
// 1. Add only recipes for the identified differences here.
// 2. If you are moving the case to the target version:
migrationHelper.migratePetriNet(useCase, targetNet)
// 3. Uncomment only when the workflow or tasks have changed:
// migrationHelper.reloadTasks(useCase, targetNet)
}, sourceNetId, 100)
}
if (migrationHelper.hasErrors()) {
def errors = migrationHelper.popErrors()
errors.each { error -> log.error("Migration error: {}", error) }
throw new IllegalStateException("Migration finished with ${errors.size()} errors")
}
return "OK: ${processIdentifier} ${sourceNetId} -> ${targetNetId}"Using sourceNetId in both the cursor filter and the precondition protects against accidentally migrating cases from another version.
4. Verify the result before resuming operations
- the expected number of cases matches the actual number;
- no case was left partially migrated between v1 and v2;
- a representative case opens in every affected workflow state;
- values, forms, permissions, assigned tasks, and unassigned tasks work correctly;
- FILE/FILELIST content is available in storage;
- cases and tasks are searchable in Elasticsearch;
- rerunning the verification script reports zero remaining changes.
Admin operational options, the helper API, error policies, and startup mechanisms are described separately in the Migration execution and Engine API reference. Ready-made release scripts and their exact change lists belong in the release migration catalog.
