Story/external plugin case widget - #873
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (10)
🚧 Files skipped from review as they are similar to previous changes (10)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdded external-plugin case widgets across the backend and frontend. The change persists widget configuration and bundle metadata, resolves plugin bundles, supports import/export remapping, reports configuration usage, and detects dangling references. The frontend adds widget configuration, dynamic availability, iframe rendering, lifecycle states, translations, and sample case-summary widgets. 🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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. Comment |
|
Bugs
Minor / cosmetic
|
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
backend/external-plugin/src/main/kotlin/com/ritense/externalplugin/service/ExternalPluginConfigurationMappingResolver.kt (1)
130-140: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRecheck widget issues after every widget-tab mutation.
This method is invoked after import, but management REST and JSON editing can persist an unresolvable widget configuration without invoking it. The widget then remains unavailable and the missing-configuration flow does not appear.
After
CaseWidgetServicecreates, updates, or deletes a widget tab, invokerecheckIssuesForCaseDefinitionin the same persistence lifecycle.backend/case/src/main/kotlin/com/ritense/case_/configuration/CaseWidgetAutoConfiguration.kt (1)
202-208: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winCondition the default widget resolver on the SPI interface.
ExternalPluginAutoConfigurationonly skips creatingExternalPluginCaseWidgetResolverImplwhen anotherExternalPluginCaseWidgetResolverImplbean exists. If a supplier defines a differentExternalPluginCaseWidgetResolver, this bean is still created and theOptional<ExternalPluginCaseWidgetResolver>injections inCaseWidgetTabExporterandExternalPluginCaseWidgetDataProviderget multiple Spring candidates. Use@ConditionalOnMissingBean(ExternalPluginCaseWidgetResolver::class)in both auto-configurations.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 89843bee-a57f-4b20-aca2-22023704448a
📒 Files selected for processing (65)
backend/case/src/main/kotlin/com/ritense/case_/configuration/CaseWidgetAutoConfiguration.ktbackend/case/src/main/kotlin/com/ritense/case_/repository/ExternalPluginCaseWidgetRepository.ktbackend/case/src/main/kotlin/com/ritense/case_/rest/dto/ExternalPluginWidgetContentDto.ktbackend/case/src/main/kotlin/com/ritense/case_/service/CaseExternalPluginWidgetService.ktbackend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetTabExporter.ktbackend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetTabImporter.ktbackend/case/src/main/kotlin/com/ritense/case_/service/ExternalPluginCaseWidgetResolver.ktbackend/case/src/main/kotlin/com/ritense/case_/widget/externalplugin/ExternalPluginCaseWidget.ktbackend/case/src/main/kotlin/com/ritense/case_/widget/externalplugin/ExternalPluginCaseWidgetDataProvider.ktbackend/case/src/main/kotlin/com/ritense/case_/widget/externalplugin/ExternalPluginCaseWidgetDto.ktbackend/case/src/main/kotlin/com/ritense/case_/widget/externalplugin/ExternalPluginCaseWidgetMapper.ktbackend/case/src/main/kotlin/com/ritense/case_/widget/externalplugin/ExternalPluginWidgetProperties.ktbackend/case/src/test/kotlin/com/ritense/case_/service/CaseWidgetTabExporterTest.ktbackend/case/src/test/kotlin/com/ritense/case_/service/CaseWidgetTabImporterExternalPluginTest.ktbackend/case/src/test/kotlin/com/ritense/case_/widget/externalplugin/ExternalPluginCaseWidgetDataProviderTest.ktbackend/case/src/test/kotlin/com/ritense/case_/widget/externalplugin/ExternalPluginCaseWidgetMapperTest.ktbackend/core/src/main/resources/config/liquibase/13-32-0/13-32-0-master.xmlbackend/core/src/main/resources/config/liquibase/13-32-0/20260731-add-external-plugin-case-widget.xmlbackend/external-plugin/src/main/kotlin/com/ritense/externalplugin/autoconfigure/ExternalPluginAutoConfiguration.ktbackend/external-plugin/src/main/kotlin/com/ritense/externalplugin/exception/ExternalPluginConfigurationInUseException.ktbackend/external-plugin/src/main/kotlin/com/ritense/externalplugin/exception/ExternalPluginHostInUseException.ktbackend/external-plugin/src/main/kotlin/com/ritense/externalplugin/preview/ExternalPluginImportPreviewContributor.ktbackend/external-plugin/src/main/kotlin/com/ritense/externalplugin/service/ExternalPluginCaseWidgetResolverImpl.ktbackend/external-plugin/src/main/kotlin/com/ritense/externalplugin/service/ExternalPluginConfigurationMappingResolver.ktbackend/external-plugin/src/main/kotlin/com/ritense/externalplugin/service/ExternalPluginHostService.ktbackend/external-plugin/src/main/kotlin/com/ritense/externalplugin/service/ExternalPluginHostUsageResolver.ktbackend/external-plugin/src/test/kotlin/com/ritense/externalplugin/exception/ExternalPluginHostInUseExceptionTest.ktbackend/external-plugin/src/test/kotlin/com/ritense/externalplugin/preview/ExternalPluginImportPreviewContributorTest.ktbackend/external-plugin/src/test/kotlin/com/ritense/externalplugin/service/ExternalPluginCaseWidgetResolverImplTest.ktbackend/external-plugin/src/test/kotlin/com/ritense/externalplugin/service/ExternalPluginConfigurationMappingResolverTest.ktbackend/external-plugin/src/test/kotlin/com/ritense/externalplugin/service/ExternalPluginHostUsageResolverTest.ktbackend/plugin/src/main/kotlin/com/ritense/plugin/web/rest/dto/PluginUsageDto.ktfrontend/projects/valtimo/case-management/src/lib/components/case-management-detail/tabs/case-management-general/components/case-management-missing-plugin-configurations/case-management-missing-plugin-configurations.component.tsfrontend/projects/valtimo/case-management/src/lib/components/case-management-detail/tabs/case-management-tabs/widget-tab/case-management-widget-tab/case-management-widget-tab.component.htmlfrontend/projects/valtimo/case-management/src/lib/components/case-management-detail/tabs/case-management-tabs/widget-tab/case-management-widget-tab/case-management-widget-tab.component.tsfrontend/projects/valtimo/case-management/src/lib/services/tab.service.tsfrontend/projects/valtimo/case/src/lib/components/case-detail/tab/widgets/components/external-plugin/case-widget-external-plugin.component.htmlfrontend/projects/valtimo/case/src/lib/components/case-detail/tab/widgets/components/external-plugin/case-widget-external-plugin.component.scssfrontend/projects/valtimo/case/src/lib/components/case-detail/tab/widgets/components/external-plugin/case-widget-external-plugin.component.tsfrontend/projects/valtimo/case/src/lib/components/case-detail/tab/widgets/widgets.component.tsfrontend/projects/valtimo/case/src/lib/models/external-plugin-widget.model.tsfrontend/projects/valtimo/case/src/lib/models/index.tsfrontend/projects/valtimo/layout/src/lib/components/widget-management/management-content/external-plugin/widget-management-external-plugin.component.htmlfrontend/projects/valtimo/layout/src/lib/components/widget-management/management-content/external-plugin/widget-management-external-plugin.component.scssfrontend/projects/valtimo/layout/src/lib/components/widget-management/management-content/external-plugin/widget-management-external-plugin.component.tsfrontend/projects/valtimo/layout/src/lib/components/widget-management/management-content/index.tsfrontend/projects/valtimo/layout/src/lib/constants/external-plugin-widget-token.tsfrontend/projects/valtimo/layout/src/lib/constants/index.tsfrontend/projects/valtimo/layout/src/lib/models/widget-content.model.tsfrontend/projects/valtimo/layout/src/lib/models/widget-editor.model.tsfrontend/projects/valtimo/layout/src/lib/models/widget-wizard.model.tsfrontend/projects/valtimo/layout/src/lib/models/widget.model.tsfrontend/projects/valtimo/layout/src/lib/services/widget-wizard.service.tsfrontend/projects/valtimo/plugin-management/src/lib/components/plugin-usage-modal/plugin-usage-modal.component.htmlfrontend/projects/valtimo/plugin-management/src/lib/components/plugin-usage-modal/plugin-usage-modal.component.tsfrontend/projects/valtimo/plugin/src/lib/models/external-plugin.model.tsfrontend/projects/valtimo/plugin/src/lib/services/external-plugin.service.tsfrontend/projects/valtimo/shared/assets/core/en.jsonfrontend/projects/valtimo/shared/assets/core/nl.jsonplugin-host/docs/external-plugin-system-plan.mdplugin-host/sample-plugins/case-summary/frontend/case-widget-metrics.htmlplugin-host/sample-plugins/case-summary/frontend/case-widget-metrics.tsxplugin-host/sample-plugins/case-summary/frontend/case-widget.htmlplugin-host/sample-plugins/case-summary/frontend/case-widget.tsxplugin-host/sample-plugins/case-summary/manifest.json
| /** | ||
| * One external-plugin widget of a case definition: the configuration it references (`null` when it | ||
| * imported dangling) plus the design-time plugin identity that keeps it identifiable in the repair | ||
| * panel. Consumed by the external-plugin dangling-repair resolver. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the dangling-ID description.
An imported dangling widget retains its original configuration UUID. CaseWidgetTabImporter.remapExternalPluginWidgets preserves an unmapped ID so the repair flow can map it later. State that configurationId is null only when no ID is configured.
| /** | ||
| * A case widget is not a process link, so it gets no detection from the process-link importer. | ||
| * Trigger an in-transaction recheck here — for external-plugin widgets this is what raises the | ||
| * configuration issue when a widget references a plugin configuration missing in this environment | ||
| * (mirrors [CaseTabImporter.afterImport]). | ||
| */ | ||
| override fun afterImport(request: ImportRequest) { | ||
| val caseDefinitionId = request.caseDefinitionId ?: return | ||
| pluginConfigurationMappingResolvers.forEach { it.recheckIssuesForCaseDefinition(caseDefinitionId) } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline backend/case/src/main/kotlin/com/ritense/case_/service/CaseExternalPluginWidgetService.kt --items all
rg -n -C 4 'save|update|create|recheckIssuesForCaseDefinition' \
backend/case/src/main/kotlin/com/ritense/case_/service/CaseExternalPluginWidgetService.kt \
backend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetService.ktRepository: valtimo-platform/valtimo
Length of output: 6801
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## Candidate files and usages"
rg -n -C 5 'recheckIssuesForCaseDefinition|CaseExternalPluginWidgetService|CaseWidgetService|CaseTabImporter|ImportRequest|afterImport|pluginConfigurationMappingResolvers' \
backend/case/src/main/kotlin \
|| true
echo
echo "## Service files snippets"
wc -l backend/case/src/main/kotlin/com/ritense/case_/service/CaseExternalPluginWidgetService.kt backend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetService.kt
sed -n '1,180p' backend/case/src/main/kotlin/com/ritense/case_/service/CaseExternalPluginWidgetService.kt
sed -n '1,180p' backend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetService.ktRepository: valtimo-platform/valtimo
Length of output: 50380
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## focused recheck usages"
rg -n -C 5 'recheckIssuesForCaseDefinition' backend/case/src/main/kotlin || true
echo
echo "## focused widget write/resource methods"
sed -n '70,220p' backend/case/src/main/kotlin/com/ritense/case_/rest/CaseWidgetTabResource.kt
sed -n '40,180p' backend/case/src/main/kotlin/com/ritense/case_/rest/CaseWidgetTabManagementResource.kt
sed -n '70,165p' backend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetService.kt
sed -n '50,140p' backend/case/src/main/kotlin/com/ritense/case_/service/CaseExternalPluginWidgetService.kt
echo
echo "## create/update identifiers in widgets and resources"
rg -n -C 3 'createWidget|updateWidget|saveWidgetTab|save\\(|external-plugin|externalPluginConfigurationId|widgetKey|key' \
backend/case/src/main/kotlin/com/ritense/case_/rest backend/case/src/main/kotlin/com/ritense/case_/service || trueRepository: valtimo-platform/valtimo
Length of output: 13578
Trigger the same widget recheck from non-import write paths.
recheckIssuesForCaseDefinition is only called from importer afterImport, but updateWidgetTab and the importer deploy/copy paths can persist external-plugin widgets. Call the resolver recheck for the updated case definition after each non-import widget write, including remapConfiguration.
| override fun validate(caseDefinitionId: CaseDefinitionId) { | ||
| require(properties.configurationId != null) { | ||
| "External-plugin widget '$key' must reference a plugin configuration." | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect normal widget write paths and import-specific handling.
fd 'CaseExternalPluginWidgetService\.kt' backend/case
ast-grep outline backend/case/src/main/kotlin/com/ritense/case_/service/CaseExternalPluginWidgetService.kt --items all
rg -n -C 5 \
'ExternalPluginCaseWidgetDto|ExternalPluginCaseWidgetResolver|configurationId|validate\(|dangling' \
backend/case/src/main/kotlinRepository: valtimo-platform/valtimo
Length of output: 50381
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- candidate files ---\n'
fd -a 'ExternalPluginCaseWidgetDto\.kt|CaseWidgetTabDto\.kt|ExternalPluginCaseWidgetResolver\.kt|CaseWidgetService\.kt|CaseWidgetTabImporter\.kt|CaseWidgetTabExporter\.kt|CaseExternalPluginWidgetService\.kt' backend/case
printf '\n--- DTOs snippet ---\n'
cat -n backend/case/src/main/kotlin/com/ritense/case_/widget/externalplugin/ExternalPluginCaseWidgetDto.kt
printf '\n'
cat -n backend/case/src/main/kotlin/com/ritense/case_/rest/dto/CaseWidgetTabWidgetDto.kt
printf '\n'
cat -n backend/case/src/main/kotlin/com/ritense/case_/rest/dto/CaseWidgetTabDto.kt
printf '\n--- service/import/export snippets ---\n'
sed -n '1,240p' backend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetTabImporter.kt | cat -n
printf '\n--- CaseWidgetService methods relevant to update/create ---\n'
rg -n -A 8 -B 3 'fun .*Widget|updateWidget|createWidget|findById|validate' backend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetService.kt backend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetTabManagementService.ktRepository: valtimo-platform/valtimo
Length of output: 253
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate files ---'
fd 'ExternalPluginCaseWidgetDto\.kt|CaseWidgetTabDto\.kt|ExternalPluginCaseWidgetResolver\.kt|CaseWidgetService\.kt|CaseWidgetTabImporter\.kt|CaseWidgetTabExporter\.kt|CaseExternalPluginWidgetService\.kt' backend/case || true
printf '%s\n' ''
printf '%s\n' '--- ExternalPluginCaseWidgetDto.kt ---'
cat -n backend/case/src/main/kotlin/com/ritense/case_/widget/externalplugin/ExternalPluginCaseWidgetDto.kt
printf '%s\n' ''
printf '%s\n' '--- CaseWidgetTabWidgetDto.kt ---'
cat -n backend/case/src/main/kotlin/com/ritense/case_/rest/dto/CaseWidgetTabWidgetDto.kt
printf '%s\n' ''
printf '%s\n' '--- CaseWidgetTabDto.kt ---'
cat -n backend/case/src/main/kotlin/com/ritense/case_/rest/dto/CaseWidgetTabDto.kt
printf '%s\n' ''
printf '%s\n
' '--- CaseWidgetTabImporter.kt ---'
sed -n '1,240p' backend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetTabImporter.kt | cat -n
printf '%s\n' ''
printf '%s\n' '--- CaseWidgetService.kt relevant methods ---'
rg -n -A 8 -B 3 'fun .*Widget|updateWidget|createWidget|findById|validate' backend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetService.kt backend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetTabManagementService.kt || trueRepository: valtimo-platform/valtimo
Length of output: 35185
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- CaseWidgetTabManagerResource.kt ---'
fd 'CaseWidgetTabManagementResource\.kt' backend/case -x sh -c 'cat -n "$1"' sh {}
printf '%s\n' '--- full updateWidgetTab implementation ---'
sed -n '114,134p' backend/case/src/main/kotlin/com/ritense/case_/service/CaseWidgetService.kt | cat -n
printf '%s\n' '--- CaseDefinitionId.of validation ---'
rg -n -A 8 -B 3 'fun CaseDefinitionId\.of|data class CaseDefinitionId|class CaseDefinitionId|assertCanUpdateCaseDefinition' backend/case backend/valtimo-contract || true
printf '%s\n' '--- mapper for external plugin widgets ---'
fd 'ExternalPluginCaseWidgetMapper\.kt|ExternalPluginWidgetProperties\.kt|ExternalPluginCaseWidget\.kt' backend/case -x sh -c 'printf "\n--- $1 ---\n"; cat -n "$1"' sh {}
printf '%s\n' '--- external plugin configuration issue endpoints/repo/service references ---'
rg -n -A 4 -B 2 'dangling-plugin-configurations|plugin-configuration-mappings|recheckIssues|dangling' backend/valtimo-contract backend/case || trueRepository: valtimo-platform/valtimo
Length of output: 50380
Reject or immediately repair unresolved external-plugin configuration IDs on widget writes.
ExternalPluginCaseWidgetDto.validate only requires a non-null UUID, so /widget-tab updates and JSON-based writes can persist widgets with unresolvable references. Import mapping must still allow unmapped dangling IDs, but normal writes should resolve the referenced configuration or persist a repairable dangling-reference issue immediately.
| "One or more BPMN process links, case tabs or case widgets reference this configuration. " + | ||
| "Remove the references before deleting the configuration.", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
List building-block mappings in both conflict details.
Both deletion guards can return building-block mapping usages. The messages name only process links, case tabs, and case widgets. When a building-block mapping is the only blocker, the detail gives an incorrect reason.
backend/external-plugin/src/main/kotlin/com/ritense/externalplugin/exception/ExternalPluginConfigurationInUseException.kt#L32-L33: Add building-block mappings to the configuration deletion detail.backend/external-plugin/src/main/kotlin/com/ritense/externalplugin/exception/ExternalPluginHostInUseException.kt#L32-L33: Add building-block mappings to the host deletion detail.
📍 Affects 2 files
backend/external-plugin/src/main/kotlin/com/ritense/externalplugin/exception/ExternalPluginConfigurationInUseException.kt#L32-L33(this comment)backend/external-plugin/src/main/kotlin/com/ritense/externalplugin/exception/ExternalPluginHostInUseException.kt#L32-L33
| assertThat(exception.title).isEqualTo("External plugin host is in use") | ||
| assertThat(exception.status).isEqualTo(Status.CONFLICT) | ||
| assertThat(exception.detail).contains("BPMN process links reference") | ||
| assertThat(exception.detail).contains("BPMN process links, case tabs or case widgets reference") |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Describe building-block mapping deletion blockers.
The host usage resolver includes building-block mapping usages. The exception detail still omits them. Update ExternalPluginHostInUseException and this assertion so a 409 identifies all supported reference types.
Proposed test update
-assertThat(exception.detail).contains("BPMN process links, case tabs or case widgets reference")
+assertThat(exception.detail).contains("case widgets or building-block mappings reference")| [label]="'widgetTabManagement.content.externalPlugin.configLabel' | translate" | ||
| [placeholder]="'widgetTabManagement.content.externalPlugin.configPlaceholder' | translate" | ||
| (selected)="onConfigSelected($event)" | ||
| > | ||
| <cds-dropdown-list [cdsLayer]="1"></cds-dropdown-list> | ||
| </cds-combo-box> | ||
|
|
||
| <cds-combo-box | ||
| *ngIf="$showBundleSelect()" | ||
| [cdsLayer]="1" | ||
| [dropUp]="false" | ||
| [items]="$bundleItems()" | ||
| itemValueKey="bundleKey" | ||
| [label]="'widgetTabManagement.content.externalPlugin.bundleLabel' | translate" | ||
| [placeholder]="'widgetTabManagement.content.externalPlugin.bundlePlaceholder' | translate" | ||
| (selected)="onBundleSelected($event)" | ||
| > | ||
| <cds-dropdown-list [cdsLayer]="1"></cds-dropdown-list> | ||
| </cds-combo-box> | ||
| </ng-container> | ||
|
|
||
| <ng-template #pluginUnavailable> | ||
| <p class="valtimo-widget-management-external-plugin__unavailable"> | ||
| {{ 'widgetTabManagement.content.externalPlugin.unavailableMessage' | translate }} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Use the hyphenated external-plugin translation-key segment.
externalPlugin does not match the enum-value translation keys. These labels render raw translation keys when the entries are not found. Replace externalPlugin with external-plugin for the configuration, bundle, and unavailable-message keys.
| ::ng-deep .cds--text-input, | ||
| ::ng-deep .cds--list-box, | ||
| ::ng-deep .cds--list-box__field { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Resolve the Stylelint errors for ::ng-deep.
The current Stylelint configuration reports each ::ng-deep selector as an error. Use a supported scoped styling method, or add a targeted project-approved Stylelint exception.
🧰 Tools
🪛 Stylelint (17.14.0)
[error] 22-22: Unknown pseudo-element selector "::ng-deep" (selector-pseudo-element-no-unknown)
(selector-pseudo-element-no-unknown)
[error] 23-23: Unknown pseudo-element selector "::ng-deep" (selector-pseudo-element-no-unknown)
(selector-pseudo-element-no-unknown)
[error] 24-24: Unknown pseudo-element selector "::ng-deep" (selector-pseudo-element-no-unknown)
(selector-pseudo-element-no-unknown)
Source: Linters/SAST tools
| effect(() => | ||
| this.widgetWizardService.$widgetContentValid.set( | ||
| !!this._$selectedConfigId() && (!this.$showBundleSelect() || !!this._$selectedBundleKey()) | ||
| ) | ||
| ); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Require a resolved configuration before the wizard is valid.
A prefetched configuration ID can remain after getConfigOptions() filters that configuration out. In that state, $showBundleSelect() is false and this effect marks the widget content valid. The user can then save a widget with an unavailable configuration.
Require _$selectedConfig() to exist. Also validate the selected bundle against that configuration before setting $widgetContentValid.
| "GLOBAL": "Global" | ||
| }, | ||
| "caseTab": "Case tab", | ||
| "caseWidget": "Case widget", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Include case widgets in all deletion-usage descriptions.
A configuration or host deletion can be blocked by a case widget. The current UI text lists only process links, case tabs, and building blocks. The system-plan text also describes only two usage payload shapes.
frontend/projects/valtimo/shared/assets/core/en.json#L2381-L2381: Add “case widgets” tohostInUseModal.descriptionandconfigurationInUseModal.description.frontend/projects/valtimo/shared/assets/core/nl.json#L2408-L2408: Add the Dutch case-widget equivalent to both deletion descriptions.plugin-host/docs/external-plugin-system-plan.md#L912-L912: Update the host deletion row andPluginUsageDtodescription to include widget usages andwidgetKey.
📍 Affects 3 files
frontend/projects/valtimo/shared/assets/core/en.json#L2381-L2381(this comment)frontend/projects/valtimo/shared/assets/core/nl.json#L2408-L2408plugin-host/docs/external-plugin-system-plan.md#L912-L912
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Case Summary — Metrics Widget</title> | ||
| <style> | ||
| * { margin: 0; padding: 0; box-sizing: border-box; } | ||
| body { font-family: 'IBM Plex Sans', sans-serif; background: transparent; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div id="root"></div> | ||
| <script src="case-widget-metrics.bundle.js"></script> | ||
| </body> | ||
| </html> |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add the required license header.
Both new HTML files start with a doctype. Add the standard EUPL HTML comment header before it.
plugin-host/sample-plugins/case-summary/frontend/case-widget-metrics.html#L1-L16: Insert the standard HTML license header before<!DOCTYPE html>.plugin-host/sample-plugins/case-summary/frontend/case-widget.html#L1-L16: Insert the standard HTML license header before<!DOCTYPE html>.
As per path instructions, “**/*.{kt,java,ts,html}: Please verify that each file starts with the standard license header.”
📍 Affects 2 files
plugin-host/sample-plugins/case-summary/frontend/case-widget-metrics.html#L1-L16(this comment)plugin-host/sample-plugins/case-summary/frontend/case-widget.html#L1-L16
Source: Path instructions
…into story/external-plugin-case-widget # Conflicts: # frontend/projects/valtimo/case/src/lib/components/case-detail/tab/widgets/widgets.component.ts # frontend/projects/valtimo/layout/src/lib/models/widget-editor.model.ts # frontend/projects/valtimo/layout/src/lib/models/widget-wizard.model.ts # frontend/projects/valtimo/layout/src/lib/models/widget.model.ts # frontend/projects/valtimo/shared/assets/core/en.json # frontend/projects/valtimo/shared/assets/core/nl.json
Fixes: https://github.com/generiekzaakafhandelcomponent/atlas-internal/issues/491
A. Admin config — availability gate & configuration picker
case-widgetbundle (deactivate/remove them), open a WIDGETS tab → "add widget". The"External plugin" type is not offered.
case-summaryconfiguration, reopen"add widget" → "External plugin" now appears in the generic widgets group.
step shows a Plugin configuration combo box listing each activated config exposing a
case-widgetbundle, labelled"<config title> (Case Summary (0.1.0))".case-widgetbundles → a second Widget combo box appears offering "Case Summary Widget" and
"Case Metrics Widget". The step stays invalid (can't proceed) until a bundle is chosen.
one
case-widgetbundle) selecting it auto-resolves the bundle and the second combo box doesnot appear; the step becomes valid immediately.
Re-open the widget (edit) → the config and bundle are pre-selected (prefill) and the title
is retained.
the editor shows the "External plugins unavailable" message instead of the combos. (In a normal
GZAC build this should not occur because of the A1 gate.)
B. Standard widget properties (full parity)
For an external-plugin widget, walk the generic wizard steps and confirm each works:
the case; a condition that resolves true → the widget is shown.
C. Rendering on the case detail page
the plugin bundle inside
<valtimo-external-plugin-iframe>(opaque-origin sandbox, noallow-same-origin).summary-widgetwidget shows the Case Summary card; ametrics-widgetwidget shows the Case Metrics tiles (view count / currency / short doc id).Two widgets pointing at different bundles of the same config render differently side by side.
sdk.getPluginDataworks. The plugin-served data (view count, currency, message)loads via the parent-proxy
/dataroute.sdk.callValtimoworks. The case-definition line loads via the downscoped user token(GZAC proxy, PBAC ∩ allowlist).
unresolvable widget (see D-matrix) shows the "unavailable" message; a hard failure shows the
error message.
widget is not returned / not rendered; a user with permission sees it.
D. Export
config/case/<key>/<ver>/case/widget-tab/<key>.case-widget-tab.jsonfile contains theexternal-pluginwidget withproperties.configurationIdand the self-describingproperties.pluginDefinitionKey(case-summary) +properties.pluginDefinitionVersion(
0.1.0).(no plugin-identity noise on them).
E. Import matrix — configuration id & plugin type: found / not found / later configured
Matrix of what to test (Src exported a widget referencing config
Cof plugincase-summary@0.1.0):Cin Tgtcase-summaryinstalled in TgtCdirectly and rendersC→ a Tgt config in PLUGINS step0.2.0)D-matrix — detailed steps
id
Cexists (e.g. re-import into Src, or an environment that already hasC). The PLUGINSstep marks it as already present (no mapping required). After import, open a case → the widget
renders (resolves
Cdirectly).case-summary@0.1.0installed with a different configuration idC2(notC). The PLUGINSstep lists the widget's configuration and offers
C2(label"<title> — Case Summary (0.1.0)").Map
C → C2, finish import. Open a case → the widget renders againstC2. Verify thewidget's stored config id is now
C2(via/dboncase_widget_tab_widget .external_plugin_configuration_id, or that it renders).(leave the row unset) and finish import. Expected:
- [ ] The "missing plugin configurations" panel on the case-definition detail becomes visible
and lists a row for
case-summary (0.1.0)(identified from the self-describing export).- [ ] Opening a case → the widget shows the "unavailable" state (bundleUrl null).
- [ ] The widget kept its original (now-dangling) config id (not nulled) — confirm via
/db:external_plugin_configuration_id= the source id,plugin_definition_key/versionpopulated.case-summaryinstalled at all (no host/definition). Expected:- [ ] The PLUGINS step / repair panel row shows status not-installed (the plugin is still
identifiable by key/version from the self-describing export, but there is nothing to map to).
- [ ] Widget imports dangling → unavailable on the case.
case-summaryat adifferent version (e.g.
0.2.0) with a config, the repair/PLUGINS row offers that configbehind an explicit version-mismatch warning. Mapping to it is allowed; after mapping the
widget renders against the mismatched-version config.
F. Dangling repair (later configured) — closes E3 / E4
host for
case-summary(definition becomesAVAILABLE) and create a configurationC3.The case-definition "missing plugin configurations" panel row now shows status available
with
C3selectable.for the
case-summary (0.1.0)row and Save. Expected:- [ ] Success toast; the panel row disappears / the issue clears.
- [ ] Opening a case → the previously-unavailable widget now renders.
- [ ] The widget's stored
external_plugin_configuration_idwas rewritten to the mapped id(
/db), and theexternal-plugin-case-widgetconfiguration issue is resolved.source config, one mapping repairs all of them (they share the source id in the row's
sourcePluginConfigurationIds).tab or process link, each surface is repaired independently (repairing the widget
does not clear the tab's issue, and vice-versa).
G. Delete guard
referencing config
C, attempt to deleteC. Expected: blocked (HTTP 409), the read-onlyin-use modal lists the usage as a CASE parent with the widget's case definition + owning
WIDGETS tab (
tabKey/tabName, plus the widget key). No force override.the plugin host → blocked, the modal includes the widget usage.
deleting the configuration/host succeeds.
H. Multi-version coexistence
case-summaryat two versions (e.g.0.1.0and abumped version), each with its own configuration. Add one widget backed by each configuration on
the same (or different) WIDGETS tab → both render, each pinned to its own plugin version.
I. Internationalisation
in the "add widget" picker follow the UI language (EN/NL).
placeholders switch with the language.
the type/description and combos live.
sdk.t)follows the UI locale (EN/NL) passed into the iframe.
J. Regression / safety
export, and import exactly as before (the shared
WidgetComponentMapchange is additive).external-plugin widgets exports/imports unchanged.
empty
pluginConfigurationMappings) into the same environment keeps the original config idsand resolves normally (does not accidentally dangle widgets).
MySQL as well as PostgreSQL (the dedicated STI columns + queries are dual-DB).