Skip to content

Comments

feat(dashboards-ng): custom id provider for widget instance#1049

Merged
timowolf merged 1 commit intomainfrom
feat/dashboards/widget-id-provider
Dec 29, 2025
Merged

feat(dashboards-ng): custom id provider for widget instance#1049
timowolf merged 1 commit intomainfrom
feat/dashboards/widget-id-provider

Conversation

@chintankavathia
Copy link
Member

@chintankavathia chintankavathia commented Nov 20, 2025

  • Add SiWidgetIdProvider abstract class and injection token for custom widget ID generation
  • Implement SiWidgetDefaultIdProvider with random ID generation as default
  • Update SiGridComponent to use widget ID provider
  • Update SiDefaultWidgetStorage to support custom ID provider for new widgets
  • Add documentation in README.md for widget instance ID generation
  • Add example AppWidgetIdProvider implementation in dashboards-demo
  • Remove hardcoded NEW_WIDGET_PREFIX and counter-based ID generation
  • Add unit tests for custom ID provider integration

BREAKING CHANGE: New widgets are now assigned transient IDs upon creation using the SiWidgetIdProvider.transientWidgetIdResolver, rather than using temporary NEW_WIDGET_PREFIX IDs that are replaced on save.

The save() method signature in the abstract class SiWidgetStorage has been modified to separate existing widgets from new widgets:

Before:

abstract save(
  widgets: (WidgetConfig | Omit<WidgetConfig, 'id'>)[],
  removedWidgets?: WidgetConfig[],
  dashboardId?: string
): Observable<WidgetConfig[]>;

After:

abstract save(
  modifiedWidgets: WidgetConfig[],
  addedWidgets: WidgetConfig[],
  removedWidgets?: WidgetConfig[],
  dashboardId?: string
): Observable<WidgetConfig[]>;

addedWidgets array will contain new widgets with ids generated by SiWidgetIdProvider.transientWidgetIdResolver.

Diagram

image

Summary by CodeRabbit

  • New Features

    • Pluggable widget instance ID generation via SI_WIDGET_ID_PROVIDER with default SiWidgetDefaultIdProvider; demo AppWidgetIdProvider included.
  • Breaking Changes / Persistence

    • New widgets now get transient IDs from the ID provider instead of NEW_WIDGET_PREFIX/counter.
    • SiWidgetStorage.save signature changed to save(modifiedWidgets, addedWidgets, removedWidgets?, dashboardId?) — callers/implementations must be updated.
    • Save flow now separates modified vs. added widgets and resolves IDs for added widgets before persistence.
  • Code

    • SiGridComponent, SiDefaultWidgetStorage, and related code updated to use the provider-based ID flow; NEW_WIDGET_PREFIX removed.
    • Public API updated to export SI_WIDGET_ID_PROVIDER, SiWidgetIdProvider, and SiWidgetDefaultIdProvider.
  • Docs & Tests

    • README docs and dashboards-demo example added for custom ID providers.
    • Unit tests added/updated to cover custom ID provider integration.
    • Note: reviewer reported outdated golden files (no resolution in PR).

@coderabbitai
Copy link

coderabbitai bot commented Nov 20, 2025

📝 Walkthrough

Walkthrough

Replaces ad-hoc "new widget" ID logic with a pluggable SI_WIDGET_ID_PROVIDER (abstract + default), updates grid and storage to use the provider and to separate modified vs added widgets for persistence, adds a demo AppWidgetIdProvider and registers it in the demo app, updates demo storage override to the new save(modifiedWidgets, addedWidgets, removedWidgets?, dashboardId?) signature, updates tests to exercise custom ID providers, and documents the new ID provider usage.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Grid as SiGridComponent
    participant Provider as SI_WIDGET_ID_PROVIDER
    participant Storage as SiWidgetStorage

    rect rgb(230, 245, 255)
    Note over Grid,Provider: Add widget -> transient ID generation
    User->>Grid: addWidgetInstance(widget)
    Grid->>Provider: generateWidgetId(widget, dashboardId)
    Provider-->>Grid: transientId
    Grid->>Grid: store instance with transientId
    end

    rect rgb(240, 255, 230)
    Note over Grid,Storage: Save -> partition & persist
    User->>Grid: save()
    Grid->>Grid: partition -> modifiedWidgets, addedWidgets, removedWidgets
    Grid->>Storage: save(modifiedWidgets, addedWidgets, removedWidgets, dashboardId)
    Storage->>Provider: generateWidgetId(each addedWidget, dashboardId)
    Provider-->>Storage: resolvedIds
    Storage->>Storage: merge modified + added(with resolvedIds) and persist
    Storage-->>Grid: Observable<WidgetConfig[]> (persisted result)
    end
Loading

Suggested labels

core-team-discussion, documentation

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(dashboards-ng): custom id provider for widget instance' clearly and concisely describes the main feature addition: introducing a pluggable custom ID provider system for widget instances.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/dashboards/widget-id-provider

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 and usage tips.

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch from 7264d1d to 292a059 Compare November 20, 2025 06:13
@github-actions
Copy link

github-actions bot commented Nov 20, 2025

@chintankavathia chintankavathia added the breaking-changes Marks issues and PRs that are breaking the API label Nov 20, 2025
@chintankavathia chintankavathia marked this pull request as ready for review November 20, 2025 06:32
@chintankavathia chintankavathia requested a review from a team as a code owner November 20, 2025 06:32
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
projects/dashboards-demo/src/app/app-widget-id-provider.ts (1)

7-20: Consider clarifying the demo's intent.

The implementation demonstrates the basic usage pattern correctly. A few observations:

  • The explicit string return type annotation on line 8 is more restrictive than the abstract signature (string | Promise<string>), which is valid in TypeScript but could be omitted for flexibility.
  • Math.random() provides adequate uniqueness for a demo but may lead to collisions in production scenarios. Consider adding a comment clarifying this is for demonstration purposes.
  • The commented async example is helpful documentation.

If this is intended as a reference implementation, consider adding a comment:

 export class AppWidgetIdProvider extends SiWidgetIdProvider {
+  // Note: This is a demo implementation. For production use, consider UUIDs or server-generated IDs.
   override widgetIdResolver(widgetId: string, dashboardId: string): string {
     return (dashboardId ?? '') + Math.random().toString(36).substring(2, 9);
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)

46-54: Consider documenting collision risk for high-volume scenarios.

The default implementation provides a simple and fast ID generation using Math.random(). For most use cases this is adequate, but in high-volume production scenarios, collisions are theoretically possible.

Consider adding a JSDoc comment to set expectations:

 /**
  * The default implementation of the {@link SiWidgetIdProvider} which
- * generates random widget ids.
+ * generates random alphanumeric widget IDs using Math.random().
+ * For production systems requiring guaranteed uniqueness or security,
+ * consider implementing a custom provider using UUIDs or server-generated IDs.
  */
 @Injectable({ providedIn: 'root' })
 export class SiWidgetDefaultIdProvider extends SiWidgetIdProvider {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed39f98 and 292a059.

📒 Files selected for processing (8)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts (1 hunks)
  • projects/dashboards-demo/src/app/app.config.ts (2 hunks)
  • projects/dashboards-ng/README.md (1 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts (5 hunks)
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts (1 hunks)
  • projects/dashboards-ng/src/model/si-widget-storage.ts (3 hunks)
  • projects/dashboards-ng/src/public-api.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-demo/src/app/app.config.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-demo/src/app/app-widget-id-provider.ts (1)
  • AppWidgetIdProvider (7-20)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
  • SiDefaultWidgetStorage (87-167)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/si-widget-storage.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
🔇 Additional comments (20)
projects/dashboards-ng/src/public-api.ts (1)

15-15: LGTM!

The export properly exposes the new widget ID provider API to consumers of the library, following the existing export pattern.

projects/dashboards-demo/src/app/app.config.ts (3)

10-15: LGTM!

The import statement correctly adds SI_WIDGET_ID_PROVIDER to the existing imports from @siemens/dashboards-ng, maintaining a clean and organized import structure.


19-19: LGTM!

The import of the custom AppWidgetIdProvider implementation is properly placed and follows the project's import organization conventions.


39-39: LGTM!

The DI provider registration correctly wires SI_WIDGET_ID_PROVIDER to use the custom AppWidgetIdProvider class, demonstrating the proper usage pattern for consumers of the library.

projects/dashboards-ng/README.md (2)

187-196: LGTM!

The introduction to the widget instance ID generation system is clear and well-structured, accurately describing the default behavior and the purpose of the provider system.


237-251: LGTM!

The provider registration examples correctly demonstrate both standalone and module-based approaches, and the fallback behavior note is an important detail for users to understand the system's resilience.

projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (3)

14-14: LGTM!

The import of SI_WIDGET_ID_PROVIDER is correctly added to support the new test suite for custom ID resolver functionality.


22-22: LGTM!

The removal of NEW_WIDGET_PREFIX from the import statement aligns with the breaking change that removes the temporary ID mechanism in favor of the new provider system.


226-268: LGTM!

The new test suite properly validates the custom ID resolver functionality through dependency injection. The test:

  • Correctly configures a custom SI_WIDGET_ID_PROVIDER with inline implementation
  • Verifies that the custom resolver is invoked when adding widget instances
  • Validates the expected ID pattern ('custom-id-default') is generated

This provides good coverage for the new pluggable ID provider feature.

projects/dashboards-ng/src/model/si-widget-storage.ts (4)

7-7: LGTM!

The import change from of to from is correct, as the updated implementation returns from(Promise.all(newWidgets)) to handle asynchronous ID resolution.


10-10: LGTM!

The imports of SI_WIDGET_ID_PROVIDER and SiWidgetDefaultIdProvider are correctly added to support the new injectable ID provider system with fallback behavior.


89-90: LGTM!

The injection of both the configurable idProvider via SI_WIDGET_ID_PROVIDER and the defaultIdProvider for fallback is the correct approach to support custom providers while maintaining resilience.


138-151: LGTM!

The update method correctly handles both resolved WidgetConfig[] and Promise<WidgetConfig>[] arrays. Using Promise.all ensures all widget IDs are resolved before:

  • Updating the BehaviorSubject with the resolved configurations
  • Persisting to storage

This maintains consistency between the in-memory observable and persisted state.

projects/dashboards-ng/src/components/grid/si-grid.component.ts (5)

26-29: LGTM!

The import of SI_WIDGET_ID_PROVIDER and SiWidgetDefaultIdProvider correctly adds the dependencies needed for the new pluggable ID provider system.


55-56: LGTM!

The dependency injection of both providers follows the established pattern: the configurable provider via the token, and the default provider for fallback scenarios.


302-328: LGTM - Breaking change properly implemented.

The refactored addWidgetInstance method correctly implements asynchronous ID resolution:

  • Returns Promise<void> (documented breaking change)
  • Handles both synchronous (string) and asynchronous (Promise<string>) ID resolution
  • Includes fallback to defaultWidgetIdProvider on error, ensuring widgets can always be added
  • Properly tracks transient widgets before persistence

The error handling silently falls back to the default provider, which is appropriate since it ensures the operation succeeds while the error context isn't critical for end users.


255-258: LGTM!

The simplified save logic correctly reflects the new ID provider system where widgets receive their final IDs immediately upon creation, eliminating the need to filter or replace temporary IDs before persistence.


341-343: LGTM - Important correctness fix.

The guard correctly prevents transient (unsaved) widgets from being added to markedForRemoval. Since transient widgets haven't been persisted yet, they should simply be removed from the visible list without triggering a delete operation in the storage layer.

This ensures the storage layer only receives deletion requests for widgets that actually exist in persistence.

projects/dashboards-ng/src/model/si-widget-id-provider.ts (2)

9-24: LGTM!

The SI_WIDGET_ID_PROVIDER injection token is well-designed:

  • Provides clear documentation with usage example
  • Uses providedIn: 'root' for singleton behavior
  • Includes a sensible default factory that injects SiWidgetDefaultIdProvider
  • Allows easy customization through DI

26-40: LGTM!

The abstract SiWidgetIdProvider class provides a clean and flexible contract:

  • Union return type (string | Promise<string>) supports both synchronous and asynchronous ID generation
  • Type-safe parameter using WidgetConfig['widgetId']
  • Clear JSDoc documentation
  • Optional dashboardId parameter enables context-aware ID generation

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch from 292a059 to b295ac9 Compare November 20, 2025 06:48
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (1)

14-22: Custom ID resolver wiring in tests looks good; consider adding async/fallback coverage

The new spec suite correctly:

  • Overrides SI_WIDGET_ID_PROVIDER with a custom implementation.
  • Verifies that addWidgetInstance uses the custom resolver and produces the expected custom-id-default ID when dashboardId is absent.

To more fully exercise the new ID-provider contract, you might also:

  • Add a test where widgetIdResolver returns a Promise<string> to cover the async path.
  • Add a test where the custom resolver rejects/throws and verify fallback to SiWidgetDefaultIdProvider (or at least that the grid still assigns a valid ID).

This would guard against regressions in the more complex async + fallback logic.

Also applies to: 226-268

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 292a059 and b295ac9.

📒 Files selected for processing (8)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts (1 hunks)
  • projects/dashboards-demo/src/app/app.config.ts (2 hunks)
  • projects/dashboards-ng/README.md (1 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts (5 hunks)
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts (1 hunks)
  • projects/dashboards-ng/src/model/si-widget-storage.ts (3 hunks)
  • projects/dashboards-ng/src/public-api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • projects/dashboards-ng/README.md
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts
  • projects/dashboards-demo/src/app/app.config.ts
🧰 Additional context used
🧬 Code graph analysis (3)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (3)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
  • SiDefaultWidgetStorage (87-167)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/configuration.ts (1)
  • SI_DASHBOARD_CONFIGURATION (21-27)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/model/si-widget-storage.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
projects/dashboards-ng/src/public-api.ts (1)

15-15: Public re-export of ID provider looks correct

Re-exporting ./model/si-widget-id-provider from the dashboards-ng public API aligns with the new DI-based ID provider contract and keeps the surface consistent with other model exports. No issues here.

projects/dashboards-ng/src/components/grid/si-grid.component.ts (1)

336-343: Guard against marking transient widgets as removed

The added check in removeWidgetInstance to skip adding a widget to markedForRemoval if it’s present in transientWidgetInstances is a good fix: it ensures widgets that were never persisted aren’t treated as “to be removed” on save, which matches the semantics of transientWidgetInstances.

No changes needed here.

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch from b295ac9 to 22041d2 Compare November 20, 2025 08:47
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (1)

302-328: Address async/await mismatch in addWidgetInstance callers.

The review concern is confirmed and critical. addWidgetInstance is now async, but the main production caller does not await it:

  • si-flexible-dashboard.component.ts:270: this.grid().addWidgetInstance(widgetConfig); must be awaited
  • si-flexible-dashboard.component.spec.ts:82: The mock returns void instead of Promise<void>, creating a type inconsistency
  • si-grid.component.spec.ts (lines 137, 150, 163, 182, 199, 260): Test calls should await the Promise to properly handle async behavior

Without awaiting, there is a race condition risk: if the ID resolver is slow, subsequent operations may proceed before the widget is fully added.

♻️ Duplicate comments (1)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)

118-136: Handle update() Promise rejections to avoid unhandled errors.

The async save() flow is fine, but update() still calls Promise.all(newConfigs).then(...) without a .catch(). If any of the per-widget promises reject, or if storage.setItem(...) throws inside the then handler, this results in an unhandled Promise rejection and the error is not surfaced via the returned Observable.

Consider at least adding a catch to update() so these failures are handled (and optionally logged), e.g.:

  protected update(
    newConfigs: WidgetConfig[] | Promise<WidgetConfig>[],
    dashboardId?: string
  ): void {
    const widgets$ = this.load(dashboardId) as BehaviorSubject<WidgetConfig[]>;
-    Promise.all(newConfigs).then(widgetConfigs => {
-      widgets$.next(widgetConfigs);
-      if (!dashboardId) {
-        this.storage.setItem(STORAGE_KEY, JSON.stringify(widgetConfigs));
-      } else {
-        this.storage.setItem(`${STORAGE_KEY}-${dashboardId}`, JSON.stringify(widgetConfigs));
-      }
-    });
+    Promise.all(newConfigs)
+      .then(widgetConfigs => {
+        widgets$.next(widgetConfigs);
+        if (!dashboardId) {
+          this.storage.setItem(STORAGE_KEY, JSON.stringify(widgetConfigs));
+        } else {
+          this.storage.setItem(`${STORAGE_KEY}-${dashboardId}`, JSON.stringify(widgetConfigs));
+        }
+      })
+      .catch(err => {
+        // Avoid unhandled rejections; you may want to replace this with your logging strategy.
+        console.error('Updating widget storage failed', err);
+      });
  }

This keeps the external API the same while preventing unhandled rejections and making storage-layer failures observable.

Also applies to: 138-150

🧹 Nitpick comments (1)
projects/dashboards-ng/README.md (1)

204-207: Add missing imports in the async provider example.

In the ServerWidgetIdProvider snippet you use inject(HttpClient) but only import Injectable and SiWidgetIdProvider plus firstValueFrom. For completeness and copy‑paste‑ability, consider adding:

import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { SiWidgetIdProvider } from '@siemens/dashboards-ng';
import { firstValueFrom } from 'rxjs';

Also applies to: 222-227

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b295ac9 and 22041d2.

📒 Files selected for processing (8)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts (1 hunks)
  • projects/dashboards-demo/src/app/app.config.ts (2 hunks)
  • projects/dashboards-ng/README.md (1 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts (5 hunks)
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts (1 hunks)
  • projects/dashboards-ng/src/model/si-widget-storage.ts (3 hunks)
  • projects/dashboards-ng/src/public-api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • projects/dashboards-ng/src/public-api.ts
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts
  • projects/dashboards-demo/src/app/app.config.ts
🧰 Additional context used
🧬 Code graph analysis (3)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
  • SiDefaultWidgetStorage (87-167)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/si-widget-storage.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (1)

226-267: Custom ID resolver test wiring looks correct.

The second describe block cleanly overrides SI_WIDGET_ID_PROVIDER and verifies that addWidgetInstance uses it to produce the expected custom-<widgetId>-default ID. This gives good coverage of the DI wiring for custom ID providers.

projects/dashboards-ng/src/components/grid/si-grid.component.ts (1)

248-259: Saving with updated positions looks good.

Updating widget positions via updateWidgetPositions just before calling widgetStorage.save ensures the persisted layout always reflects the latest grid positions, even if no further grid events occurred. This aligns the saved state with what the user sees.

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch from 22041d2 to 364d23c Compare November 20, 2025 08:55
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)

21-24: Provider abstraction and token wiring look solid; optional override nit

The SI_WIDGET_ID_PROVIDER token defaulting to SiWidgetDefaultIdProvider and the abstract SiWidgetIdProvider.widgetIdResolver contract are consistent with how the grid and storage use ID providers. The default implementation is straightforward and appropriate for random IDs.

Consider marking widgetIdResolver in SiWidgetDefaultIdProvider with the override keyword to guard against accidental signature drift, but otherwise this file looks good as-is.

Also applies to: 29-40, 46-53

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22041d2 and 364d23c.

📒 Files selected for processing (8)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts (1 hunks)
  • projects/dashboards-demo/src/app/app.config.ts (2 hunks)
  • projects/dashboards-ng/README.md (1 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts (5 hunks)
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts (1 hunks)
  • projects/dashboards-ng/src/model/si-widget-storage.ts (3 hunks)
  • projects/dashboards-ng/src/public-api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
  • projects/dashboards-demo/src/app/app.config.ts
🧰 Additional context used
🧬 Code graph analysis (3)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/model/si-widget-storage.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (7)
projects/dashboards-ng/src/public-api.ts (1)

10-16: Public export for widget ID provider looks correct

Re‑exporting ./model/si-widget-id-provider here cleanly exposes SI_WIDGET_ID_PROVIDER / SiWidgetIdProvider / SiWidgetDefaultIdProvider for consumers and matches the usage shown in the README examples. No issues from an API-surface perspective.

projects/dashboards-ng/src/model/si-widget-storage.ts (2)

7-11: ID provider injection into default storage is consistent

Importing SI_WIDGET_ID_PROVIDER / SiWidgetDefaultIdProvider and injecting them in SiDefaultWidgetStorage ensures the storage layer uses the same pluggable ID generation mechanism as the grid, which keeps persistence and UI aligned. No issues with this wiring.

Also applies to: 88-90


118-136: Async ID resolution and persistence flow look correct

The save implementation correctly handles both synchronous and Promise‑based widgetIdResolver results, with a fallback to the default provider when the returned Promise rejects, and update waits for all IDs to resolve before emitting and writing to Storage. This matches the documented behavior and maintains a coherent async save path.

Also applies to: 138-150

projects/dashboards-ng/src/components/grid/si-grid.component.ts (4)

26-29: Grid’s dependency on the pluggable ID provider is wired correctly

Importing and injecting SI_WIDGET_ID_PROVIDER plus SiWidgetDefaultIdProvider here keeps the grid aligned with the storage’s ID resolution strategy. The DI usage is straightforward and matches the new provider model.

Also applies to: 55-56


255-256: Save now delegates current layout and removals cleanly to storage

Recomputing widget positions via updateWidgetPositions and then calling save(widgets, this.markedForRemoval, this.dashboardId()) ensures persistence uses the latest layout plus the explicit removal list. This keeps the grid’s visible state and stored state in sync.

Also applies to: 258-258


302-321: Async addWidgetInstance correctly supports sync/async ID providers with fallback

addWidgetInstance now accepts providers that return either a string or a Promise, and falls back to defaultWidgetIdProvider when the returned Promise rejects, before inserting the new widget and recomputing positions. Given your stated contract that synchronous throws from custom providers should surface, this implementation is consistent with both the storage logic and the README wording.


341-343: Avoiding marking transient widgets for removal is a good fix

The extra guard against transientWidgetInstances prevents widgets that were never persisted from being added to markedForRemoval, which avoids emitting bogus “removals” to storage implementations that rely on that list.

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch from 364d23c to babfaf0 Compare November 20, 2025 09:04
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)

124-126: Simplify by removing no-op .then().

The .then() call with no arguments on line 125 is a no-op that doesn't affect behavior but reduces clarity.

Apply this diff:

-          const resolvedId = await id
-            .then()
-            .catch(err => this.defaultIdProvider.widgetIdResolver(widget.widgetId, dashboardId));
+          const resolvedId = await id
+            .catch(err => this.defaultIdProvider.widgetIdResolver(widget.widgetId, dashboardId));
projects/dashboards-ng/src/components/grid/si-grid.component.ts (1)

309-311: Simplify by removing identity function .then(i => i).

The .then(i => i) on line 310 is an identity function that doesn't transform the value, making it redundant and reducing code clarity.

Apply this diff:

-      resolvedId = await id
-        .then(i => i)
-        .catch(err => {
+      resolvedId = await id.catch(err => {
           // In case of an error, we still need to return some id so fallback to default
           return this.defaultWidgetIdProvider.widgetIdResolver(
             widgetInstanceConfig.widgetId,
             this.dashboardId()
           );
         });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 364d23c and babfaf0.

📒 Files selected for processing (8)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts (1 hunks)
  • projects/dashboards-demo/src/app/app.config.ts (2 hunks)
  • projects/dashboards-ng/README.md (1 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts (5 hunks)
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts (1 hunks)
  • projects/dashboards-ng/src/model/si-widget-storage.ts (3 hunks)
  • projects/dashboards-ng/src/public-api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts
  • projects/dashboards-ng/src/public-api.ts
  • projects/dashboards-demo/src/app/app.config.ts
🧰 Additional context used
🧬 Code graph analysis (3)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (3)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
  • SiDefaultWidgetStorage (87-167)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/configuration.ts (1)
  • SI_DASHBOARD_CONFIGURATION (21-27)
projects/dashboards-ng/src/model/si-widget-storage.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
🔇 Additional comments (5)
projects/dashboards-ng/README.md (1)

187-253: Documentation is comprehensive and accurate.

The new "Widget instance ID generation" section thoroughly covers:

  • Default random ID generation via SiWidgetDefaultIdProvider
  • Custom provider implementation (both synchronous and asynchronous patterns)
  • Proper registration in standalone and module-based apps
  • Fallback behavior when custom providers fail

The code examples are syntactically correct and align with the implementation. All previously flagged issues (abstract class terminology, firstValueFrom usage, fallback wording) have been addressed.

projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (1)

226-268: Test suite correctly validates custom ID provider integration.

The new test suite properly configures a custom widgetIdResolver via DI and asserts that the generated widget ID follows the expected pattern (custom-id-default). This validates that the grid component correctly delegates ID generation to the injected provider.

projects/dashboards-ng/src/model/si-widget-storage.ts (1)

118-135: Implementation correctly handles async ID resolution with fallback.

The save method properly delegates ID generation to the injected provider and handles both synchronous (string) and asynchronous (Promise) return values. The fallback to defaultIdProvider on promise rejection ensures widgets can always be added.

projects/dashboards-ng/src/components/grid/si-grid.component.ts (2)

302-321: Async ID resolution with fallback correctly implemented.

The method properly delegates to the injected widgetIdProvider and handles both synchronous and asynchronous ID generation. The fallback to defaultWidgetIdProvider on promise rejection ensures widgets can always be added even if the custom provider fails.


341-343: Guard correctly prevents transient widgets from being marked for removal.

This check ensures that widgets which haven't been persisted yet (transient widgets) are simply discarded rather than being added to markedForRemoval, avoiding unnecessary operations during save.

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch 2 times, most recently from e13ecce to f143835 Compare November 20, 2025 09:22
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)

51-53: Consider collision risk and unused parameters in default ID generation.

The default implementation generates IDs using Math.random().toString(36).substring(2, 9), which produces ~7-character alphanumeric strings. While suitable for prototyping and demos, this approach has some considerations:

  • Collision probability: With base-36 and 7 characters, you have ~78 billion possible IDs. For typical dashboard usage (tens to hundreds of widgets), collision probability is very low but non-zero.
  • Unused parameters: The widgetId and dashboardId parameters are not used in ID generation. While this is intentional (random IDs don't depend on context), consider whether the ID could benefit from incorporating these values (e.g., ${dashboardId}-${widgetId}-${randomPart}) to guarantee uniqueness within a dashboard or make IDs more traceable.

The current implementation is acceptable for the default provider, but document that production applications should consider providing a custom implementation if stronger guarantees are needed (e.g., UUID v4, server-generated IDs, or context-aware prefixing).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between babfaf0 and f143835.

📒 Files selected for processing (10)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts (1 hunks)
  • projects/dashboards-demo/src/app/app.config.ts (2 hunks)
  • projects/dashboards-ng/README.md (1 hunks)
  • projects/dashboards-ng/src/components/flexible-dashboard/si-flexible-dashboard.component.spec.ts (1 hunks)
  • projects/dashboards-ng/src/components/flexible-dashboard/si-flexible-dashboard.component.ts (1 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (7 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts (5 hunks)
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts (1 hunks)
  • projects/dashboards-ng/src/model/si-widget-storage.ts (3 hunks)
  • projects/dashboards-ng/src/public-api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts
  • projects/dashboards-ng/README.md
  • projects/dashboards-demo/src/app/app.config.ts
🧰 Additional context used
🧬 Code graph analysis (5)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (3)
projects/dashboards-ng/test/test-widget/test-widget.ts (1)
  • TEST_WIDGET (15-33)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
  • SiDefaultWidgetStorage (87-167)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/components/flexible-dashboard/si-flexible-dashboard.component.spec.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/model/si-widget-storage.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (14)
projects/dashboards-ng/src/public-api.ts (1)

15-15: LGTM! Public API export enables custom ID provider implementations.

The new export correctly surfaces the ID provider infrastructure (token, abstract class, and default implementation) to library consumers, enabling them to provide custom widget ID generation logic as intended by the PR.

projects/dashboards-ng/src/components/flexible-dashboard/si-flexible-dashboard.component.ts (1)

265-272: LGTM! Async handler correctly awaits the updated addWidgetInstance signature.

The catalog closed event subscription callback is now properly async and awaits the addWidgetInstance call, which aligns with the updated method signature that returns Promise<void>.

projects/dashboards-ng/src/components/flexible-dashboard/si-flexible-dashboard.component.spec.ts (1)

82-84: LGTM! Mock signature correctly reflects async change.

The test mock's addWidgetInstance signature is properly updated to return Promise<void>, aligning with the production code change.

projects/dashboards-ng/src/model/si-widget-id-provider.ts (2)

21-24: LGTM! Well-designed injection token with sensible defaults.

The token correctly uses providedIn: 'root' and provides a factory that injects the default implementation, allowing consumers to override with custom providers while maintaining a safe fallback.


29-40: LGTM! Flexible abstract contract supports both sync and async ID generation.

The abstract class correctly defines the contract with a return type of string | Promise<string>, enabling both synchronous (fast) and asynchronous (e.g., API-based) ID providers. The method signature appropriately accepts widgetId and optional dashboardId for context-aware ID generation.

projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2)

136-147: LGTM! Test properly updated for async widget addition.

The test correctly uses async/await to handle the updated addWidgetInstance signature and validates unique ID generation for multiple widgets.


225-267: LGTM! Excellent integration test for custom ID providers.

This new test suite effectively validates that:

  • Custom ID providers can be injected via SI_WIDGET_ID_PROVIDER
  • The custom resolver is invoked during widget creation
  • Generated IDs follow the expected custom pattern

This provides good coverage of the new pluggable ID provider feature.

projects/dashboards-ng/src/components/grid/si-grid.component.ts (4)

55-56: LGTM! ID providers correctly injected.

The component properly injects both the configurable SI_WIDGET_ID_PROVIDER and the SiWidgetDefaultIdProvider fallback, enabling custom ID generation with a safe default.


302-328: Async ID resolution correctly implemented with fallback.

The method properly:

  • Accepts Omit<WidgetConfig, 'id'> to enforce that callers don't provide IDs
  • Invokes the custom ID provider with context (widgetId, dashboardId)
  • Handles both sync (string) and async (Promise) return values
  • Falls back to default provider on Promise rejection
  • Returns Promise<void> to signal async completion

Note: As discussed in past review comments, synchronous throws from widgetIdResolver will propagate as rejections (standard async function behavior), which is acceptable per the architectural decision that custom providers should handle their own sync errors.


255-258: Save flow simplified correctly—no more temporary ID filtering.

The removal of NEW_WIDGET_PREFIX filtering aligns with the new architecture where widgets receive final IDs immediately via the provider. The save flow now simply updates positions and delegates to storage, which is cleaner and more straightforward.


341-343: LGTM! Transient widget check prevents unnecessary persistence.

The guard correctly prevents adding transient (not-yet-saved) widgets to markedForRemoval, avoiding the persistence of widgets that were never saved in the first place. This is a sensible optimization.

projects/dashboards-ng/src/model/si-widget-storage.ts (3)

89-90: LGTM! ID providers correctly injected.

The storage class properly injects both the configurable SI_WIDGET_ID_PROVIDER and the fallback SiWidgetDefaultIdProvider, enabling custom ID generation with a safe default.


118-135: Async ID resolution correctly implemented with fallback.

The save flow properly:

  • Maps each widget without an ID to an async operation that resolves its ID
  • Invokes the custom ID provider with context
  • Handles both sync (string) and async (Promise) return values
  • Falls back to default provider on Promise rejection via .catch()
  • Updates storage and returns an Observable from Promise.all

This correctly implements the new provider-based ID resolution architecture.


138-151: Updated signature correctly handles async ID resolution.

The update() method signature now accepts WidgetConfig[] | Promise<WidgetConfig>[] and uses Promise.all to resolve all widgets before persisting to storage. This properly supports the async ID resolution flow introduced by the provider system.

Note: The Promise.all at line 143 does not have a .catch() handler. As discussed in past review comments, this architectural decision places error handling responsibility on the caller (via the Observable returned from save()).

Copy link
Member

@timowolf timowolf left a comment

Choose a reason for hiding this comment

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

In many cases, the final persisted id of a resource is returned by the API, when creating the entity on a HTTP POST. Upates need a HTTP PUT. With this change, the storage layer does not get the information of which widgets are new and which are updated.

The save method takes actually three parameter (updated, new, deleted), but first two are merged and separated by removing the id in the old implementation.

Maybe that was not so bad, maybe there is a better way of doing it.

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch from f143835 to 4c0834f Compare December 2, 2025 06:01
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
projects/dashboards-demo/src/app/app-widget-storage.ts (1)

51-57: Consider aligning saveAsDefaults with the new ID provider pattern.

The saveAsDefaults method still generates IDs directly using Math.random().toString(36).substring(2, 9) instead of using the injected SiWidgetIdProvider. While this may be intentional for the demo, it could cause confusion as it bypasses the custom ID provider mechanism.

If you want consistency with the new provider pattern, consider injecting and using the widget ID provider here as well.

projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2)

149-157: Unnecessary async marker.

The test function is marked as async but doesn't contain any await expressions. Either remove the async keyword or use await if async operations are intended.

-  it('#removeWidget() shall remove WidgetConfig from visible widgets', async () => {
+  it('#removeWidget() shall remove WidgetConfig from visible widgets', () => {

271-278: Consider more robust async waiting.

Using setTimeout(resolve, 0) to wait for async operations is fragile and depends on microtask timing. Consider using fixture.whenStable() or wrapping the test in fakeAsync with tick() for more deterministic behavior.

     component.save();
 
     // wait for async save operation to complete
-    await new Promise(resolve => setTimeout(resolve, 0));
+    await fixture.whenStable();
 
     const widgets = component.visibleWidgetInstances$.value;
     expect(widgets[0].id).toBe('custom-id-default');
projects/dashboards-ng/src/model/si-widget-storage.ts (1)

131-134: Remove redundant .then() call.

Calling .then() with no arguments is a no-op that just creates an unnecessary intermediate promise.

       } else {
-        const resolvedId = await id
-          .then()
-          .catch(err => this.defaultIdProvider.widgetIdResolver(widget, dashboardId));
+        const resolvedId = await id.catch(err =>
+          this.defaultIdProvider.widgetIdResolver(widget, dashboardId)
+        );
         return { ...widget, id: resolvedId };
       }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f143835 and 4c0834f.

📒 Files selected for processing (9)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts (1 hunks)
  • projects/dashboards-demo/src/app/app-widget-storage.ts (1 hunks)
  • projects/dashboards-demo/src/app/app.config.ts (2 hunks)
  • projects/dashboards-ng/README.md (1 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (5 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts (5 hunks)
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts (1 hunks)
  • projects/dashboards-ng/src/model/si-widget-storage.ts (4 hunks)
  • projects/dashboards-ng/src/public-api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts
  • projects/dashboards-ng/src/public-api.ts
🧰 Additional context used
🧬 Code graph analysis (4)
projects/dashboards-demo/src/app/app.config.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-demo/src/app/app-widget-id-provider.ts (1)
  • AppWidgetIdProvider (7-28)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (3)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
  • SiDefaultWidgetStorage (94-177)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/configuration.ts (1)
  • SI_DASHBOARD_CONFIGURATION (21-27)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (1)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (15)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (3)

1-24: Well-structured injection token with factory default.

The InjectionToken with providedIn: 'root' and factory pattern correctly establishes the DI boundary. This allows consumers to override while ensuring a default is always available.


29-48: Consider documenting the transient vs permanent ID distinction more explicitly.

The abstract class design is clean. The transientWidgetIdResolver provides a sensible default, and allowing widgetIdResolver to return Promise<string> enables async ID generation (e.g., from a backend).

One observation: the widget parameter in transientWidgetIdResolver is unused in the default implementation. This is acceptable for API flexibility, but consider adding a brief JSDoc note explaining when consumers might want to use it (e.g., generating IDs based on widget type).


54-61: Default provider implementation is correct.

The SiWidgetDefaultIdProvider correctly extends the abstract class and provides synchronous random ID generation. The providedIn: 'root' ensures singleton behavior.

projects/dashboards-demo/src/app/app.config.ts (2)

10-15: Imports correctly updated.

The new imports for SI_WIDGET_ID_PROVIDER from @siemens/dashboards-ng are properly added alongside existing dashboard imports.


39-39: Provider registration demonstrates correct usage.

This demonstrates the intended pattern for consumers to override the default ID provider. The registration using useClass is appropriate here.

projects/dashboards-ng/README.md (1)

237-253: Provider registration documentation is clear and complete.

The examples for both standalone and module-based applications correctly demonstrate the DI registration pattern. The fallback behavior note is helpful for consumers.

projects/dashboards-demo/src/app/app-widget-storage.ts (1)

119-129: Save method signature correctly updated to match new storage API.

The updated signature separating modifiedWidgets and addedWidgets aligns with the new provider-based ID resolution flow. The super.save() call correctly propagates all parameters.

projects/dashboards-ng/src/components/grid/si-grid.component.ts (4)

52-52: Provider injection is correctly implemented.

The SI_WIDGET_ID_PROVIDER injection follows standard Angular DI patterns and integrates cleanly with the existing service injections.


251-258: Save logic correctly partitions modified and added widgets.

The implementation efficiently uses a Set for O(1) lookups when partitioning widgets. The separation of modifiedWidgets and addedWidgets aligns with the new storage API contract.


302-314: Transient ID generation is synchronous by design.

Using transientWidgetIdResolver (which is always synchronous) for new widgets during edit mode is a good design choice. This avoids async complexity in the UI flow while still allowing custom ID generation at save time via widgetIdResolver.


327-329: Guard prevents transient widgets from being marked for removal.

This correctly avoids persisting removal operations for widgets that were never persisted in the first place. The logic is sound.

projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (1)

215-215: LGTM!

The updated save() call correctly uses the new signature with modifiedWidgets, empty addedWidgets, and empty removedWidgets arrays.

projects/dashboards-ng/src/model/si-widget-storage.ts (3)

36-47: LGTM!

The updated JSDoc clearly documents the new signature separating modifiedWidgets and addedWidgets, which aligns well with the new ID provider architecture where only added widgets need ID resolution.


96-97: LGTM!

Injecting both the custom ID provider and the default provider enables proper fallback behavior when custom ID resolution fails.


148-161: LGTM!

The update method correctly handles both synchronous and async widget configurations by using Promise.all before persisting to storage.

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch from 4c0834f to 495e5ce Compare December 2, 2025 06:26
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
projects/dashboards-ng/README.md (1)

228-237: Fix parameter name mismatch in async example.

Line 228 declares the parameter as widgetIdResolver(widgetId: WidgetConfig, ...), but line 231 references widget.widgetId, which doesn't match. The parameter should be named widget to be consistent with the sync example (line 210) and to make the code functional.

Apply this diff to fix the parameter naming:

-  override async widgetIdResolver(widgetId: WidgetConfig, dashboardId?: string): Promise<string> {
+  override async widgetIdResolver(widget: WidgetConfig, dashboardId?: string): Promise<string> {
     const response = await firstValueFrom(
       this.http.post<{ id: string }>('/api/widgets/generate-id', {
         widgetId: widget.widgetId,
🧹 Nitpick comments (1)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)

126-136: Remove unnecessary .then() call.

The .then() on line 132 is called without a callback, making it a no-op that only adds noise.

       const resolvedId = await id
-          .then()
           .catch(err => this.defaultIdProvider.widgetIdResolver(widget, dashboardId));
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c0834f and 495e5ce.

📒 Files selected for processing (9)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts (1 hunks)
  • projects/dashboards-demo/src/app/app-widget-storage.ts (1 hunks)
  • projects/dashboards-demo/src/app/app.config.ts (2 hunks)
  • projects/dashboards-ng/README.md (1 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (5 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts (5 hunks)
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts (1 hunks)
  • projects/dashboards-ng/src/model/si-widget-storage.ts (4 hunks)
  • projects/dashboards-ng/src/public-api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • projects/dashboards-ng/src/public-api.ts
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts
  • projects/dashboards-demo/src/app/app.config.ts
🧰 Additional context used
🧬 Code graph analysis (5)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (1)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-demo/src/app/app-widget-storage.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (4)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
  • SiDefaultWidgetStorage (94-177)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/model/configuration.ts (1)
  • SI_DASHBOARD_CONFIGURATION (21-27)
projects/dashboards-ng/src/model/si-widget-storage.ts (2)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-demo/src/app/app-widget-id-provider.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (11)
projects/dashboards-demo/src/app/app-widget-storage.ts (1)

119-129: LGTM! Storage API signature updated correctly.

The save() method signature aligns with the updated SiWidgetStorage abstract class, properly separating modified and added widgets. The delegation to super.save() with all parameters is correct.

projects/dashboards-demo/src/app/app-widget-id-provider.ts (2)

7-19: LGTM! Clean implementation of custom ID provider.

The widgetIdResolver correctly handles the optional dashboardId parameter with nullish coalescing. The commented async example provides helpful guidance for consumers who need Promise-based ID generation.


21-27: Delegation to base implementation is appropriate.

The transientWidgetIdResolver correctly delegates to the base class, which is a sensible default. The comment indicates this is intentional and can be customized.

projects/dashboards-ng/src/components/grid/si-grid.component.ts (3)

251-258: Clean separation of modified and added widgets.

The logic correctly uses a Set to efficiently distinguish between modified (existing) and added (transient) widgets before calling storage. This aligns well with the new storage API contract.


302-306: Transient ID generation using provider.

The addWidgetInstance method correctly uses transientWidgetIdResolver for temporary IDs. The final ID will be resolved by the storage layer during save(), which provides clean separation of concerns.


327-329: Good guard to prevent marking transient widgets for removal.

This check correctly avoids adding widgets that were never persisted to markedForRemoval. Transient widgets don't need backend removal since they only exist in the current session.

projects/dashboards-ng/src/model/si-widget-storage.ts (2)

138-145: Async ID resolution and aggregation looks correct.

The implementation correctly:

  1. Wraps modified widgets in resolved promises
  2. Combines with async-resolved added widgets
  3. Returns an Observable that emits once all IDs are resolved

148-161: Update method signature change handles async resolution.

The update method now correctly accepts both synchronous WidgetConfig[] and asynchronous Promise<WidgetConfig>[] arrays, allowing it to defer persistence until all IDs are resolved.

projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (3)

225-259: Well-structured test suite for custom ID resolver.

The new test suite correctly:

  1. Provides a custom SI_WIDGET_ID_PROVIDER via DI
  2. Uses correct parameter types matching the provider interface (widget: WidgetConfig and widget: Omit<WidgetConfig, 'id'>)
  3. Sets up the testing module consistently with the main suite

261-278: Good test coverage for two-phase ID resolution.

This test effectively validates the provider-based ID generation flow:

  1. Pre-save: widget has transient ID (transient-custom-id-default)
  2. Post-save: widget has final ID (custom-id-default)

This confirms the separation between transientWidgetIdResolver (used in grid) and widgetIdResolver (used in storage during save).


215-215: Updated save() call aligns with new API.

The test correctly passes the new signature: save([widgetConfig], [], [], myDashboardId) where the second array represents added widgets (empty here since widgetConfig already has an ID).

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch from 495e5ce to 96de60a Compare December 2, 2025 06:31
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (1)

225-279: Custom ID provider test suite exercises both transient and final IDs

The “SiGridComponent with custom id resolver” block wires SI_WIDGET_ID_PROVIDER with correctly typed resolver functions and verifies that transient IDs and persisted IDs follow the expected transient-custom-* and custom-* patterns, giving good coverage of the new provider flow.

As a tiny cleanup, you could drop the unused { provide: SiWidgetStorage, useClass: SiDefaultWidgetStorage } provider in this describe block, since SiGridComponent uses the SI_WIDGET_STORE token instead.

projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)

5-62: ID provider abstraction and default implementation are coherent

SI_WIDGET_ID_PROVIDER, SiWidgetIdProvider, and SiWidgetDefaultIdProvider define a clear extension point for widget ID generation (including optional async resolution) and integrate cleanly with the rest of the code that now injects this token.

If you touch this file again, you might consider tightening the JSDoc text (there’s a stray * before @example) and optionally mentioning that the default implementations currently ignore dashboardId so consumers know they need a custom provider when IDs must be dashboard‑scoped.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 495e5ce and 96de60a.

📒 Files selected for processing (9)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts (1 hunks)
  • projects/dashboards-demo/src/app/app-widget-storage.ts (1 hunks)
  • projects/dashboards-demo/src/app/app.config.ts (2 hunks)
  • projects/dashboards-ng/README.md (1 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (5 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts (5 hunks)
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts (1 hunks)
  • projects/dashboards-ng/src/model/si-widget-storage.ts (4 hunks)
  • projects/dashboards-ng/src/public-api.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • projects/dashboards-ng/README.md
🧰 Additional context used
🧬 Code graph analysis (6)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (1)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-demo/src/app/app.config.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-demo/src/app/app-widget-id-provider.ts (1)
  • AppWidgetIdProvider (7-28)
projects/dashboards-demo/src/app/app-widget-storage.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-demo/src/app/app-widget-id-provider.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (4)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
  • SiDefaultWidgetStorage (94-177)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/model/configuration.ts (1)
  • SI_DASHBOARD_CONFIGURATION (21-27)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
🔇 Additional comments (11)
projects/dashboards-ng/src/public-api.ts (1)

15-15: Expose ID provider types via public API

Re‑exporting ./model/si-widget-id-provider here is correct and keeps the new ID provider contract available to consumers.

projects/dashboards-demo/src/app/app.config.ts (1)

10-15: Custom widget ID provider is wired correctly in demo app config

The added imports and { provide: SI_WIDGET_ID_PROVIDER, useClass: AppWidgetIdProvider } registration cleanly hook the demo into the new ID provider mechanism while keeping existing store/config wiring intact.

Also applies to: 19-19, 39-39

projects/dashboards-demo/src/app/app-widget-storage.ts (1)

119-129: Updated save signature correctly delegates to base storage

The override matches the new (modifiedWidgets, addedWidgets, removedWidgets?, dashboardId?) contract and cleanly forwards arguments to SiDefaultWidgetStorage.save while preserving the demo’s artificial delay behavior.

projects/dashboards-demo/src/app/app-widget-id-provider.ts (1)

5-28: Demo ID provider correctly implements SiWidgetIdProvider

The AppWidgetIdProvider override signatures match the base contract and provide a clear example of custom final IDs plus reuse of the default transient ID behavior; this is a good, minimal demo implementation.

projects/dashboards-ng/src/components/grid/si-grid.component.ts (4)

26-27: Injecting SI_WIDGET_ID_PROVIDER into grid is consistent with the new ID abstraction

The new import and widgetIdProvider injection integrate the grid cleanly with the configurable ID provider without affecting existing signals or store wiring.

Also applies to: 52-52


251-259: Splitting widgets into modified vs added before save is logically sound

Updating positions once and then partitioning via transientWidgetIds gives modifiedWidgets and addedWidgets that together represent the current visible state, matching the new SiWidgetStorage.save(modified, added, removed, dashboardId) contract.


302-307: Using the transient ID resolver for new widgets matches the provider contract

addWidgetInstance now delegates ID creation to widgetIdProvider.transientWidgetIdResolver, ensuring custom providers control transient IDs and keeping the method synchronous as designed.


327-329: Avoiding removal tracking for transient widgets is correct

The added guard prevents never‑persisted transient instances from being added to markedForRemoval, so storage only receives true deletions of previously persisted widgets.

projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (1)

149-157: Tests correctly reflect new save contract and grid behavior

The updated specs use the new save(modified, added, removed, dashboardId) signature and still validate removeWidget, handleGridEvent, and dashboardId‑driven reload behavior under the new storage semantics.

Also applies to: 198-209, 211-221

projects/dashboards-ng/src/model/si-widget-storage.ts (2)

7-11: Revised SiWidgetStorage.save contract matches new widget lifecycle

The abstract save(modifiedWidgets, addedWidgets, removedWidgets?, dashboardId?) plus updated documentation aligns with how the grid now separates modified vs newly added widgets and gives storage implementations all necessary context for persistence and removals.

Also applies to: 35-55


95-106: Default storage correctly integrates the ID provider into async save flow

SiDefaultWidgetStorage now injects the configurable ID provider and uses it to resolve IDs for addedWidgets (including Promise‑returning resolvers), combines them with modifiedWidgets, and updates both in‑memory subjects and backing storage after Promise.all has resolved.

Also applies to: 120-146, 148-161

Copy link
Member

@timowolf timowolf left a comment

Choose a reason for hiding this comment

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

Many thanks. I think we only need one generateId function that might be overwritten by injection. See my comments.

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch from 96de60a to 4c02328 Compare December 18, 2025 05:02
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2)

149-157: async declaration is unnecessary here.

The test function is marked async but doesn't contain any await expressions. Consider removing async for clarity.

🔧 Suggested fix:
-  it('#removeWidget() shall remove WidgetConfig from visible widgets', async () => {
+  it('#removeWidget() shall remove WidgetConfig from visible widgets', () => {

234-241: Mock type could be more precise.

The mock's generateWidgetId parameter type is WidgetConfig but the SiWidgetIdProvider interface declares it as Omit<WidgetConfig, 'id'>. While structurally compatible, using the exact type would better document the contract.

🔧 Suggested fix:
         {
           provide: SI_WIDGET_ID_PROVIDER,
           useValue: {
-            generateWidgetId: (widget: WidgetConfig, dashboardId?: string) => {
+            generateWidgetId: (widget: Omit<WidgetConfig, 'id'>, dashboardId?: string) => {
               return `custom-${widget.widgetId}-${dashboardId ?? 'default'}`;
             }
           }
         },
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96de60a and 4c02328.

📒 Files selected for processing (9)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts (1 hunks)
  • projects/dashboards-demo/src/app/app-widget-storage.ts (1 hunks)
  • projects/dashboards-demo/src/app/app.config.ts (2 hunks)
  • projects/dashboards-ng/README.md (1 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (5 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts (5 hunks)
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts (1 hunks)
  • projects/dashboards-ng/src/model/si-widget-storage.ts (4 hunks)
  • projects/dashboards-ng/src/public-api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • projects/dashboards-demo/src/app/app-widget-storage.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/README.md
  • projects/dashboards-demo/src/app/app.config.ts
  • projects/dashboards-ng/src/public-api.ts
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-12-17T04:34:55.597Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1173
File: src/app/examples/si-charts/interactive/interactive.ts:5-5
Timestamp: 2025-12-17T04:34:55.597Z
Learning: Guideline: In Angular projects using OnPush change detection (zoneless mode), you only need to call ChangeDetectorRef.markForCheck() for asynchronous operations (e.g., setTimeout, setInterval, Promises, Observables) where a change is not automatically detected. Event handlers triggered by Angular template bindings (click, input, etc.) will trigger change detection automatically and do not require explicit markForCheck() calls. Apply this broadly to TS files within Angular components/services that use OnPush; use markForCheck() selectively for async work where change detection wouldn’t run otherwise.

Applied to files:

  • projects/dashboards-ng/src/model/si-widget-id-provider.ts
  • projects/dashboards-ng/src/model/si-widget-storage.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-15T07:17:06.981Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/widget-instance-editor-dialog/si-widget-instance-editor-dialog.component.spec.ts:37-49
Timestamp: 2025-12-15T07:17:06.981Z
Learning: In widget-instance-editor-dialog component tests (projects/dashboards-ng/src/components/widget-instance-editor-dialog/si-widget-instance-editor-dialog.component.spec.ts), real setTimeout waits (e.g., `await new Promise(resolve => setTimeout(resolve, 0))`) must be used instead of `fixture.whenStable()` when testing component initialization. This is likely due to the component's lifecycle behavior or modal initialization timing that fixture.whenStable() cannot properly wait for in zoneless mode.

Applied to files:

  • projects/dashboards-ng/src/model/si-widget-storage.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-17T06:59:43.496Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1036
File: projects/dashboards-ng/src/widget-loader.ts:176-182
Timestamp: 2025-12-17T06:59:43.496Z
Learning: In projects/dashboards-ng/src/widget-loader.ts, when checking `factory.moduleName`, there's no need to also check for `factory.moduleLoader` because the TypeScript type system (ModuleOptions type) enforces that `moduleLoader` must be present whenever `moduleName` is provided.

Applied to files:

  • projects/dashboards-ng/src/model/si-widget-storage.ts
📚 Learning: 2025-12-15T07:16:53.762Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts:92-105
Timestamp: 2025-12-15T07:16:53.762Z
Learning: In gridstack-wrapper component tests (projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts), real setTimeout waits must be used instead of fixture.whenStable() to avoid injector destroyed errors during mount/unmount operations in zoneless mode. The GridStack timing-dependent operations require actual async delays.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-15T07:16:32.082Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts:69-80
Timestamp: 2025-12-15T07:16:32.082Z
Learning: In gridstack-wrapper component tests (projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts), real setTimeout waits (e.g., `await new Promise(resolve => setTimeout(resolve, 0))`) must be used instead of `fixture.whenStable()` when testing grid item rendering and initialization. This is likely due to gridstack library's initialization timing or lifecycle behavior that fixture.whenStable() cannot properly wait for in zoneless mode.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-04T05:50:17.637Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1135
File: projects/element-ng/tree-view/si-tree-view.component.spec.ts:953-964
Timestamp: 2025-12-04T05:50:17.637Z
Learning: In the siemens/element repository, when making tree-view tests zoneless-ready, jasmine.clock() should be used to mimic setTimeouts even in zoneless mode, as the component implementation relies on setTimeout calls that need to be controlled in tests.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-11T10:09:01.564Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1151
File: projects/element-ng/auto-collapsable-list/si-auto-collapsable-list.directive.spec.ts:83-104
Timestamp: 2025-12-11T10:09:01.564Z
Learning: In the auto-collapsable-list directive tests (projects/element-ng/auto-collapsable-list/si-auto-collapsable-list.directive.spec.ts), jasmine.clock() successfully controls the directive's setTimeout-based layout logic in zoneless mode. Mock timers work fine here, unlike in select component overflow tests where real setTimeout waits are required.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-08T11:25:51.584Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1163
File: projects/element-ng/select/si-select.component.spec.ts:511-516
Timestamp: 2025-12-08T11:25:51.584Z
Learning: In the select component overflow tests (projects/element-ng/select/si-select.component.spec.ts), jasmine.clock() cannot be used to control the timing for overflow item detection. Real setTimeout waits must be used instead, likely due to ResizeObserver or debounced layout calculations that jasmine.clock() cannot control in zoneless mode.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-04T05:50:38.735Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1135
File: projects/element-ng/tree-view/si-tree-view.component.spec.ts:1244-1245
Timestamp: 2025-12-04T05:50:38.735Z
Learning: In zoneless Angular tests with OnPush components, calling `await runOnPushChangeDetection(fixture)` followed by `await fixture.whenStable()` is NOT redundant. The `runOnPushChangeDetection` helper forces change detection to run for OnPush components, while `whenStable()` waits for all async operations to complete. Both are necessary and serve different purposes.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-08T11:24:45.272Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1163
File: projects/element-ng/select/options/si-select-lazy-options.directive.spec.ts:67-85
Timestamp: 2025-12-08T11:24:45.272Z
Learning: In select lazy options tests (projects/element-ng/select/options/si-select-lazy-options.directive.spec.ts), jasmine.clock() cannot control RxJS debounceTime in zoneless mode. Real setTimeout waits must be used instead for tests involving search debouncing.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-15T10:05:59.100Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1193
File: projects/element-ng/search-bar/si-search-bar.component.spec.ts:21-28
Timestamp: 2025-12-15T10:05:59.100Z
Learning: In tests that rely on Jasmine's fake clock to control time-based RxJS operators (e.g., debounceTime), use jasmine.clock().install() together with jasmine.clock().mockDate() to enable precise timing control. Without mockDate, scheduler-based operators may not respond to the fake clock. Apply this guidance to spec files (e.g., any file named *.spec.ts or *.spec.js) that test time-sensitive behavior, ensuring you install and mockDate before advancing time in tests.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
🧬 Code graph analysis (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (3)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
  • SiDefaultWidgetStorage (94-159)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
🔇 Additional comments (7)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (2)

9-24: Well-structured injection token with sensible defaults.

The token is correctly configured with providedIn: 'root' and a factory that injects the default provider, enabling tree-shaking while providing a clean override mechanism.


26-38: Clean abstract class design.

The abstract class properly defines the contract for custom ID providers with appropriate parameter types.

projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2)

14-22: Clean import updates for the new ID provider feature.

The imports correctly bring in SI_WIDGET_ID_PROVIDER and remove the obsolete NEW_WIDGET_PREFIX dependency.


258-267: Good test coverage for custom ID provider integration.

The test validates the end-to-end flow of custom ID generation, ensuring the provider is correctly wired and the expected ID format is produced.

projects/dashboards-ng/src/model/si-widget-storage.ts (3)

94-104: Clean dependency injection for the ID provider.

Using field initialization with inject() is the modern Angular pattern and correctly wires the provider for ID generation.


119-133: Solid implementation of the new save signature.

The logic correctly:

  1. Maps over addedWidgets to generate IDs via the provider
  2. Combines resolved added widgets with modified widgets
  3. Delegates to update() for persistence

Note that removedWidgets is intentionally unused here since this default implementation replaces the entire widget list — the removed widgets are implicitly excluded. Custom implementations can use this parameter for optimized delta-based persistence.


135-143: Streamlined update method.

The refactored update method cleanly accepts resolved WidgetConfig[] and handles both BehaviorSubject emission and storage persistence.

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch from 4c02328 to c7a2b5a Compare December 18, 2025 06:14
@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch from c7a2b5a to 459ba75 Compare December 18, 2025 06:16
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)

46-58: Fix documentation/implementation mismatch for ID format.

The JSDoc on line 54 states the format is ${dashboardId}-${widget.widgetId}-${crypto.randomUUID()}, but the implementation on line 57 produces ${dashboardId ?? ''}${widget.widgetId}-${crypto.randomUUID()} — missing the hyphen separator between dashboardId and widget.widgetId when dashboardId is provided.

For example, with dashboardId="dash1" and widgetId="widget1":

  • Documentation implies: dash1-widget1-<uuid>
  • Code produces: dash1widget1-<uuid>
🔎 Apply this diff to add the hyphen separator:
  override generateWidgetId(widget: Omit<WidgetConfig, 'id'>, dashboardId?: string): string {
-    return `${dashboardId ?? ''}${widget.widgetId}-${crypto.randomUUID()}`;
+    return `${dashboardId ? `${dashboardId}-` : ''}${widget.widgetId}-${crypto.randomUUID()}`;
  }

Alternatively, update the JSDoc to match the current implementation if this behavior is intentional.

projects/dashboards-ng/src/model/si-widget-storage.ts (1)

36-47: Update JSDoc to reference the correct method name.

Line 37 references SiWidgetIdProvider.transientWidgetIdResolver, but the actual method in the SiWidgetIdProvider interface is generateWidgetId. This creates confusion about the API.

🔎 Apply this diff to correct the method reference:
-   * generated through the `SiWidgetIdProvider.transientWidgetIdResolver` implementation
+   * generated through the `SiWidgetIdProvider.generateWidgetId` implementation
🧹 Nitpick comments (1)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)

96-96: Verify if injected idProvider is intentionally unused.

The idProvider is injected on line 96 but doesn't appear to be referenced anywhere in the implementation. Based on the PR objectives, IDs are generated by the grid component before reaching storage, so the provider may not be needed here.

If this field is not used and not planned for future use, consider removing it to avoid confusion:

-  private idProvider = inject(SI_WIDGET_ID_PROVIDER);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c02328 and c7a2b5a.

📒 Files selected for processing (9)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts (1 hunks)
  • projects/dashboards-demo/src/app/app-widget-storage.ts (1 hunks)
  • projects/dashboards-demo/src/app/app.config.ts (2 hunks)
  • projects/dashboards-ng/README.md (1 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (5 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts (5 hunks)
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts (1 hunks)
  • projects/dashboards-ng/src/model/si-widget-storage.ts (4 hunks)
  • projects/dashboards-ng/src/public-api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • projects/dashboards-ng/src/public-api.ts
  • projects/dashboards-demo/src/app/app.config.ts
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts
  • projects/dashboards-ng/README.md
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-12-15T07:17:06.981Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/widget-instance-editor-dialog/si-widget-instance-editor-dialog.component.spec.ts:37-49
Timestamp: 2025-12-15T07:17:06.981Z
Learning: In widget-instance-editor-dialog component tests (projects/dashboards-ng/src/components/widget-instance-editor-dialog/si-widget-instance-editor-dialog.component.spec.ts), real setTimeout waits (e.g., `await new Promise(resolve => setTimeout(resolve, 0))`) must be used instead of `fixture.whenStable()` when testing component initialization. This is likely due to the component's lifecycle behavior or modal initialization timing that fixture.whenStable() cannot properly wait for in zoneless mode.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
  • projects/dashboards-ng/src/model/si-widget-storage.ts
📚 Learning: 2025-12-15T07:16:32.082Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts:69-80
Timestamp: 2025-12-15T07:16:32.082Z
Learning: In gridstack-wrapper component tests (projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts), real setTimeout waits (e.g., `await new Promise(resolve => setTimeout(resolve, 0))`) must be used instead of `fixture.whenStable()` when testing grid item rendering and initialization. This is likely due to gridstack library's initialization timing or lifecycle behavior that fixture.whenStable() cannot properly wait for in zoneless mode.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-15T07:16:53.762Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts:92-105
Timestamp: 2025-12-15T07:16:53.762Z
Learning: In gridstack-wrapper component tests (projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts), real setTimeout waits must be used instead of fixture.whenStable() to avoid injector destroyed errors during mount/unmount operations in zoneless mode. The GridStack timing-dependent operations require actual async delays.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-17T04:34:55.597Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1173
File: src/app/examples/si-charts/interactive/interactive.ts:5-5
Timestamp: 2025-12-17T04:34:55.597Z
Learning: Guideline: In Angular projects using OnPush change detection (zoneless mode), you only need to call ChangeDetectorRef.markForCheck() for asynchronous operations (e.g., setTimeout, setInterval, Promises, Observables) where a change is not automatically detected. Event handlers triggered by Angular template bindings (click, input, etc.) will trigger change detection automatically and do not require explicit markForCheck() calls. Apply this broadly to TS files within Angular components/services that use OnPush; use markForCheck() selectively for async work where change detection wouldn’t run otherwise.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
  • projects/dashboards-demo/src/app/app-widget-storage.ts
  • projects/dashboards-ng/src/model/si-widget-storage.ts
📚 Learning: 2025-12-04T05:50:17.637Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1135
File: projects/element-ng/tree-view/si-tree-view.component.spec.ts:953-964
Timestamp: 2025-12-04T05:50:17.637Z
Learning: In the siemens/element repository, when making tree-view tests zoneless-ready, jasmine.clock() should be used to mimic setTimeouts even in zoneless mode, as the component implementation relies on setTimeout calls that need to be controlled in tests.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-11T10:09:01.564Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1151
File: projects/element-ng/auto-collapsable-list/si-auto-collapsable-list.directive.spec.ts:83-104
Timestamp: 2025-12-11T10:09:01.564Z
Learning: In the auto-collapsable-list directive tests (projects/element-ng/auto-collapsable-list/si-auto-collapsable-list.directive.spec.ts), jasmine.clock() successfully controls the directive's setTimeout-based layout logic in zoneless mode. Mock timers work fine here, unlike in select component overflow tests where real setTimeout waits are required.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-08T11:25:51.584Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1163
File: projects/element-ng/select/si-select.component.spec.ts:511-516
Timestamp: 2025-12-08T11:25:51.584Z
Learning: In the select component overflow tests (projects/element-ng/select/si-select.component.spec.ts), jasmine.clock() cannot be used to control the timing for overflow item detection. Real setTimeout waits must be used instead, likely due to ResizeObserver or debounced layout calculations that jasmine.clock() cannot control in zoneless mode.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-04T05:50:38.735Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1135
File: projects/element-ng/tree-view/si-tree-view.component.spec.ts:1244-1245
Timestamp: 2025-12-04T05:50:38.735Z
Learning: In zoneless Angular tests with OnPush components, calling `await runOnPushChangeDetection(fixture)` followed by `await fixture.whenStable()` is NOT redundant. The `runOnPushChangeDetection` helper forces change detection to run for OnPush components, while `whenStable()` waits for all async operations to complete. Both are necessary and serve different purposes.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-08T11:24:45.272Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1163
File: projects/element-ng/select/options/si-select-lazy-options.directive.spec.ts:67-85
Timestamp: 2025-12-08T11:24:45.272Z
Learning: In select lazy options tests (projects/element-ng/select/options/si-select-lazy-options.directive.spec.ts), jasmine.clock() cannot control RxJS debounceTime in zoneless mode. Real setTimeout waits must be used instead for tests involving search debouncing.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-15T10:05:59.100Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1193
File: projects/element-ng/search-bar/si-search-bar.component.spec.ts:21-28
Timestamp: 2025-12-15T10:05:59.100Z
Learning: In tests that rely on Jasmine's fake clock to control time-based RxJS operators (e.g., debounceTime), use jasmine.clock().install() together with jasmine.clock().mockDate() to enable precise timing control. Without mockDate, scheduler-based operators may not respond to the fake clock. Apply this guidance to spec files (e.g., any file named *.spec.ts or *.spec.js) that test time-sensitive behavior, ensuring you install and mockDate before advancing time in tests.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-17T06:59:43.496Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1036
File: projects/dashboards-ng/src/widget-loader.ts:176-182
Timestamp: 2025-12-17T06:59:43.496Z
Learning: In projects/dashboards-ng/src/widget-loader.ts, when checking `factory.moduleName`, there's no need to also check for `factory.moduleLoader` because the TypeScript type system (ModuleOptions type) enforces that `moduleLoader` must be present whenever `moduleName` is provided.

Applied to files:

  • projects/dashboards-ng/src/model/si-widget-storage.ts
🧬 Code graph analysis (4)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (1)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (4)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
  • SiDefaultWidgetStorage (94-154)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/model/configuration.ts (1)
  • SI_DASHBOARD_CONFIGURATION (21-27)
projects/dashboards-demo/src/app/app-widget-storage.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
🔇 Additional comments (10)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)

26-38: LGTM! Clear abstraction for widget ID generation.

The abstract class provides a clean contract for custom ID providers, with appropriate typing using Omit<WidgetConfig, 'id'> to ensure new widgets don't have IDs yet.

projects/dashboards-demo/src/app/app-widget-storage.ts (1)

119-129: LGTM! Signature correctly updated for new storage API.

The save method properly separates modifiedWidgets and addedWidgets parameters and passes them to the base implementation. The demo delay logic is appropriately preserved.

projects/dashboards-ng/src/components/grid/si-grid.component.ts (4)

26-26: LGTM! Proper DI setup for widget ID provider.

The component correctly imports and injects the SI_WIDGET_ID_PROVIDER token to support custom ID generation.

Also applies to: 52-52


251-258: LGTM! Clean partition logic for modified vs added widgets.

The save method correctly partitions widgets into modifiedWidgets (existing) and addedWidgets (transient) using a Set-based lookup, eliminating the previous prefix-based approach. The storage API call properly passes both arrays.


302-311: LGTM! Provider-based ID generation properly integrated.

The method now uses widgetIdProvider.generateWidgetId() to generate IDs for new widget instances, replacing the previous counter-based approach. The generated ID is correctly assigned and the widget is tracked in transientWidgetInstances.


319-333: LGTM! Correct handling of transient vs persisted widget removal.

The method properly checks if a widget is transient before adding it to markedForRemoval. Transient widgets (not yet saved) are simply removed from the visible list, while persisted widgets are marked for deletion from storage.

projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2)

211-222: LGTM! Test correctly updated for new storage API.

The test properly calls widgetStorage.save() with the new signature, passing the widget as a modified widget (first parameter) and empty arrays for added and removed widgets.


225-268: LGTM! Comprehensive test for custom ID provider integration.

The new test suite properly:

  • Configures a custom SI_WIDGET_ID_PROVIDER with a mock implementation
  • Verifies that widget IDs are generated using the custom provider
  • Checks the expected ID format (custom-id-default)

The mock signature correctly matches the SiWidgetIdProvider interface.

projects/dashboards-ng/src/model/si-widget-storage.ts (2)

119-128: LGTM! Save implementation correctly handles separated widget lists.

The method properly accepts modifiedWidgets and addedWidgets as separate parameters, combines them for persistence, and returns the merged list. The implementation is clean and straightforward.


130-138: LGTM! Update method correctly persists widget configurations.

The method properly updates the BehaviorSubject and persists to storage, with correct handling of both default and dashboard-specific storage keys.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)

46-58: Documentation does not match implementation format.

The JSDoc at line 54 states the format is ${dashboardId}-${widget.widgetId}-${crypto.randomUUID()}, but the implementation at line 57 produces ${dashboardId ?? ''}${widget.widgetId}-${crypto.randomUUID()} — missing the hyphen between dashboardId and widget.widgetId when dashboardId is provided.

For dashboardId="dash1" and widgetId="widget1":

  • Doc implies: dash1-widget1-<uuid>
  • Code produces: dash1widget1-<uuid>

Either update the implementation to add the separator or fix the JSDoc to match.

🔧 Suggested fix — add hyphen separator when dashboardId is present:
  override generateWidgetId(widget: Omit<WidgetConfig, 'id'>, dashboardId?: string): string {
-   return `${dashboardId ?? ''}${widget.widgetId}-${crypto.randomUUID()}`;
+   return `${dashboardId ? `${dashboardId}-` : ''}${widget.widgetId}-${crypto.randomUUID()}`;
  }
projects/dashboards-ng/src/model/si-widget-storage.ts (1)

36-47: Address outstanding documentation issues.

Multiple reviewers have flagged issues with this documentation that still need to be addressed:

  1. Line 37: References non-existent SiWidgetIdProvider.transientWidgetIdResolver (should refer to generateWidgetId or clarify that IDs are assigned before save() is called).

  2. Lines 38-39: States "it is in the responsibility of the implementor to set the ids of the new widgets," but the signature shows addedWidgets: WidgetConfig[] which already have IDs assigned. This is contradictory.

  3. Lines 39-41: The sentence about checking for removed widgets is unclear (as noted by timowolf).

Per timowolf's suggestion, consider simplifying this to clarify that IDs are assigned earlier in the flow and that the method receives widgets that are already identified.

🧹 Nitpick comments (2)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (1)

234-241: Mock parameter type should use Omit<WidgetConfig, 'id'>.

The mock's generateWidgetId signature uses widget: WidgetConfig, but the SiWidgetIdProvider abstract class defines the parameter as widget: Omit<WidgetConfig, 'id'>. While this works at runtime (TypeScript structural typing), the type annotation should match the interface for clarity and type safety.

🔧 Suggested fix:
         {
           provide: SI_WIDGET_ID_PROVIDER,
           useValue: {
-            generateWidgetId: (widget: WidgetConfig, dashboardId?: string) => {
+            generateWidgetId: (widget: Omit<WidgetConfig, 'id'>, dashboardId?: string) => {
               return `custom-${widget.widgetId}-${dashboardId ?? 'default'}`;
             }
           }
         },
projects/dashboards-demo/src/app/app-widget-storage.ts (1)

51-57: Consider using the ID provider for consistency.

The saveAsDefaults method generates IDs using Math.random().toString(36).substring(2, 9) rather than the injected SI_WIDGET_ID_PROVIDER. While this is demo code and may be intentional, using the provider would ensure consistent ID generation across the application.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7a2b5a and 459ba75.

📒 Files selected for processing (9)
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts (1 hunks)
  • projects/dashboards-demo/src/app/app-widget-storage.ts (1 hunks)
  • projects/dashboards-demo/src/app/app.config.ts (2 hunks)
  • projects/dashboards-ng/README.md (1 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (5 hunks)
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts (5 hunks)
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts (1 hunks)
  • projects/dashboards-ng/src/model/si-widget-storage.ts (4 hunks)
  • projects/dashboards-ng/src/public-api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • projects/dashboards-demo/src/app/app.config.ts
  • projects/dashboards-ng/README.md
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-12-17T04:34:55.597Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1173
File: src/app/examples/si-charts/interactive/interactive.ts:5-5
Timestamp: 2025-12-17T04:34:55.597Z
Learning: Guideline: In Angular projects using OnPush change detection (zoneless mode), you only need to call ChangeDetectorRef.markForCheck() for asynchronous operations (e.g., setTimeout, setInterval, Promises, Observables) where a change is not automatically detected. Event handlers triggered by Angular template bindings (click, input, etc.) will trigger change detection automatically and do not require explicit markForCheck() calls. Apply this broadly to TS files within Angular components/services that use OnPush; use markForCheck() selectively for async work where change detection wouldn’t run otherwise.

Applied to files:

  • projects/dashboards-ng/src/public-api.ts
  • projects/dashboards-demo/src/app/app-widget-storage.ts
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
  • projects/dashboards-ng/src/model/si-widget-storage.ts
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts
📚 Learning: 2025-12-15T07:17:06.981Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/widget-instance-editor-dialog/si-widget-instance-editor-dialog.component.spec.ts:37-49
Timestamp: 2025-12-15T07:17:06.981Z
Learning: In widget-instance-editor-dialog component tests (projects/dashboards-ng/src/components/widget-instance-editor-dialog/si-widget-instance-editor-dialog.component.spec.ts), real setTimeout waits (e.g., `await new Promise(resolve => setTimeout(resolve, 0))`) must be used instead of `fixture.whenStable()` when testing component initialization. This is likely due to the component's lifecycle behavior or modal initialization timing that fixture.whenStable() cannot properly wait for in zoneless mode.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
  • projects/dashboards-ng/src/model/si-widget-storage.ts
📚 Learning: 2025-12-15T07:16:32.082Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts:69-80
Timestamp: 2025-12-15T07:16:32.082Z
Learning: In gridstack-wrapper component tests (projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts), real setTimeout waits (e.g., `await new Promise(resolve => setTimeout(resolve, 0))`) must be used instead of `fixture.whenStable()` when testing grid item rendering and initialization. This is likely due to gridstack library's initialization timing or lifecycle behavior that fixture.whenStable() cannot properly wait for in zoneless mode.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-15T07:16:53.762Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts:92-105
Timestamp: 2025-12-15T07:16:53.762Z
Learning: In gridstack-wrapper component tests (projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts), real setTimeout waits must be used instead of fixture.whenStable() to avoid injector destroyed errors during mount/unmount operations in zoneless mode. The GridStack timing-dependent operations require actual async delays.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-04T05:50:17.637Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1135
File: projects/element-ng/tree-view/si-tree-view.component.spec.ts:953-964
Timestamp: 2025-12-04T05:50:17.637Z
Learning: In the siemens/element repository, when making tree-view tests zoneless-ready, jasmine.clock() should be used to mimic setTimeouts even in zoneless mode, as the component implementation relies on setTimeout calls that need to be controlled in tests.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-11T10:09:01.564Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1151
File: projects/element-ng/auto-collapsable-list/si-auto-collapsable-list.directive.spec.ts:83-104
Timestamp: 2025-12-11T10:09:01.564Z
Learning: In the auto-collapsable-list directive tests (projects/element-ng/auto-collapsable-list/si-auto-collapsable-list.directive.spec.ts), jasmine.clock() successfully controls the directive's setTimeout-based layout logic in zoneless mode. Mock timers work fine here, unlike in select component overflow tests where real setTimeout waits are required.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-08T11:25:51.584Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1163
File: projects/element-ng/select/si-select.component.spec.ts:511-516
Timestamp: 2025-12-08T11:25:51.584Z
Learning: In the select component overflow tests (projects/element-ng/select/si-select.component.spec.ts), jasmine.clock() cannot be used to control the timing for overflow item detection. Real setTimeout waits must be used instead, likely due to ResizeObserver or debounced layout calculations that jasmine.clock() cannot control in zoneless mode.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-04T05:50:38.735Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1135
File: projects/element-ng/tree-view/si-tree-view.component.spec.ts:1244-1245
Timestamp: 2025-12-04T05:50:38.735Z
Learning: In zoneless Angular tests with OnPush components, calling `await runOnPushChangeDetection(fixture)` followed by `await fixture.whenStable()` is NOT redundant. The `runOnPushChangeDetection` helper forces change detection to run for OnPush components, while `whenStable()` waits for all async operations to complete. Both are necessary and serve different purposes.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-08T11:24:45.272Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1163
File: projects/element-ng/select/options/si-select-lazy-options.directive.spec.ts:67-85
Timestamp: 2025-12-08T11:24:45.272Z
Learning: In select lazy options tests (projects/element-ng/select/options/si-select-lazy-options.directive.spec.ts), jasmine.clock() cannot control RxJS debounceTime in zoneless mode. Real setTimeout waits must be used instead for tests involving search debouncing.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-15T10:05:59.100Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1193
File: projects/element-ng/search-bar/si-search-bar.component.spec.ts:21-28
Timestamp: 2025-12-15T10:05:59.100Z
Learning: In tests that rely on Jasmine's fake clock to control time-based RxJS operators (e.g., debounceTime), use jasmine.clock().install() together with jasmine.clock().mockDate() to enable precise timing control. Without mockDate, scheduler-based operators may not respond to the fake clock. Apply this guidance to spec files (e.g., any file named *.spec.ts or *.spec.js) that test time-sensitive behavior, ensuring you install and mockDate before advancing time in tests.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-17T06:59:43.496Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1036
File: projects/dashboards-ng/src/widget-loader.ts:176-182
Timestamp: 2025-12-17T06:59:43.496Z
Learning: In projects/dashboards-ng/src/widget-loader.ts, when checking `factory.moduleName`, there's no need to also check for `factory.moduleLoader` because the TypeScript type system (ModuleOptions type) enforces that `moduleLoader` must be present whenever `moduleName` is provided.

Applied to files:

  • projects/dashboards-ng/src/model/si-widget-storage.ts
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts
🧬 Code graph analysis (5)
projects/dashboards-demo/src/app/app-widget-storage.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (1)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (3)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
  • SiDefaultWidgetStorage (94-154)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/model/si-widget-storage.ts (2)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (115-177)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
🔇 Additional comments (11)
projects/dashboards-demo/src/app/app-widget-id-provider.ts (1)

1-5: LGTM!

The imports are appropriate and the copyright header is correct.

projects/dashboards-ng/src/public-api.ts (1)

15-15: LGTM!

The new export for si-widget-id-provider is correctly placed alongside other model exports, enabling consumers to provide custom widget ID generation logic.

projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)

21-24: LGTM!

The injection token setup with a factory defaulting to SiWidgetDefaultIdProvider is a clean pattern that allows consumers to override while providing sensible defaults.

projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (2)

258-267: LGTM!

The test properly validates that the custom ID provider is used for widget ID generation, verifying the expected custom-id-default format. Good coverage for the new provider injection feature.


211-222: LGTM!

The save() call correctly uses the new signature with separated modifiedWidgets, addedWidgets, and removedWidgets parameters.

projects/dashboards-ng/src/components/grid/si-grid.component.ts (3)

251-258: LGTM!

The save flow correctly partitions widgets into modifiedWidgets (existing persisted widgets) and addedWidgets (transient widgets added in edit mode) using a Set for efficient lookup. This cleanly separates the concerns for the updated storage API.


302-311: LGTM!

The addWidgetInstance method cleanly integrates with the widget ID provider for synchronous ID generation. The approach aligns with the discussion in past reviews where synchronous errors from custom providers should fail fast for immediate visibility during development.


319-333: LGTM!

The logic correctly avoids marking transient widgets for removal since they were never persisted to the backend. Only widgets that exist in persistedWidgetInstances (i.e., not in transientWidgetInstances) should be added to markedForRemoval.

projects/dashboards-demo/src/app/app-widget-storage.ts (1)

119-129: LGTM!

The save() method signature is correctly updated to match the new SiDefaultWidgetStorage API, properly forwarding modifiedWidgets, addedWidgets, and removedWidgets to the parent implementation while maintaining the demo's simulated delay behavior.

projects/dashboards-ng/src/model/si-widget-storage.ts (2)

119-128: LGTM - simplified persistence implementation.

The implementation correctly merges addedWidgets and modifiedWidgets for persistence without performing ID generation. This aligns with the design decision (per timowolf's feedback) that ID generation happens earlier in the flow, and by the time save() is called, all widgets already have their IDs assigned.


130-138: LGTM - cleaner synchronous update.

The update() method has been simplified to accept WidgetConfig[] directly instead of dealing with async operations. This removes the complexity around Promise resolution and error handling, making the code cleaner and more maintainable.

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch 6 times, most recently from 08ae388 to bfd67b8 Compare December 18, 2025 12:31
Copy link
Member

@timowolf timowolf left a comment

Choose a reason for hiding this comment

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

Works like a charm

@timowolf timowolf added this to the 49.0.0 milestone Dec 22, 2025
@timowolf timowolf force-pushed the feat/dashboards/widget-id-provider branch from bfd67b8 to 1e5e3cf Compare December 22, 2025 15:08
@timowolf timowolf enabled auto-merge (rebase) December 22, 2025 15:08
@kfenner kfenner force-pushed the feat/dashboards/widget-id-provider branch from 1e5e3cf to 28ce4c9 Compare December 23, 2025 15:45
@kfenner
Copy link
Member

kfenner commented Dec 23, 2025

@timowolf @spike-rabbit Looks like the goldens are outdated:
image

@chintankavathia chintankavathia force-pushed the feat/dashboards/widget-id-provider branch from 28ce4c9 to 1199318 Compare December 24, 2025 05:23
- Add `SiWidgetIdProvider` abstract class and injection token for custom widget ID generation
- Implement `SiWidgetDefaultIdProvider` with random ID generation as default
- Update `SiGridComponent` to use widget ID provider
- Update `SiDefaultWidgetStorage` to support custom ID provider for new widgets
- Add documentation in README.md for widget instance ID generation
- Add example `AppWidgetIdProvider` implementation in dashboards-demo
- Remove hardcoded `NEW_WIDGET_PREFIX` and counter-based ID generation
- Add unit tests for custom ID provider integration

BREAKING CHANGE: New widgets are now assigned IDs upon creation using the `SiWidgetIdProvider.generateWidgetId`, rather than using temporary `NEW_WIDGET_PREFIX` IDs that are replaced on save.

The `save()` method signature in the abstract class `SiWidgetStorage` has been modified to separate existing widgets from new widgets:

Before:

```
abstract save(
  widgets: (WidgetConfig | Omit<WidgetConfig, 'id'>)[],
  removedWidgets?: WidgetConfig[],
  dashboardId?: string
): Observable<WidgetConfig[]>;
```

After:
```
abstract save(
  modifiedWidgets: WidgetConfig[],
  addedWidgets: WidgetConfig[],
  removedWidgets?: WidgetConfig[],
  dashboardId?: string
): Observable<WidgetConfig[]>;
```

`addedWidgets` array will contain new widgets with ids generated by `SiWidgetIdProvider.generateWidgetId`.
@kfenner kfenner force-pushed the feat/dashboards/widget-id-provider branch from 1199318 to 3bb7737 Compare December 29, 2025 09:52
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
projects/dashboards-demo/src/app/app-widget-storage.ts (1)

52-54: Ad-hoc ID generation bypasses the new ID provider infrastructure.

This code directly mutates widget IDs using Math.random() instead of using the SI_WIDGET_ID_PROVIDER introduced in this PR. This is inconsistent with the new pluggable ID provider pattern and bypasses any custom ID generation logic (like the AppWidgetIdProvider registered in the demo app).

Consider injecting SI_WIDGET_ID_PROVIDER and using it to generate IDs:

🔎 Suggested approach using the ID provider

Inject the provider in the constructor:

+import { inject } from '@angular/core';
+import { SI_WIDGET_ID_PROVIDER } from '@siemens/dashboards-ng';
+
 export class AppWidgetStorage extends SiDefaultWidgetStorage implements SiWidgetStorage {
+  private idProvider = inject(SI_WIDGET_ID_PROVIDER);
   private secondaryMenuItems = new BehaviorSubject<DashboardToolbarItem[]>([]);

Then use it in saveAsDefaults:

   saveAsDefaults = (grid: SiGridComponent): Observable<void> => {
-    grid.transientWidgetInstances.forEach(
-      widget => (widget.id = Math.random().toString(36).substring(2, 9))
-    );
+    grid.transientWidgetInstances.forEach(widget => {
+      widget.id = this.idProvider.generateWidgetId(widget, grid.dashboardId());
+    });
     this.storage.setItem(CUSTOM_DEFAULTS, JSON.stringify(grid.visibleWidgetInstances$.value));
     return of();
   };
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1199318 and 3bb7737.

📒 Files selected for processing (10)
  • api-goldens/dashboards-ng/index.api.md
  • projects/dashboards-demo/src/app/app-widget-id-provider.ts
  • projects/dashboards-demo/src/app/app-widget-storage.ts
  • projects/dashboards-demo/src/app/app.config.ts
  • projects/dashboards-ng/README.md
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts
  • projects/dashboards-ng/src/model/si-widget-storage.ts
  • projects/dashboards-ng/src/public-api.ts
🧰 Additional context used
🧠 Learnings (15)
📚 Learning: 2025-12-17T06:59:51.485Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1036
File: projects/dashboards-ng/src/widget-loader.ts:176-182
Timestamp: 2025-12-17T06:59:51.485Z
Learning: In projects/dashboards-ng/src/widget-loader.ts, when checking `factory.moduleName`, there's no need to also check for `factory.moduleLoader` because the TypeScript type system (ModuleOptions type) enforces that `moduleLoader` must be present whenever `moduleName` is provided.

Applied to files:

  • projects/dashboards-demo/src/app/app-widget-id-provider.ts
  • projects/dashboards-ng/README.md
  • projects/dashboards-ng/src/model/si-widget-storage.ts
📚 Learning: 2025-12-17T04:34:55.597Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1173
File: src/app/examples/si-charts/interactive/interactive.ts:5-5
Timestamp: 2025-12-17T04:34:55.597Z
Learning: Guideline: In Angular projects using OnPush change detection (zoneless mode), you only need to call ChangeDetectorRef.markForCheck() for asynchronous operations (e.g., setTimeout, setInterval, Promises, Observables) where a change is not automatically detected. Event handlers triggered by Angular template bindings (click, input, etc.) will trigger change detection automatically and do not require explicit markForCheck() calls. Apply this broadly to TS files within Angular components/services that use OnPush; use markForCheck() selectively for async work where change detection wouldn’t run otherwise.

Applied to files:

  • projects/dashboards-demo/src/app/app-widget-id-provider.ts
  • projects/dashboards-ng/src/public-api.ts
  • projects/dashboards-demo/src/app/app-widget-storage.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts
  • projects/dashboards-demo/src/app/app.config.ts
  • projects/dashboards-ng/src/model/si-widget-storage.ts
📚 Learning: 2025-12-15T07:16:53.762Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts:92-105
Timestamp: 2025-12-15T07:16:53.762Z
Learning: In gridstack-wrapper component tests (projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts), real setTimeout waits must be used instead of fixture.whenStable() to avoid injector destroyed errors during mount/unmount operations in zoneless mode. The GridStack timing-dependent operations require actual async delays.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
📚 Learning: 2025-12-15T07:16:32.082Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts:69-80
Timestamp: 2025-12-15T07:16:32.082Z
Learning: In gridstack-wrapper component tests (projects/dashboards-ng/src/components/gridstack-wrapper/si-gridstack-wrapper.component.spec.ts), real setTimeout waits (e.g., `await new Promise(resolve => setTimeout(resolve, 0))`) must be used instead of `fixture.whenStable()` when testing grid item rendering and initialization. This is likely due to gridstack library's initialization timing or lifecycle behavior that fixture.whenStable() cannot properly wait for in zoneless mode.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
📚 Learning: 2025-12-15T07:17:06.981Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1191
File: projects/dashboards-ng/src/components/widget-instance-editor-dialog/si-widget-instance-editor-dialog.component.spec.ts:37-49
Timestamp: 2025-12-15T07:17:06.981Z
Learning: In widget-instance-editor-dialog component tests (projects/dashboards-ng/src/components/widget-instance-editor-dialog/si-widget-instance-editor-dialog.component.spec.ts), real setTimeout waits (e.g., `await new Promise(resolve => setTimeout(resolve, 0))`) must be used instead of `fixture.whenStable()` when testing component initialization. This is likely due to the component's lifecycle behavior or modal initialization timing that fixture.whenStable() cannot properly wait for in zoneless mode.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/README.md
  • projects/dashboards-ng/src/model/si-widget-storage.ts
📚 Learning: 2025-12-04T05:50:17.637Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1135
File: projects/element-ng/tree-view/si-tree-view.component.spec.ts:953-964
Timestamp: 2025-12-04T05:50:17.637Z
Learning: In the siemens/element repository, when making tree-view tests zoneless-ready, jasmine.clock() should be used to mimic setTimeouts even in zoneless mode, as the component implementation relies on setTimeout calls that need to be controlled in tests.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-11T10:09:01.564Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1151
File: projects/element-ng/auto-collapsable-list/si-auto-collapsable-list.directive.spec.ts:83-104
Timestamp: 2025-12-11T10:09:01.564Z
Learning: In the auto-collapsable-list directive tests (projects/element-ng/auto-collapsable-list/si-auto-collapsable-list.directive.spec.ts), jasmine.clock() successfully controls the directive's setTimeout-based layout logic in zoneless mode. Mock timers work fine here, unlike in select component overflow tests where real setTimeout waits are required.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-04T05:50:38.735Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1135
File: projects/element-ng/tree-view/si-tree-view.component.spec.ts:1244-1245
Timestamp: 2025-12-04T05:50:38.735Z
Learning: In zoneless Angular tests with OnPush components, calling `await runOnPushChangeDetection(fixture)` followed by `await fixture.whenStable()` is NOT redundant. The `runOnPushChangeDetection` helper forces change detection to run for OnPush components, while `whenStable()` waits for all async operations to complete. Both are necessary and serve different purposes.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-01T14:12:11.111Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1104
File: projects/element-ng/datepicker/components/si-day-selection.component.spec.ts:198-202
Timestamp: 2025-12-01T14:12:11.111Z
Learning: In zoneless Angular tests (using provideZonelessChangeDetection), prefer `await fixture.whenStable()` over `fixture.detectChanges()` after dispatching events or triggering async operations, as `whenStable()` waits for all asynchronous operations to complete and better mimics the production environment.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-08T11:25:51.584Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1163
File: projects/element-ng/select/si-select.component.spec.ts:511-516
Timestamp: 2025-12-08T11:25:51.584Z
Learning: In the select component overflow tests (projects/element-ng/select/si-select.component.spec.ts), jasmine.clock() cannot be used to control the timing for overflow item detection. Real setTimeout waits must be used instead, likely due to ResizeObserver or debounced layout calculations that jasmine.clock() cannot control in zoneless mode.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-15T10:05:59.100Z
Learnt from: chintankavathia
Repo: siemens/element PR: 1193
File: projects/element-ng/search-bar/si-search-bar.component.spec.ts:21-28
Timestamp: 2025-12-15T10:05:59.100Z
Learning: In tests that rely on Jasmine's fake clock to control time-based RxJS operators (e.g., debounceTime), use jasmine.clock().install() together with jasmine.clock().mockDate() to enable precise timing control. Without mockDate, scheduler-based operators may not respond to the fake clock. Apply this guidance to spec files (e.g., any file named *.spec.ts or *.spec.js) that test time-sensitive behavior, ensuring you install and mockDate before advancing time in tests.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts
📚 Learning: 2025-12-22T13:21:01.053Z
Learnt from: mistrykaran91
Repo: siemens/element PR: 1234
File: projects/element-ng/ag-grid/provider.ts:74-81
Timestamp: 2025-12-22T13:21:01.053Z
Learning: In the siemens/element repository, configuration providers use custom string tokens with useValue for consistency (e.g., provideSiDatatableConfig uses { provide: 'configuration', useValue: {...} }). The AG Grid provider follows this same pattern with { provide: 'ag-grid-options', useValue: provideGlobalGridOptions({...}) }.

Applied to files:

  • projects/dashboards-ng/src/components/grid/si-grid.component.ts
  • projects/dashboards-ng/src/model/si-widget-id-provider.ts
  • api-goldens/dashboards-ng/index.api.md
  • projects/dashboards-demo/src/app/app.config.ts
📚 Learning: 2025-12-23T09:24:35.163Z
Learnt from: spike-rabbit
Repo: siemens/element PR: 1235
File: api-goldens/dashboards-ng/translate/index.api.md:12-84
Timestamp: 2025-12-23T09:24:35.163Z
Learning: In the siemens/element repository, do not review files under the api-goldens/ directory (e.g., api-goldens/**/index.api.md) since they are auto-generated by API Extractor. Exclude these from review checks and focus on source files that are not auto-generated.

Applied to files:

  • api-goldens/dashboards-ng/index.api.md
📚 Learning: 2025-12-23T09:24:57.843Z
Learnt from: spike-rabbit
Repo: siemens/element PR: 1235
File: api-goldens/dashboards-ng/index.api.md:324-325
Timestamp: 2025-12-23T09:24:57.843Z
Learning: Do not review files in the api-goldens directory, as they are auto-generated API reports produced by API Extractor.

Applied to files:

  • api-goldens/dashboards-ng/index.api.md
📚 Learning: 2025-12-23T09:24:40.395Z
Learnt from: spike-rabbit
Repo: siemens/element PR: 1235
File: api-goldens/dashboards-ng/module-federation/index.api.md:7-11
Timestamp: 2025-12-23T09:24:40.395Z
Learning: In the siemens/element repository, do not review any files under the api-goldens directory, as they are auto-generated API reports produced by API Extractor. These MD files should be excluded from review unless explicitly overridden.

Applied to files:

  • api-goldens/dashboards-ng/index.api.md
🧬 Code graph analysis (7)
projects/dashboards-demo/src/app/app-widget-id-provider.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (129-191)
projects/dashboards-demo/src/app/app-widget-storage.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (129-191)
projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (4)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
  • SiDefaultWidgetStorage (94-153)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (129-191)
projects/dashboards-ng/src/model/configuration.ts (1)
  • SI_DASHBOARD_CONFIGURATION (21-27)
projects/dashboards-ng/src/components/grid/si-grid.component.ts (1)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (129-191)
projects/dashboards-demo/src/app/app.config.ts (2)
projects/dashboards-ng/src/model/si-widget-id-provider.ts (1)
  • SI_WIDGET_ID_PROVIDER (21-24)
projects/dashboards-demo/src/app/app-widget-id-provider.ts (1)
  • AppWidgetIdProvider (7-11)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)
projects/dashboards-ng/src/model/widgets.model.ts (1)
  • WidgetConfig (129-191)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (15)
projects/dashboards-ng/src/model/si-widget-storage.ts (1)

35-54: LGTM! Clean separation of modified and added widgets.

The updated save() signature clearly separates modifiedWidgets (existing widgets) from addedWidgets (new widgets with provider-generated IDs), improving clarity and making the persistence contract explicit. The documentation accurately describes the responsibility for backend ID reconciliation when applicable.

projects/dashboards-ng/src/public-api.ts (1)

15-15: LGTM! Public API surface correctly expanded.

The export of si-widget-id-provider makes the new provider infrastructure available to library consumers, enabling custom ID generation implementations as documented in the README.

projects/dashboards-ng/README.md (1)

187-232: LGTM! Comprehensive and accurate documentation.

The new "Widget instance ID generation" section effectively documents:

  • The default UUID-based ID generation using crypto.randomUUID()
  • How to implement custom ID providers by extending SiWidgetIdProvider
  • Provider registration for both standalone and module-based Angular applications
  • Correct method signature: generateWidgetId(widget: WidgetConfig, dashboardId?: string): string

The examples are clear and actionable for library consumers.

projects/dashboards-demo/src/app/app.config.ts (1)

39-39: LGTM! Provider registration correctly demonstrates custom ID provider usage.

The registration of AppWidgetIdProvider via SI_WIDGET_ID_PROVIDER follows Angular DI best practices and effectively demonstrates how library consumers can override the default ID generation behavior.

projects/dashboards-ng/src/model/si-widget-id-provider.ts (3)

21-24: LGTM! Injection token with sensible default factory.

The SI_WIDGET_ID_PROVIDER token correctly provides a default factory that injects SiWidgetDefaultIdProvider, ensuring the library works out-of-the-box while remaining extensible for custom implementations.


29-38: LGTM! Clean abstract contract for ID generation.

The abstract SiWidgetIdProvider class defines a clear, type-safe contract for widget ID generation. The signature generateWidgetId(widget: Omit<WidgetConfig, 'id'>, dashboardId?: string): string appropriately accepts widgets without IDs and optional dashboard context.


44-59: LGTM! Secure default implementation with comprehensive documentation.

The SiWidgetDefaultIdProvider uses crypto.randomUUID() to generate RFC4122 version 4 UUIDs with 122 bits of entropy, providing cryptographically secure uniqueness. The JSDoc accurately documents the algorithm and format. The implementation intentionally ignores the widget and dashboardId parameters, which is appropriate for a generic default that prioritizes uniqueness over context.

projects/dashboards-ng/src/components/grid/si-grid.component.spec.ts (3)

21-29: LGTM! Test imports updated for provider-based ID generation.

The imports correctly include SI_WIDGET_ID_PROVIDER to support testing the new provider-based ID generation infrastructure.


226-226: LGTM! Storage API call correctly uses new signature.

The save() call properly separates parameters: modifiedWidgets (containing the persisted widgetConfig), addedWidgets (empty array), removedWidgets (empty array), and dashboardId. This aligns with the new storage API contract.


236-280: LGTM! Comprehensive test coverage for custom ID provider integration.

The new test suite effectively validates:

  • Custom provider registration via SI_WIDGET_ID_PROVIDER
  • ID generation using the custom generateWidgetId implementation
  • Integration with the grid component's addWidgetInstance flow
  • Correct ID format: "custom-id-default" (matching the mock implementation)

The mock provider correctly implements the generateWidgetId(widget: WidgetConfig, dashboardId?: string) signature, addressing previous review feedback.

projects/dashboards-ng/src/components/grid/si-grid.component.ts (4)

26-26: LGTM! Provider injection correctly implemented.

The component properly imports and injects SI_WIDGET_ID_PROVIDER, enabling pluggable ID generation while respecting the DI token's default factory fallback.

Also applies to: 52-52


251-258: LGTM! Clean separation of transient and persisted widgets for save.

The implementation correctly:

  1. Updates widget positions before persistence
  2. Creates a Set of transient widget IDs for efficient lookup
  3. Filters modifiedWidgets (existing, non-transient)
  4. Filters addedWidgets (transient, newly added)
  5. Calls save() with the new signature: save(modifiedWidgets, addedWidgets, markedForRemoval, dashboardId())

This separation aligns perfectly with the updated storage API contract and enables backend systems to handle new vs. existing widgets differently (e.g., INSERT vs. UPDATE).


302-311: LGTM! ID generation correctly delegated to provider.

Line 303 properly delegates ID generation to widgetIdProvider.generateWidgetId(widgetInstanceConfig, this.dashboardId()), passing the widget config without an ID and the optional dashboard context. The generated ID is assigned to the new widget, which is then tracked in transientWidgetInstances for later separation during save.


319-333: LGTM! Transient widget removal correctly handled.

Lines 324-326 correctly check whether the widget being removed is transient (newly added, not persisted). Only non-transient widgets are added to markedForRemoval for backend deletion, preventing unnecessary removal operations for widgets that were never persisted.

projects/dashboards-demo/src/app/app-widget-storage.ts (1)

119-129: LGTM! Signature correctly updated to separate modified and added widgets.

The updated save method signature correctly splits widgets into modifiedWidgets and addedWidgets arrays, aligning with the new storage API. The call to super.save() properly passes all parameters through, and the delay behavior is preserved.

@github-actions
Copy link

Code Coverage

@timowolf timowolf merged commit f815092 into main Dec 29, 2025
11 checks passed
@timowolf timowolf deleted the feat/dashboards/widget-id-provider branch December 29, 2025 10:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-changes Marks issues and PRs that are breaking the API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants