From 9728e2c03d6e7a234a945b0c8ca71efb318de4bb Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 16 Jun 2026 15:56:18 +0200 Subject: [PATCH] Fixed device/asset profile import when default rule chain no longer exists (#15718) * Fixed device/asset profile import when default rule chain no longer exists * Minor fixes * Code refactorings and optimizations --- .../import-export/import-export.service.ts | 40 ++++++++++++++++++- .../assets/locale/locale.constant-en_US.json | 1 + 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/shared/import-export/import-export.service.ts b/ui-ngx/src/app/shared/import-export/import-export.service.ts index 6593e8c9bf..3c4d3b3f12 100644 --- a/ui-ngx/src/app/shared/import-export/import-export.service.ts +++ b/ui-ngx/src/app/shared/import-export/import-export.service.ts @@ -89,12 +89,16 @@ import { import { FormProperty, propertyValid } from '@shared/models/dynamic-form.models'; import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; import { CalculatedField } from '@shared/models/calculated-field.models'; +import { RuleChainId } from '@shared/models/id/rule-chain-id'; export type editMissingAliasesFunction = (widgets: Array, isSingleWidget: boolean, customTitle: string, missingEntityAliases: EntityAliases) => Observable; type SupportEntityResources = 'includeResourcesInExportWidgetTypes' | 'includeResourcesInExportDashboard' | 'includeBundleWidgetsInExport'; +const profileRuleChainFields = ['defaultRuleChainId', 'defaultEdgeRuleChainId'] as const; +type ProfileRuleChainField = typeof profileRuleChainFields[number]; + // @dynamic @Injectable() export class ImportExportService { @@ -749,7 +753,8 @@ export class ImportExportService { type: 'error'})); throw new Error('Invalid device profile file'); } else { - return this.deviceProfileService.saveDeviceProfile(this.prepareImport(deviceProfile)); + return this.clearMissingRuleChainsAndSave(deviceProfile, + profile => this.deviceProfileService.saveDeviceProfile(profile)); } }), catchError(() => of(null)) @@ -776,13 +781,44 @@ export class ImportExportService { type: 'error'})); throw new Error('Invalid asset profile file'); } else { - return this.assetProfileService.saveAssetProfile(this.prepareImport(assetProfile)); + return this.clearMissingRuleChainsAndSave(assetProfile, + profile => this.assetProfileService.saveAssetProfile(profile)); } }), catchError(() => of(null)) ); } + private clearMissingRuleChainsAndSave & Partial>>( + profile: T, save: (profile: T) => Observable): Observable { + return this.clearMissingProfileRuleChains(profile).pipe( + mergeMap(cleared => save(this.prepareImport(cleared))) + ); + } + + private clearMissingProfileRuleChains>>(profile: T): Observable { + const fieldsToCheck = profileRuleChainFields.filter(field => profile[field]?.id); + + if (!fieldsToCheck.length) { + return of(profile); + } + const ruleChainIds = fieldsToCheck.map(field => profile[field].id); + return this.ruleChainService.getRuleChainsByIds(ruleChainIds, {ignoreErrors: true, ignoreLoading: true}).pipe( + map(ruleChains => { + const existingIds = new Set(ruleChains.map(ruleChain => ruleChain.id.id)); + const missingFields = fieldsToCheck.filter(field => !existingIds.has(profile[field].id)); + if (!missingFields.length) { + return profile; + } + this.store.dispatch(new ActionNotificationShow( + {message: this.translate.instant('rulechain.rulechain-not-found-warning'), + type: 'warn'})); + missingFields.forEach(field => profile[field] = null); + return profile; + }) + ); + } + public exportTenantProfile(tenantProfileId: string) { this.tenantProfileService.getTenantProfile(tenantProfileId).subscribe({ next: (tenantProfile) => { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 764676dfbc..09c1e1939e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5137,6 +5137,7 @@ "create-new-rulechain": "Create new rule chain", "rulechain-file": "Rule chain file", "invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.", + "rulechain-not-found-warning": "Default rule chain not found. The profile was saved without one — you can assign it in the profile settings.", "copyId": "Copy rule chain Id", "idCopiedMessage": "Rule chain Id has been copied to clipboard", "select-rulechain": "Select rule chain",