From 6ea9932aa76186192820086d2dda96febcecc71e Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Fri, 3 Jul 2026 02:11:07 +0400 Subject: [PATCH] arch(sprint1): add unknown widget fallback diagnostics --- .../runtime/runtime-diagnostics.service.ts | 16 +++++++ .../dynamic-page-layout.component.ts | 43 +++++++++++++++++-- src/app/widgets/ui/index.ts | 1 + .../widgets/ui/unknown-widget.component.ts | 38 ++++++++++++++++ 4 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/app/core/runtime/runtime-diagnostics.service.ts create mode 100644 src/app/widgets/ui/unknown-widget.component.ts diff --git a/src/app/core/runtime/runtime-diagnostics.service.ts b/src/app/core/runtime/runtime-diagnostics.service.ts new file mode 100644 index 0000000..c1010f7 --- /dev/null +++ b/src/app/core/runtime/runtime-diagnostics.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; + +export interface UnknownWidgetDiagnostic { + tenant: string; + page: string; + section: string; + widget: string; + reason: string; +} + +@Injectable({ providedIn: 'root' }) +export class RuntimeDiagnosticsService { + logUnknownWidget(event: UnknownWidgetDiagnostic): void { + console.warn('[PlatformRuntime][UnknownWidget]', event); + } +} diff --git a/src/app/layouts/containers/dynamic-page-layout.component.ts b/src/app/layouts/containers/dynamic-page-layout.component.ts index 506530e..2ee227f 100644 --- a/src/app/layouts/containers/dynamic-page-layout.component.ts +++ b/src/app/layouts/containers/dynamic-page-layout.component.ts @@ -4,6 +4,9 @@ import { NgComponentOutlet } from '@angular/common'; import { PageRenderModel } from '../../dynamic-renderer/page-renderer/page-renderer.model'; import { WidgetRenderNode } from '../../dynamic-renderer/widget-host/widget-host.model'; import { WidgetHostService } from '../../dynamic-renderer/widget-host/widget-host.service'; +import { RuntimeDiagnosticsService } from '../../core/runtime/runtime-diagnostics.service'; +import { UnknownWidgetComponent } from '../../widgets/ui'; +import { ConfigService } from '../../core/config/config.service'; @Component({ selector: 'app-dynamic-page-layout', @@ -16,7 +19,7 @@ import { WidgetHostService } from '../../dynamic-renderer/widget-host/widget-hos
@for (widget of section.widgets; track widget.id) {
- @if (resolveWidget(widget); as resolved) { + @if (resolveWidget(widget, section.id); as resolved) { }
@@ -36,10 +39,42 @@ import { WidgetHostService } from '../../dynamic-renderer/widget-host/widget-hos }) export class DynamicPageLayoutComponent { @Input() model: PageRenderModel | null = null; + private readonly loggedUnknownWidgets = new Set(); - constructor(private readonly widgetHost: WidgetHostService) {} + constructor( + private readonly widgetHost: WidgetHostService, + private readonly diagnostics: RuntimeDiagnosticsService, + private readonly configService: ConfigService + ) {} - resolveWidget(widget: WidgetRenderNode) { - return this.widgetHost.resolveWidget(widget.source); + resolveWidget(widget: WidgetRenderNode, sectionId?: string) { + const resolved = this.widgetHost.resolveWidget(widget.source); + if (resolved) { + return resolved; + } + + const diagnosticKey = `${this.model?.id ?? 'unknown'}:${sectionId ?? 'unknown'}:${widget.id}`; + if (!this.loggedUnknownWidgets.has(diagnosticKey)) { + this.loggedUnknownWidgets.add(diagnosticKey); + + const tenantId = this.configService.getBootstrapSnapshot()?.tenant.id ?? 'unknown'; + this.diagnostics.logUnknownWidget({ + tenant: tenantId, + page: this.model?.key ?? 'unknown', + section: sectionId ?? 'unknown', + widget: `${widget.type}@${widget.version}`, + reason: 'not_registered_in_widget_registry' + }); + } + + return { + type: widget.type, + version: widget.version, + component: UnknownWidgetComponent, + props: { + widgetType: widget.type, + widgetVersion: widget.version + } + }; } } diff --git a/src/app/widgets/ui/index.ts b/src/app/widgets/ui/index.ts index 9543cce..3754f18 100644 --- a/src/app/widgets/ui/index.ts +++ b/src/app/widgets/ui/index.ts @@ -1,3 +1,4 @@ export * from './footer-navigation-widget.component'; export * from './hero-widget.component'; export * from './product-carousel-widget.component'; +export * from './unknown-widget.component'; diff --git a/src/app/widgets/ui/unknown-widget.component.ts b/src/app/widgets/ui/unknown-widget.component.ts new file mode 100644 index 0000000..c11d861 --- /dev/null +++ b/src/app/widgets/ui/unknown-widget.component.ts @@ -0,0 +1,38 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-unknown-widget', + standalone: true, + template: ` +
+ Widget unavailable +

{{ widgetType }}@{{ widgetVersion }}

+
+ `, + styles: [ + ` + .unknown-widget { + border: 1px dashed var(--border-color, #d3dad9); + border-radius: var(--radius-md, 12px); + padding: 0.75rem; + color: var(--text-secondary, #667a77); + background: var(--background-secondary, #f5f5f5); + } + + .unknown-widget strong { + display: block; + font-size: 0.9rem; + } + + .unknown-widget p { + margin: 0.25rem 0 0; + font-size: 0.8rem; + } + ` + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class UnknownWidgetComponent { + @Input() widgetType: string = 'unknown'; + @Input() widgetVersion: string = 'unknown'; +}