Skip to content

Include process links when exporting and importing a system process - #869

Open
maarten-ritense wants to merge 6 commits into
next-minorfrom
story/481-system-process-export-include-process-links
Open

Include process links when exporting and importing a system process#869
maarten-ritense wants to merge 6 commits into
next-minorfrom
story/481-system-process-export-include-process-links

Conversation

@maarten-ritense

@maarten-ritense maarten-ritense commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Describe the changes

Link to the related Github issue: generiekzaakafhandelcomponent/gzac-issues#481

A process that is not linked to a case definition (the processes on /processes) could only be moved between environments as a bare BPMN file, so its process links had to be reconnected by hand on every next environment. This adds an export that includes the process links, and an import for that package.

The import half already existed and was simply unreachable: GlobalProcessDefinitionImporter, GlobalProcessLinkImporter and ValtimoImportService.importGlobal already consume config/global/bpmn/*.bpmn and config/global/process-link/*.process-link.json — that is how the dev app deploys document-upload and its plugin links at startup. What was missing is the export side (no exporter wrote into config/global), the REST endpoints and the UI.

Specify the code branch location: story/481-system-process-export-include-process-linksnext-minor

Relevant comments:

Deliberate scope. The package contains the process and its process links, nothing else. Called sub-processes, decision tables, forms and form flow definitions are not included, because they can be shared with other processes and including them would widen what an import overwrites. Instead the import reports which of those are missing on the target environment, so it is clear what still has to be imported separately. Only statically determinable references are reported (a literal calledElement / operatonDecisionRef, and form / form flow process links) — expression based references cannot be resolved up front.

Forms and form flows cannot fail silently, they fail the whole import. FormProcessLinkMapper.resolveFormDefinition throws when the form does not exist and ProcessLinkImporter is @Transactional, so a missing form rolls the import back. Such a package is therefore refused before importing, naming the form, instead of leaving a half-imported process behind. Form flow process links can never be created for a process outside a case definition (assertFormFlowDefinitionExists requires a blueprint id), so those are reported as unsupported.

⚠ Behaviour change: config/global process links are now leading. The imported file is the complete set of process links for a process, so links that are not in the file are removed. This applies to importing a package and to the startup autodeployment of config/global/process-link/*.json. On an environment where a link was added through the interface on top of a process that also ships a config/global process-link file, that link disappears on the next restart. This was chosen deliberately (configuration stays authoritative, environments cannot drift) and is described in the release notes.

Two entry points, two policies. Importing over a process that exists as a system process which may not be updated (isSystemProcess + valtimo.process.systemProcessUpdatable=false) is refused. That guard lives in the REST layer, and GlobalProcessDefinitionImporter deliberately keeps skipIsDeployableCheck = true so startup autodeployment retains its authority over its own system processes. Moving the check into the importer would break that.

Along the way, two defects in the existing upload were fixed: the plain Export produced diagram.bpmn regardless of which process it was, and the accept list used invalid tokens (bpmn, zip), which meant no filtering in the file dialog and Carbon's onDrop silently discarding every dropped file — it matches on the dot-prefixed extension. Now .bpmn / .zip.

Export manifest. next-minor gained an export manifest after this branch started (b9eb8e60c4), and a system process export now produces one too: ArtifactType.PROCESS_DEFINITION, with the plugins of its process links as dependencies. A bpmn has no field a manifest can $ref into, so both values are literals: the title is the process name (falling back to the key) and the version is the operaton:versionTag of the model, which is stable across environments, falling back to the version of the deployment, which is not. A version tag that encodes a case or building block definition is ignored, so that encoding never leaks into a manifest.

This needed a 2-line change to ValtimoExportService: it collected manifestDependencies only from non-root exporters, which assumed one exporter per root request. That assumption holds for the existing roots (CaseDefinitionExporter, BuildingBlockDefinitionExporter are the only exporters of their requests), but not here — the bpmn and the process links are exported by two exporters of the same request, so the plugin dependencies were dropped. Dependencies are now collected from any result that does not itself contribute the root artifact, which is the actual reason for the original carve-out: a producer's manifestDependencies describes itself, for when it is pulled in as a dependency. No behaviour change for existing exports, and should ignore manifest dependencies contributed by the root request still passes; the new test fails without the change.

Note for reviewers testing manually: the preview response gained existingProcessDefinitionKeys, so a backend that predates this branch silently skips the replace confirmation. Restart the backend before testing.

The plugin-configuration mapping UI is a new standalone component in @valtimo/plugin. case-management-upload still has its own copy; deduplicating it is a follow-up rather than part of this change.

Breaking changes

  • The contribution only contains changes that are not breaking.

Documentation

  • Release notes have been written for these changes.

New features or changes that have been introduced have been documented.

  • Yes

Tests

Unit tests have been added that cover these changes

  • Yes

Integration tests have been added that cover these changes

  • Yes

Describe the testing steps

  • Export with process links on /processes/document-upload downloads <key>_v<version>_<timestamp>.process.zip containing only config/global/bpmn/document-upload.bpmn, config/global/process-link/document-upload.process-link.json and valtimo-manifest.json, and the process-link JSON matches the checked-in dev-app file
  • That valtimo-manifest.json names the process (Documenten API upload document, type PROCESS_DEFINITION) and lists documentenapi and zakenapi as PLUGIN dependencies. valtimoVersion is empty when running from bootRun: DefaultValtimoVersionResolver reads it from the JAR manifest, which is absent outside a packaged build
  • Importing that package on an environment where the process does not exist deploys it and creates both process links, pointing at the mapped plugin configurations (verified through the API response)
  • The plugin mapping step lists both plugin configurations and Upload completes the import
  • Importing over a protected system process is refused with "This process cannot be overwritten because it is managed by the application configuration", with Upload disabled
  • Back from the review step keeps the selected file and a usable Upload button
  • Uploading a bare .bpmn behaves exactly as before
  • Importing over an existing process asks to replace first — covered by unit and frontend specs, not yet confirmed in the browser because the running backend predated the new preview field (needs a backend restart)
  • The file dialog filters on .bpmn / .zip in Firefox and Safari — cannot be automated, setInputFiles bypasses the dialog

Security

The Secure by Design principle has been applied to these changes

  • Yes

The import refuses a package before applying any of it when it would fail or when it would overwrite a process that is managed by configuration, so no partially imported state can result. The preview and the import both derive their decisions from the archive contents server-side; nothing from the client decides what may be replaced.

Added or changed REST API endpoints have authentication and authorization in place

  • Yes

All three endpoints are restricted to ADMIN in ProcessLinkHttpSecurityConfigurer. They are covered by ProcessLinkSecuritySmokeIntegrationTest.

Valtimo access control checks have been implemented

  • Yes
  • Not applicable

These are ADMIN-only management endpoints that run under runWithoutAuthorization, exactly like the existing case-definition export and import. There is no per-resource permission model for process definitions to check against.

Dependencies

Newly added dependencies do not introduce known vulnerabilities/CVE's and are in line with the Valtimo license

  • Yes
  • Not applicable

No dependencies were added or changed.

…481)

A process that is not linked to a case definition can now be exported together
with its process links and imported on another environment, so changes tested on
one environment no longer have to be reconnected by hand elsewhere.

During the import, plugin links can be pointed at the plugin configurations of
the target environment, and a summary reports which referenced forms, form flows,
decision tables and called sub-processes are missing here. Those are exported and
imported separately, because they can be shared with other processes.

Process links supplied through config/global are now leading: a link that is not
in the file is removed. Importing over a system process that may not be updated
is refused, and importing over an existing process asks for confirmation first,
like uploading a single bpmn file already did. That plain export now names the
file after the process instead of diagram.bpmn.
@maarten-ritense
maarten-ritense requested review from a team as code owners July 30, 2026 14:55
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@maarten-ritense, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6a1cf1f8-43ef-45f7-8531-572260383fe0

📥 Commits

Reviewing files that changed from the base of the PR and between 38ea059 and 49863f0.

📒 Files selected for processing (6)
  • documentation/release-notes/13.x.x/13.41.0/README.md
  • frontend/projects/valtimo/plugin/src/lib/components/plugin-configuration-mapping/plugin-configuration-mapping.component.ts
  • frontend/projects/valtimo/plugin/src/lib/models/index.ts
  • frontend/projects/valtimo/plugin/src/lib/models/plugin-configuration-mapping.ts
  • frontend/projects/valtimo/shared/assets/core/en.json
  • frontend/projects/valtimo/shared/assets/core/nl.json
📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Export complete process packages, including BPMN and process links, as ZIP files.
    • Preview imports with existing processes, missing references, and plugin configuration mappings.
    • Review and map plugin configurations before importing.
    • Synchronize process links authoritatively during package import.
  • Bug Fixes
    • Improved exported filenames and supported BPMN and ZIP file filtering.
    • Added clearer validation for missing or unsupported references.
  • Documentation
    • Clarified system process read-only and update behavior.

Walkthrough

This change adds global process-definition and process-link export support, ZIP import previewing, missing-reference detection, plugin-configuration mapping, and authoritative process-link imports. Backend REST endpoints expose export, preview, and import operations with authorization rules. The frontend adds process-package upload steps, replacement handling, missing-reference summaries, plugin mapping controls, download behavior, service APIs, translations, tests, and documentation updates.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch story/481-system-process-export-include-process-links

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
frontend/projects/valtimo/plugin/src/lib/components/plugin-configuration-mapping/plugin-configuration-mapping.component.html (1)

43-51: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hide decorative SVG from assistive tech.

The arrow icon is purely decorative (visual separator between source/target). Add aria-hidden="true" so screen readers skip it.

♿ Proposed fix
       <svg
         class="plugin-configuration-mapping__arrow"
         height="16"
         viewBox="0 0 32 32"
         width="16"
+        aria-hidden="true"
         xmlns="http://www.w3.org/2000/svg"
       >
frontend/projects/valtimo/process-management/src/lib/components/process-management-upload/process-management-upload.component.ts (1)

279-283: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unnecessary Blob re-wrap.

file is already a Blob; wrapping it in new Blob([file], {type: file.type}) copies the bytes for no benefit.

♻️ Proposed simplification
   private toFormData(file: File): FormData {
     const formData = new FormData();
-    formData.append('file', new Blob([file], {type: file.type}), file.name);
+    formData.append('file', file, file.name);
     return formData;
   }
backend/form/src/main/kotlin/com/ritense/form/mapper/FormProcessLinkMapper.kt (1)

176-193: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate form-lookup logic with resolveFormDefinition.

The blueprintId-conditional getFormDefinitionByName lookup here duplicates the same branching already in resolveFormDefinition (Lines 194-199). Extract a shared helper to avoid the two diverging if they're changed independently.

♻️ Proposed refactor
+    private fun findFormDefinition(formName: String, blueprintId: BlueprintId?): java.util.Optional<FormIoFormDefinition> =
+        if (blueprintId != null) {
+            formDefinitionService.getFormDefinitionByName(formName, blueprintId)
+        } else {
+            formDefinitionService.getFormDefinitionByName(formName)
+        }
+
     override fun getMissingReference(deployDto: ProcessLinkDeployDto, blueprintId: BlueprintId?): MissingReferenceDto? {
         deployDto as FormProcessLinkDeployDto
-        val formDefinition = if (blueprintId != null) {
-            formDefinitionService.getFormDefinitionByName(deployDto.formDefinitionName, blueprintId)
-        } else {
-            formDefinitionService.getFormDefinitionByName(deployDto.formDefinitionName)
-        }
-        return if (formDefinition.isPresent) {
+        return if (findFormDefinition(deployDto.formDefinitionName, blueprintId).isPresent) {
             null
         } else {
             MissingReferenceDto(
                 type = MissingReferenceType.FORM,
                 reference = deployDto.formDefinitionName,
                 activityId = deployDto.activityId,
             )
         }
     }

     private fun resolveFormDefinition(formName: String, blueprintId: BlueprintId?): FormIoFormDefinition {
-        val result = if (blueprintId != null) {
-            formDefinitionService.getFormDefinitionByName(formName, blueprintId)
-        } else {
-            formDefinitionService.getFormDefinitionByName(formName)
-        }
-        `@Suppress`("UNCHECKED_CAST")
-        return (result as java.util.Optional<FormIoFormDefinition>).orElseThrow {
+        return findFormDefinition(formName, blueprintId).orElseThrow {
             IllegalStateException("Form definition $formName not found")
         }
     }
backend/process-link/src/main/kotlin/com/ritense/processlink/service/ProcessDefinitionImportPreviewService.kt (1)

213-235: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Preserve the original exception as cause.

ImportServiceException("Archive could not be read: ${e.message}") discards the original stack trace, matching the detekt SwallowedException hint. Since ImportServiceException only takes a message, use initCause to retain diagnosability for production import failures.

♻️ Proposed fix
         } catch (e: Exception) {
-            throw ImportServiceException("Archive could not be read: ${e.message}")
+            throw ImportServiceException("Archive could not be read: ${e.message}").apply { initCause(e) }
         }

Source: Linters/SAST tools


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9c75a645-46f1-4b95-93ce-60d3493840db

📥 Commits

Reviewing files that changed from the base of the PR and between f7e21ce and 92bacb6.

⛔ Files ignored due to path filters (6)
  • documentation/.gitbook/assets/read-only-system-process.png is excluded by !**/*.png
  • documentation/.gitbook/assets/setting-system-process-property.png is excluded by !**/*.png
  • documentation/.gitbook/assets/system-process.png is excluded by !**/*.png
  • documentation/using-valtimo/process/img/read-only-system-process.png is excluded by !**/*.png
  • documentation/using-valtimo/process/img/setting-system-process-property.png is excluded by !**/*.png
  • documentation/using-valtimo/process/img/system-process.png is excluded by !**/*.png
📒 Files selected for processing (46)
  • backend/core/src/main/kotlin/com/ritense/valtimo/autoconfiguration/ExportAutoConfiguration.kt
  • backend/core/src/main/kotlin/com/ritense/valtimo/exporter/GlobalProcessDefinitionExporter.kt
  • backend/exporter/src/main/kotlin/com/ritense/exporter/request/GlobalProcessDefinitionExportRequest.kt
  • backend/form-flow/src/main/kotlin/com/ritense/formflow/mapper/FormFlowProcessLinkMapper.kt
  • backend/form/src/main/kotlin/com/ritense/form/mapper/FormProcessLinkMapper.kt
  • backend/form/src/test/kotlin/com/ritense/form/mapper/FormProcessLinkMapperTest.kt
  • backend/importer/src/main/kotlin/com/ritense/importer/ImportService.kt
  • backend/importer/src/main/kotlin/com/ritense/importer/ValtimoImportService.kt
  • backend/process-link/src/main/kotlin/com/ritense/processlink/configuration/ProcessLinkAutoConfiguration.kt
  • backend/process-link/src/main/kotlin/com/ritense/processlink/exporter/GlobalProcessLinkExporter.kt
  • backend/process-link/src/main/kotlin/com/ritense/processlink/importer/GlobalProcessLinkImporter.kt
  • backend/process-link/src/main/kotlin/com/ritense/processlink/importer/ProcessLinkImporter.kt
  • backend/process-link/src/main/kotlin/com/ritense/processlink/mapper/ProcessLinkMapper.kt
  • backend/process-link/src/main/kotlin/com/ritense/processlink/security/config/ProcessLinkHttpSecurityConfigurer.kt
  • backend/process-link/src/main/kotlin/com/ritense/processlink/service/ProcessDefinitionImportPreviewService.kt
  • backend/process-link/src/main/kotlin/com/ritense/processlink/web/rest/ProcessLinkResource.kt
  • backend/process-link/src/main/kotlin/com/ritense/processlink/web/rest/dto/MissingReferenceDto.kt
  • backend/process-link/src/main/kotlin/com/ritense/processlink/web/rest/dto/ProcessDefinitionImportPreviewResponseDto.kt
  • backend/process-link/src/main/kotlin/com/ritense/processlink/web/rest/dto/ProcessDefinitionImportResponseDto.kt
  • backend/process-link/src/test/kotlin/com/ritense/processlink/exporter/GlobalProcessLinkExporterIntTest.kt
  • backend/process-link/src/test/kotlin/com/ritense/processlink/exporter/GlobalProcessLinkExporterTest.kt
  • backend/process-link/src/test/kotlin/com/ritense/processlink/importer/GlobalProcessDefinitionRoundTripIntTest.kt
  • backend/process-link/src/test/kotlin/com/ritense/processlink/importer/GlobalProcessLinkImporterIntTest.kt
  • backend/process-link/src/test/kotlin/com/ritense/processlink/importer/GlobalProcessLinkImporterTest.kt
  • backend/process-link/src/test/kotlin/com/ritense/processlink/service/ProcessDefinitionImportPreviewServiceIntTest.kt
  • backend/process-link/src/test/kotlin/com/ritense/processlink/service/ProcessDefinitionImportPreviewServiceTest.kt
  • backend/process-link/src/test/kotlin/com/ritense/processlink/web/rest/ProcessLinkResourceTest.kt
  • documentation/features/process/systemprocesses.md
  • documentation/release-notes/13.x.x/13.40.0/README.md
  • frontend/projects/valtimo/plugin/src/lib/components/plugin-configuration-mapping/plugin-configuration-mapping.component.html
  • frontend/projects/valtimo/plugin/src/lib/components/plugin-configuration-mapping/plugin-configuration-mapping.component.scss
  • frontend/projects/valtimo/plugin/src/lib/components/plugin-configuration-mapping/plugin-configuration-mapping.component.spec.ts
  • frontend/projects/valtimo/plugin/src/lib/components/plugin-configuration-mapping/plugin-configuration-mapping.component.ts
  • frontend/projects/valtimo/plugin/src/public-api.ts
  • frontend/projects/valtimo/process-management/src/lib/components/process-management-builder/process-management-builder.component.html
  • frontend/projects/valtimo/process-management/src/lib/components/process-management-builder/process-management-builder.component.ts
  • frontend/projects/valtimo/process-management/src/lib/components/process-management-upload/process-management-upload.component.html
  • frontend/projects/valtimo/process-management/src/lib/components/process-management-upload/process-management-upload.component.scss
  • frontend/projects/valtimo/process-management/src/lib/components/process-management-upload/process-management-upload.component.spec.ts
  • frontend/projects/valtimo/process-management/src/lib/components/process-management-upload/process-management-upload.component.ts
  • frontend/projects/valtimo/process-management/src/lib/constants/process-management.test-ids.ts
  • frontend/projects/valtimo/process-management/src/lib/models/index.ts
  • frontend/projects/valtimo/process-management/src/lib/models/process-definition-import.model.ts
  • frontend/projects/valtimo/process-management/src/lib/services/process-management.service.ts
  • frontend/projects/valtimo/shared/assets/core/en.json
  • frontend/projects/valtimo/shared/assets/core/nl.json

Comment on lines +134 to +170
this.pluginManagementService
.getPluginDefinitions()
.pipe(take(1))
.subscribe(definitions => {
this.loadConfigurations(mappableConfigurations, new Set(definitions.map(({key}) => key)));
});
}

private loadConfigurations(
configurations: PluginConfigurationPreview[],
installedKeys: Set<string>
): void {
const installableKeys = [
...new Set(configurations.map(({pluginDefinitionKey}) => pluginDefinitionKey)),
].filter(key => !!key && installedKeys.has(key));

if (installableKeys.length === 0) {
this.buildRows(configurations, new Map(), installedKeys);
return;
}

const requests = installableKeys.reduce(
(accumulator, key) => ({
...accumulator,
[key]: this.pluginManagementService
.getPluginConfigurationsByPluginDefinitionKey(key)
.pipe(take(1)),
}),
{} as Record<string, Observable<PluginConfiguration[]>>
);

forkJoin(requests)
.pipe(take(1))
.subscribe(results => {
this.buildRows(configurations, new Map(Object.entries(results)), installedKeys);
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unhandled errors on plugin-lookup calls silently break the mapping step.

getPluginDefinitions() and getPluginConfigurationsByPluginDefinitionKey() have no error handler. If either call fails, rows$ stays empty and the user is never told; the import can then proceed with an incomplete plugin mapping with no warning.

🔧 Proposed fix
     this.pluginManagementService
       .getPluginDefinitions()
       .pipe(take(1))
-      .subscribe(definitions => {
-        this.loadConfigurations(mappableConfigurations, new Set(definitions.map(({key}) => key)));
-      });
+      .subscribe({
+        next: definitions => {
+          this.loadConfigurations(mappableConfigurations, new Set(definitions.map(({key}) => key)));
+        },
+        error: () => this.rows$.next([]),
+      });

Similarly add an error callback to the forkJoin(requests) subscription in loadConfigurations(), and surface a notification/error state to the user.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.pluginManagementService
.getPluginDefinitions()
.pipe(take(1))
.subscribe(definitions => {
this.loadConfigurations(mappableConfigurations, new Set(definitions.map(({key}) => key)));
});
}
private loadConfigurations(
configurations: PluginConfigurationPreview[],
installedKeys: Set<string>
): void {
const installableKeys = [
...new Set(configurations.map(({pluginDefinitionKey}) => pluginDefinitionKey)),
].filter(key => !!key && installedKeys.has(key));
if (installableKeys.length === 0) {
this.buildRows(configurations, new Map(), installedKeys);
return;
}
const requests = installableKeys.reduce(
(accumulator, key) => ({
...accumulator,
[key]: this.pluginManagementService
.getPluginConfigurationsByPluginDefinitionKey(key)
.pipe(take(1)),
}),
{} as Record<string, Observable<PluginConfiguration[]>>
);
forkJoin(requests)
.pipe(take(1))
.subscribe(results => {
this.buildRows(configurations, new Map(Object.entries(results)), installedKeys);
});
}
this.pluginManagementService
.getPluginDefinitions()
.pipe(take(1))
.subscribe({
next: definitions => {
this.loadConfigurations(mappableConfigurations, new Set(definitions.map(({key}) => key)));
},
error: () => this.rows$.next([]),
});
}
private loadConfigurations(
configurations: PluginConfigurationPreview[],
installedKeys: Set<string>
): void {
const installableKeys = [
...new Set(configurations.map(({pluginDefinitionKey}) => pluginDefinitionKey)),
].filter(key => !!key && installedKeys.has(key));
if (installableKeys.length === 0) {
this.buildRows(configurations, new Map(), installedKeys);
return;
}
const requests = installableKeys.reduce(
(accumulator, key) => ({
...accumulator,
[key]: this.pluginManagementService
.getPluginConfigurationsByPluginDefinitionKey(key)
.pipe(take(1)),
}),
{} as Record<string, Observable<PluginConfiguration[]>>
);
forkJoin(requests)
.pipe(take(1))
.subscribe(results => {
this.buildRows(configurations, new Map(Object.entries(results)), installedKeys);
});
}

Always export a process link file, also when the process has no process links: an
importer only removes the process links of a process it receives a file for, so a
process without links left the process links of the target environment in place.

Forget the previewed package when another file is uploaded afterwards. Confirming
the replacement of a bpmn file that was selected after previewing a package
imported that package instead.

Return a bad request instead of failing when the plugin configuration mappings
cannot be read, keep the cause of an unreadable archive, hide the decorative arrow
of the plugin mapping from assistive technology and stop rewrapping the uploaded
file in a blob.
An exported system process now describes itself in a valtimo-manifest.json,
like an exported case definition and building block already did. The process
is the artifact of the export, of the new type PROCESS_DEFINITION, and the
plugins of its process links are its dependencies.

A bpmn file has no field a manifest can reference, so the title and the
version of the artifact are written as literal values. The version tag of the
model is preferred over the version of the deployment, which differs per
environment. A version tag that encodes a case or building block definition is
ignored, so that encoding does not leak into a manifest.

The export service collected the manifest dependencies of non-root exporters
only, which assumes a single exporter per root request. The bpmn and the
process links of a process are exported by two exporters of the same request,
so the plugins of the process links were dropped. Dependencies are now
collected from every result that does not itself contribute the artifact,
which is the reason for the original exception: the dependencies of the
exporter of the artifact describe that artifact, for when it is pulled in as a
dependency of another export.
The types declared in plugin-configuration-mapping.component.ts now live in
plugin/src/lib/models/plugin-configuration-mapping.ts, which makes the
component's input type part of the @valtimo/plugin public surface.
/**
* Exports the BPMN of a process definition that is not part of a case definition.
*
* Unlike [ProcessDefinitionExporter] this exporter does not create related export requests for

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we not want to export called subprocesses/decision definitions?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed it a bit more internally, we would like to see this included before we accept it into the product. People will get wrong expectations if they export a process definition that references other process or decision definitions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is/was to mimic the existing export of a 'system' process with its process links included. As the existing system process export only exports the bpmn definition and not also its called elements I made the decision for this new export to exclude these as well.

Including these will extend the scope significantly and introduces more complexity if you ask me. It will do a lot more than the existing 'simple' system process export. What should the scope be then, include all called bpmn elements including and referenced form definitions too? What if called elements are used by other bpmn's as well, same question for forms.

Maybe we should discuss this a little more or it could be an iteration of this in the (near) future if there is demand for this.


override fun getMissingReference(deployDto: ProcessLinkDeployDto, blueprintId: BlueprintId?): MissingReferenceDto? {
deployDto as FormFlowProcessLinkDeployDto
val definition = when (blueprintId) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're calling the same method twice, why are you using a when for this?

@sofiaIvarsRitense

Copy link
Copy Markdown
Contributor

Testing finding:
It is not possible to upload a process which was previously exported

Screen.Recording.2026-08-04.at.15.35.03.mov

@maarten-ritense

Copy link
Copy Markdown
Contributor Author

Testing finding: It is not possible to upload a process which was previously exported
Screen.Recording.2026-08-04.at.15.35.03.mov

Yes, that should be the case. Only non 'read-only' system processes can be replaced / updated by an import. A toggle exists (see this page https://docs.valtimo.nl/features/process/systemprocesses#make-system-process-updatable in the docs) to disable this and then also read-only processes can be replaced / updated.

…e-process-links

Resolved a conflict in the release notes: 13.40.0 has been released on
next-minor, so the notes for the system process export and import moved
to the 13.41.0 skeleton.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants