Skip to content

[CSM Portal] Add dashboard builder UI - #1429

Merged
dilshanfardil merged 8 commits into
wso2-open-operations:mainfrom
rksk:feature/dashboard-builder-ui
Aug 12, 2026
Merged

[CSM Portal] Add dashboard builder UI#1429
dilshanfardil merged 8 commits into
wso2-open-operations:mainfrom
rksk:feature/dashboard-builder-ui

Conversation

@rksk

@rksk rksk commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Purpose

Describe the problems, issues, or needs driving this feature/fix and include links to related issues in the following format: Resolves issue1, issue2, etc.

We are adding an admin-only dashboard builder to the CSM portal so dashboards no longer require a hand-authored config file round-trip to preview or iterate on. Today dashboard layout/widget config is static and not editable from the app at all; this adds the missing authoring surface.

Goals

Describe the solutions that this feature/fix will introduce to resolve the problems described above

  • Let an admin create a new dashboard or open an existing one for editing, from /admin/user-management's sibling "Dashboards" tab.
  • Add/remove widgets and sections on a dashboard, with a settings control on each widget.
  • Configure each widget's resource type, shape, filters, group-by, and other params through a proper filter-condition editor, not raw JSON.
  • For list-shape widgets, configure which columns are shown (path/label/format), reusing the already-shipped BeDashboardWidget.columns / GenericColumnList rendering path that existed but was previously unreachable from any UI.
  • Preview a widget's real data before saving, using the same rendering path the live dashboard uses — full modal width for list-shape previews so a real table is actually readable while editing.
  • Warn clearly when a local draft has diverged from what's actually deployed.
  • Regroup Settings navigation: Users/Roles/Groups/Teams/Permissions now nest under a new "User management" tab, with "Dashboards" as its sibling — was a flat 6-tab row, now a two-level structure. Old /admin/{users,roles,groups,teams,permissions} paths redirect to their new /admin/user-management/* locations.

Approach

Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email documentation@wso2.com to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here.

New features/csm-admin/dashboards/ module: a list page, an editor page, a widget-editor dialog with a filter-condition editor, and a localStorage-backed draft store (no backend change — dashboard config is not runtime-editable server-side yet, so this is intentionally scoped to the authoring/preview layer only). Access to the new routes is frontend-gated to the admin role via a route guard (the existing /admin section has no backend authorization to fall back on either, so this matches the section's current posture rather than introducing a new gap).

The dashboard-render grid was extracted from AgentsLandingPagePilot into a reusable DashboardWidgetGrid component so the builder overlays edit/remove affordances on the exact same grid the live dashboard renders with, instead of forking a second renderer. The widget editor's "Preview" button renders the in-progress widget config through the real DashboardWidgetTile, so preview data is genuine, not mocked.

Grown across three follow-up commits (bug fixes from a pre-PR review pass, column config + preview-width, and the Settings nav regroup): 31 files, 4 over CodeRabbit's ~300-line line-by-line threshold (WidgetEditorDialog.tsx/.test.tsx, CsmDashboardBuilderEditorPage.tsx/.test.tsx) — all core editor surfaces for one cohesive feature; didn't split cleanly without leaving partial/broken functionality across PRs. Expect summary-level review on those four.

The Settings nav change also moves 5 existing routes to a new /admin/user-management/* prefix, with <Navigate replace> redirects from the old paths so no existing bookmark/deep-link breaks.

Known limitation: the filter-condition editor only offers eq/in operators for non-case resource types (incidents, accounts, etc.). Those endpoints' search payloads are bespoke, flat, named-key shapes with no proven notIn/gte/lte/isEmpty convention anywhere in this codebase today, so rather than invent one, the editor restricts to what's actually provable. case (and its type variants) keep the full operator set via the existing generic filter DSL. Widening this needs the real query convention confirmed against the backend first.

User stories

Summary of user stories addressed by this change

As an admin, I can build and preview a CSM dashboard's layout and widgets without hand-editing a config file, and I can tell at a glance whether my draft matches what's actually live.

Release note

Brief description of the new feature or bug fix as it will appear in the release notes

Added an admin-only dashboard builder under Admin > Dashboards, with per-widget filter configuration and live preview.

Documentation

N/A — internal admin tooling, no end-user-facing product documentation impact.

Training

N/A — no training content affected.

Certification

N/A — no certification exam impact.

Marketing

N/A — internal admin tooling, not a marketed feature.

Automation tests

  • Unit tests

    11 new/updated test files (65 tests) covering the draft store, drift detection, filter-condition serialization, access check, route guard, list page, editor page, and widget editor dialog — including an assertion that Preview issues the exact POST /cases/search request body the drafted filters imply.

  • Integration tests

    Not run against a live backend — this webapp has no local/mock backend mode (requires a real OIDC IdP). Covered instead by RTL tests mocking the API client at the same call sites the live dashboard uses.

Security checks

  • Followed secure coding standards in http://wso2.com/technical-reports/wso2-secure-engineering-guidelines? yes
  • Ran FindSecurityBugs plugin and verified report? N/A — TypeScript/React change, no Java/JVM code; eslint and tsc -b ran clean instead.
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets? yes

Samples

N/A — no new sample apps or code samples included.

Related PRs

None.

Migrations (if applicable)

N/A — no data or schema migrations; storage is client-side localStorage only.

Test environment

Node.js webapp toolchain (Vite/vitest/tsc/eslint) run locally in a git worktree; no browser/OS/DB matrix applicable — no backend or database involved in this change.

Learning

N/A.

Summary by CodeRabbit

  • New Features
    • Added an admin dashboard builder for creating, editing, previewing, and autosaving drafts.
    • Added widget configuration for filters, charts, lists, sections, layouts, and previews.
    • Added dashboard listing with draft status, continuation, and discard options.
    • Added responsive widget rendering and section refresh controls.
  • Improvements
    • Organized administration pages under User management with legacy redirects.
    • Added nested navigation and deeper quick-navigation entries.
    • Restricted dashboard-builder access to administrators.
  • Bug Fixes
    • Preserved compatibility with existing drafts and legacy filter operators.

rksk added 2 commits August 11, 2026 21:55
Lets an admin create or edit a dashboard: add/remove widgets and
sections, configure each widget's filters/shape/params through a
condition editor, and preview a widget's real data before saving by
reusing the same DashboardWidgetTile the live dashboard renders with.

Drafts persist to localStorage only; there is no backend change, so a
drift banner flags when a draft has diverged from what GET
/dashboards/{id} currently serves. Access is frontend-gated to the
admin role via a route guard, since there is no privileged backend
endpoint behind this feature to enforce anything server-side.

Extracts DashboardWidgetGrid out of AgentsLandingPagePilot so the
builder can overlay edit/remove affordances on the same grid the live
dashboard uses, instead of forking a separate renderer.
- widgetQueryConditions: restrict operators to eq/in for non-case
  resourceTypes (no other op has a proven query shape anywhere in this
  app), and type-recover boolean/numeric scalar values on write instead
  of always stringifying them.
- WidgetEditorDialog: thread selectedTeamGroupId/selectedTeamLabel into
  the Preview tile so a team-scoped widget previews the same way it
  renders on the live dashboard; guard the Row limit field against
  NaN/negative/decimal input the way Grid width already is.
- CsmDashboardBuilderEditorPage: gate the drift banner on the live
  GET /dashboards/{id} fetch actually resolving (was flashing "not yet
  deployed" on every open while the fetch was in flight), add a distinct
  notice for a failed fetch, resolve real team context for previewing,
  and stop the autosave effect from re-stamping updatedAt on an
  unmodified draft's first load.
- DashboardWidgetGrid: pass the section's raw (unresolved) key to
  renderSectionActions alongside the display-resolved title, so editing
  or removing a placeholder-named section (e.g. "{{currentTeam}}
  Escalations") no longer splits it into two sections.
- Remove the unused useSaveDashboardDraft export.

Regression tests added for each fix.
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8cb880aa-e625-43ef-b4f8-2889c5685db3

📥 Commits

Reviewing files that changed from the base of the PR and between 070b263 and dfd6f63.

📒 Files selected for processing (1)
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.test.tsx

📝 Walkthrough

Walkthrough

The web application adds an admin dashboard builder with local drafts, widget editing, previews, access control, and dashboard routes. Administration navigation now uses nested user-management tabs, recursive quick navigation, and legacy redirects. Dashboard rendering is shared through DashboardWidgetGrid.

Changes

Admin dashboard builder and navigation

Layer / File(s) Summary
Nested administration navigation
apps/csm-portal/webapp/public/config.js.example, apps/csm-portal/webapp/src/config/*, apps/csm-portal/webapp/src/components/section-tabs/SectionTabs.tsx, apps/csm-portal/webapp/src/features/csm-admin/pages/*, apps/csm-portal/webapp/src/App.tsx
Administration pages now use an admin.user-management hierarchy. Nested tabs use the secondary variant. Quick navigation includes enabled descendants recursively. Legacy administration paths redirect to the new routes.
Dashboard builder access and routes
apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/DashboardBuilderRouteGuard.tsx, apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardBuilderAccess.ts, apps/csm-portal/webapp/src/App.tsx
Dashboard builder routes are lazy-loaded and protected by a case-insensitive admin role check. Loading, denied, and profile-error states are covered by tests.
Draft persistence and drift comparison
apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDraftsStorage.ts, apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDrift.ts
Dashboard drafts persist in localStorage, receive generated IDs and timestamps, synchronize through storage events, and compare against deployable dashboard fields for drift.
Shared dashboard grid rendering
apps/csm-portal/webapp/src/features/csm-dashboard/components/DashboardWidgetGrid.tsx, apps/csm-portal/webapp/src/features/csm-dashboard/utils/dashboardWidgetGridLayout.ts, apps/csm-portal/webapp/src/features/csm-dashboard/components/AgentsLandingPagePilot.tsx
DashboardWidgetGrid groups and renders widgets by section, supports responsive layouts and refresh actions, and replaces local rendering logic in AgentsLandingPagePilot.
Widget filters and editor
apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/widgetQueryConditions.ts, apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetFilterConditionEditor.tsx, apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.tsx
Widget editing supports resource-specific filters, validation, metadata, shape-dependent settings, preview requests, column configuration, and save, edit, and delete actions.
Dashboard list and draft editor
apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderListPage.tsx, apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderEditorPage.tsx
The builder lists deployed and local drafts, creates and edits drafts, autosaves changes, detects drift, manages sections and widgets, selects team previews, and exports deployable JSON.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Admin
  participant Router
  participant DashboardBuilderRouteGuard
  participant CsmDashboardBuilderListPage
  participant CsmDashboardBuilderEditorPage
  participant LocalStorage
  Admin->>Router: Open dashboard builder
  Router->>DashboardBuilderRouteGuard: Check route access
  DashboardBuilderRouteGuard->>CsmDashboardBuilderListPage: Render authorized route
  CsmDashboardBuilderListPage->>CsmDashboardBuilderEditorPage: Open or create draft
  CsmDashboardBuilderEditorPage->>LocalStorage: Load and autosave draft
  LocalStorage-->>CsmDashboardBuilderEditorPage: Return draft state
Loading

Possibly related PRs

Suggested labels: Type/New Feature

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: adding the CSM Portal dashboard builder UI.
Description check ✅ Passed The description covers the required sections, feature scope, implementation, testing, security, limitations, and documentation impact.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@rksk rksk changed the title [CSM Portal] Add admin-only dashboard builder UI under /admin/dashboards [CSM Portal] Add dashboard builder UI Aug 11, 2026
rksk added 2 commits August 11, 2026 23:48
…lumns

Preview no longer caps a list-shape widget to a fixed 420px box (it now
fills the dialog's own content width, matching how DashboardWidgetGrid
already spans a list tile the full row); count/pie/bar previews keep the
compact cap since a big number or small chart shouldn't stretch edge to
edge.

Adds a Columns section to the widget editor for shape "list" (hidden for
count/pie), letting the admin configure the same path/label/format rows
BeDashboardWidget.columns already supports on the wire but that only
DashboardWidgetGrid's real render path exercised until now. buildWidget()
omits `columns` entirely (not `[]`) when no rows are configured, matching
the "absent is a no-op" convention documented on the type and relied on by
DashboardWidgetGrid's own `columns={widget.columns}` passthrough. The
configured columns are also threaded into the Preview's DashboardWidgetTile
call so previewing exercises GenericColumnList for real instead of the
hardcoded per-resourceType fallback.
…r a "User management" tab, sibling to Dashboards

Restructures the Settings ("admin") section's flat 6-tab strip into two levels:
a top-level [User management, Dashboards] strip, with User management owning
its own five sub-tabs. The nav tree (csmNavItems.ts) and its consumers
(useRouteTabs, featureFlags.ts) already modelled an arbitrary-depth tree, so
the extension is mostly additive: useRouteTabs resolves the nested node's
children the same way it resolves the top level, just called again with the
nested id; SectionTabs gains a "secondary" variant for the visually
subordinate nested strip; featureFlags.ts's navigableNavNodes now recurses so
the five directory pages stay reachable from Quick-nav.

Routes move from /admin/<page> to /admin/user-management/<page>; the five old
paths get `<Navigate replace>` redirects so existing deep links/bookmarks
still resolve (a judgment call, flagged for easy revert). The
/admin/<kind>/:id member-detail routes and every `routeBase="/admin/roles"`-
style literal elsewhere in the app are left untouched, since they still work
through the added redirects.

Renames the affected nav-node ids (admin.users -> admin.user-management.users
etc.) — a breaking change to the CSM_PORTAL_FEATURE_OVERRIDES contract,
addressed by updating config.js.example's example/reference; no other
override reference to these ids was found in the repo.
@rksk
rksk marked this pull request as ready for review August 11, 2026 18:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (3)
apps/csm-portal/webapp/src/features/csm-admin/pages/CsmAdminLayout.test.tsx (1)

31-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the destination pathname for navigation.

Outlet content does not prove the final URL. Add a useLocation() destination probe. In the layout test, click primary and secondary tabs and assert the pathname. In the redirect test, assert each legacy path resolves to its expected new pathname.

  • apps/csm-portal/webapp/src/features/csm-admin/pages/CsmAdminLayout.test.tsx#L31-L46: add a destination probe and interactive tab-navigation assertions.
  • apps/csm-portal/webapp/src/features/csm-admin/pages/adminRoutes.redirects.test.tsx#L41-L84: add a destination probe and assert each redirect target pathname.

As per coding guidelines: test actual navigation with <Routes> and a destination probe using useLocation().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/csm-portal/webapp/src/features/csm-admin/pages/CsmAdminLayout.test.tsx`
around lines 31 - 46, Update
apps/csm-portal/webapp/src/features/csm-admin/pages/CsmAdminLayout.test.tsx
lines 31-46 by adding a useLocation-based destination probe, then click primary
and secondary tabs and assert their resulting pathnames while retaining the
existing Routes setup. Update
apps/csm-portal/webapp/src/features/csm-admin/pages/adminRoutes.redirects.test.tsx
lines 41-84 with the same destination probe and assert that every legacy route
resolves to its expected new pathname.

Source: Coding guidelines

apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.test.tsx (1)

341-365: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The max-width: 420px assertion cannot fail.

The dialog sets the cap through the sx prop. Oxygen UI (MUI) compiles sx to a generated CSS class, not an inline style attribute. container.querySelector('[style*="max-width: 420px"]') therefore returns null for both the list shape and the count shape. The test passes even if the regression it guards returns.

Assert the resolved style instead, or assert a stable marker the component sets for the full-width case.

♻️ Proposed assertion using the computed style
-    // No element in the dialog carries the old fixed-width cap any more —
-    // a list-shape preview now sizes to the dialog's own content width
-    // instead.
-    expect(container.querySelector('[style*="max-width: 420px"]')).toBeNull();
+    // No element in the dialog carries the old fixed-width cap any more —
+    // a list-shape preview now sizes to the dialog's own content width
+    // instead. `sx` compiles to a class, so read the resolved style rather
+    // than the inline `style` attribute.
+    const capped = Array.from(container.querySelectorAll<HTMLElement>("*")).filter(
+      (el) => window.getComputedStyle(el).maxWidth === "420px",
+    );
+    expect(capped).toHaveLength(0);

This test also re-implements renderDialog inline to reach container. Return the render result from renderDialog and reuse it here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.test.tsx`
around lines 341 - 365, Update the list-shape preview test to verify the
resolved width behavior rather than searching for an inline max-width style, so
it fails if the 420px cap returns; preferably assert a stable full-width marker
if the component provides one. Refactor renderDialog to return the render
result, then reuse its container in this test instead of duplicating the
provider setup.
apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderEditorPage.test.tsx (1)

164-169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This waitFor does not wait for the resolved fetch.

getMock was already called during the initial render, so expect(getMock).toHaveBeenCalled() is satisfied on the first tick. The assertions at Lines 168-169 can therefore run before React Query commits the resolved data and before the drift memo recomputes. The test asserts the pre-resolution state a second time rather than the post-resolution state.

Wait for an observable effect of the resolution instead.

♻️ Proposed fix to wait for the settled query
     resolveGet({ ...draft, sourceDashboardId: undefined });
-    await waitFor(() => expect(getMock).toHaveBeenCalled());
-    expect(screen.queryByText(/not yet deployed/i)).not.toBeInTheDocument();
-    expect(screen.queryByText(/differs from what/i)).not.toBeInTheDocument();
+    // The "couldn't check" notice is the only banner that could appear
+    // once the query settles — assert the settled state directly rather
+    // than a call count that was already satisfied on mount.
+    await waitFor(() => {
+      expect(screen.queryByText(/couldn't check/i)).not.toBeInTheDocument();
+      expect(screen.queryByText(/not yet deployed/i)).not.toBeInTheDocument();
+      expect(screen.queryByText(/differs from what/i)).not.toBeInTheDocument();
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderEditorPage.test.tsx`
around lines 164 - 169, Update the test around resolveGet in
CsmDashboardBuilderEditorPage so waitFor observes the resolved query state
rather than merely confirming the already-invoked getMock. Wait for a
post-resolution UI effect or settled query result before asserting that both
drift messages are absent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.tsx`:
- Around line 189-193: Update handleResourceTypeChange to clear columnDrafts
when the resource type changes, alongside the existing condition and slice-draft
resets. Ensure resource-specific column paths and headers cannot persist across
resource-type switches.

In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderEditorPage.tsx`:
- Around line 172-177: Update the autosave effect around saveDashboardDraft and
its cleanup so unmount flushes the latest working value immediately before
clearing the debounce timer. Preserve debounced saves during normal editing and
ensure the cleanup uses the current working state without creating duplicate
saves.

In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderListPage.tsx`:
- Around line 118-121: Update CsmDashboardBuilderListPage so draftIds.has(d.id)
alone does not render deployment-drift warning styling or text; use the existing
drift comparison to show the warning only for materially changed drafts, or
otherwise present neutral local-draft messaging. In
apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderListPage.test.tsx
lines 113-130, seed a materially changed draft for the warning case and add an
exact-match draft case that renders no warning.

In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDraftsStorage.ts`:
- Around line 68-75: Update isDraft to validate every required DashboardDraft
field, including isDefault, isTeamBased, emptySections, and updatedAt, before
accepting a stored record. In
apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDraftsStorage.ts
lines 68-75, preserve existing checks and add type-appropriate validation. In
apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDraftsStorage.test.ts
lines 111-115, add a syntactically valid but incomplete draft and verify
listDashboardDrafts ignores it without throwing.

In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDrift.ts`:
- Around line 89-91: Update isDraftDrifted to return true when
draft.sourceDashboardId is absent, before checking live or comparing canonical
shapes. Add a dashboardDrift.test.ts case with no sourceDashboardId and a
matching live dashboard, asserting the draft is considered drifted.

In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/widgetQueryConditions.test.ts`:
- Around line 152-155: Update queryFromFilterConditions and the preview/save
flows using it so unsupported legacy operators such as notIn are never rewritten
as equality filters. Preserve the original query semantics until the user
selects eq or in, or reject the operation through validation; ensure both
preview and handleSave apply the same behavior. Update the affected test to
verify the chosen behavior.

In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/widgetQueryConditions.ts`:
- Around line 183-193: Verify the actual non-case widget query shape against the
search payload types and widget search-call construction before relying on flat
top-level mapping. In the query-to-filter conversion near
queryFromFilterConditions, skip entries whose values are non-scalar and not
arrays of scalars, rather than stringifying objects; preserve scalar and
scalar-array handling, and align the mapping with the confirmed nested or flat
contract so saving a widget cannot overwrite filters with "[object Object]".
- Around line 113-121: Update coerceScalar so numeric strings are converted only
when Number conversion is lossless: reject leading-zero integer representations
and values outside the safe-integer range, and require the numeric value to
round-trip to the original normalized string before returning it. Preserve
boolean coercion and return the original string for identifier-like or otherwise
non-lossless values.

In `@apps/csm-portal/webapp/src/features/csm-admin/pages/CsmAdminLayout.test.tsx`:
- Around line 25-29: In
apps/csm-portal/webapp/src/features/csm-admin/pages/CsmAdminLayout.test.tsx#L25-L29
and
apps/csm-portal/webapp/src/features/csm-admin/pages/adminRoutes.redirects.test.tsx#L31-L39,
add top-level mocks for `@api/backend/client` and `@config/apiConfig` before
importing CsmAdminLayout or other transitively dependent modules; ensure the
config mock exports apiConfig.backendUrl as "https://example.test" and apply the
same setup in both test files.

---

Nitpick comments:
In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.test.tsx`:
- Around line 341-365: Update the list-shape preview test to verify the resolved
width behavior rather than searching for an inline max-width style, so it fails
if the 420px cap returns; preferably assert a stable full-width marker if the
component provides one. Refactor renderDialog to return the render result, then
reuse its container in this test instead of duplicating the provider setup.

In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderEditorPage.test.tsx`:
- Around line 164-169: Update the test around resolveGet in
CsmDashboardBuilderEditorPage so waitFor observes the resolved query state
rather than merely confirming the already-invoked getMock. Wait for a
post-resolution UI effect or settled query result before asserting that both
drift messages are absent.

In `@apps/csm-portal/webapp/src/features/csm-admin/pages/CsmAdminLayout.test.tsx`:
- Around line 31-46: Update
apps/csm-portal/webapp/src/features/csm-admin/pages/CsmAdminLayout.test.tsx
lines 31-46 by adding a useLocation-based destination probe, then click primary
and secondary tabs and assert their resulting pathnames while retaining the
existing Routes setup. Update
apps/csm-portal/webapp/src/features/csm-admin/pages/adminRoutes.redirects.test.tsx
lines 41-84 with the same destination probe and assert that every legacy route
resolves to its expected new pathname.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ae49a244-6a0a-4c4d-b566-938fbc5e4499

📥 Commits

Reviewing files that changed from the base of the PR and between c858392 and 1bd1d7e.

📒 Files selected for processing (31)
  • apps/csm-portal/webapp/public/config.js.example
  • apps/csm-portal/webapp/src/App.tsx
  • apps/csm-portal/webapp/src/components/section-tabs/SectionTabs.tsx
  • apps/csm-portal/webapp/src/config/csmNavItems.test.ts
  • apps/csm-portal/webapp/src/config/csmNavItems.ts
  • apps/csm-portal/webapp/src/config/featureFlags.test.ts
  • apps/csm-portal/webapp/src/config/featureFlags.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetFilterConditionEditor.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetFilterConditionEditor.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderEditorPage.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderEditorPage.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderListPage.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderListPage.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/DashboardBuilderRouteGuard.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/DashboardBuilderRouteGuard.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardBuilderAccess.test.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardBuilderAccess.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDraftsStorage.test.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDraftsStorage.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDrift.test.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDrift.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/widgetQueryConditions.test.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/widgetQueryConditions.ts
  • apps/csm-portal/webapp/src/features/csm-admin/pages/CsmAdminLayout.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/pages/CsmAdminLayout.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/pages/adminRoutes.redirects.test.tsx
  • apps/csm-portal/webapp/src/features/csm-dashboard/components/AgentsLandingPagePilot.tsx
  • apps/csm-portal/webapp/src/features/csm-dashboard/components/DashboardWidgetGrid.tsx
  • apps/csm-portal/webapp/src/features/csm-dashboard/utils/dashboardWidgetGridLayout.ts

rksk added 2 commits August 12, 2026 00:30
Replace the free-text column path input with a freeSolo autocomplete
offering the actual attribute paths discovered by sampling the widget's
own Preview response, so an admin picks a real field instead of typing
a path blind. Falls back to a plain text field with a helper hint until
Preview has been run once, and free text remains accepted so a rare
field absent from every sampled row is still reachable.

The discovery query reuses useWidgetData with the same arguments the
Preview tile passes internally, so it shares that tile's TanStack Query
cache entry rather than firing a second network request.
… PR wso2-open-operations#1429

Fixes several correctness/crash bugs flagged in review:
- Flush a still-pending autosave on unmount instead of discarding it, so an
  edit made just before navigating away isn't silently lost (this feature
  has no backend copy).
- Only warn about a local dashboard draft when it actually differs from
  what's deployed (reusing isDraftDrifted), instead of flagging any draft's
  mere existence.
- Validate every required DashboardDraft field in the localStorage read
  path's type guard, so an incomplete stored record is dropped instead of
  crashing the drafts list sort.
- isDraftDrifted now reports drift for a draft with no sourceDashboardId
  even when a live dashboard happens to be passed in, matching its own doc
  comment.
- Non-case widget filter rows with an operator the flat search contract
  can't express are dropped on save rather than silently reinterpreted as
  eq, which used to flip a legacy notIn filter's real meaning.
- coerceScalar only converts a numeric-looking string when it round-trips
  back to the exact same text, so a leading-zero identifier or a value
  above Number.MAX_SAFE_INTEGER is kept as a string instead of corrupted.
- Clarified the widgetQueryConditions.ts doc comment: a widget's own query
  is flat and maps onto the search payload's inner filters object, not a
  second nested filters key.
- Added the @api/backend/client / @config/apiConfig mocks to
  CsmAdminLayout.test.tsx and adminRoutes.redirects.test.tsx per this
  repo's existing convention for anything transitively importing
  CsmAdminLayout.
@rksk

rksk commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDraftsStorage.test.ts (1)

75-78: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the ordering test deterministic.

setTimeout(2) does not guarantee that the two calls to saveDashboardDraft receive different updatedAt values. If both values are equal, the sort can preserve insertion order and the assertion can fail even when the implementation is correct. Use a controlled clock or a test-only timestamp seam instead of a wall-clock delay.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDraftsStorage.test.ts`
around lines 75 - 78, Make the “lists drafts most-recently-updated first” test
deterministic by replacing the setTimeout delay with a controlled clock or
test-only timestamp seam around saveDashboardDraft. Ensure the older draft
receives an earlier updatedAt than the newer draft, while preserving the
existing ordering assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.tsx`:
- Around line 196-201: Update handleResourceTypeChange to clear previewSnapshot
alongside the existing resource type, condition, slice, and column state resets.
Ensure switching resource types requires a new preview before useWidgetData
derives discoveredColumnPaths for the new resource type.

---

Outside diff comments:
In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDraftsStorage.test.ts`:
- Around line 75-78: Make the “lists drafts most-recently-updated first” test
deterministic by replacing the setTimeout delay with a controlled clock or
test-only timestamp seam around saveDashboardDraft. Ensure the older draft
receives an earlier updatedAt than the newer draft, while preserving the
existing ordering assertion.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9c573d87-842b-40e4-9d3b-a0a46fe4bdf7

📥 Commits

Reviewing files that changed from the base of the PR and between 1bd1d7e and a9ab918.

📒 Files selected for processing (16)
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderEditorPage.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderEditorPage.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderListPage.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderListPage.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDraftsStorage.test.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDraftsStorage.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDrift.test.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDrift.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/discoverAttributePaths.test.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/discoverAttributePaths.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/widgetQueryConditions.test.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/widgetQueryConditions.ts
  • apps/csm-portal/webapp/src/features/csm-admin/pages/CsmAdminLayout.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/pages/adminRoutes.redirects.test.tsx
🚧 Files skipped from review as they are similar to previous changes (11)
  • apps/csm-portal/webapp/src/features/csm-admin/pages/CsmAdminLayout.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDrift.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderListPage.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDrift.test.ts
  • apps/csm-portal/webapp/src/features/csm-admin/pages/adminRoutes.redirects.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/widgetQueryConditions.test.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderEditorPage.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderEditorPage.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/utils/dashboardDraftsStorage.ts
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/pages/CsmDashboardBuilderListPage.test.tsx

…ype change

Switching resourceType left previewSnapshot pointing at the old resource
type's preview data, so discoveredColumnPaths kept offering paths (e.g.
project.key) that don't resolve under the new resourceType. Clear
previewSnapshot alongside the existing conditions/slices/columns resets
in handleResourceTypeChange, requiring a fresh Preview before any column
paths are offered again.

Addresses CodeRabbit finding on PR wso2-open-operations#1429 (discussion_r3761150228).
@rksk

rksk commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.test.tsx`:
- Around line 494-500: Update the resourceType-switch test around the combobox
interaction to wrap the postMock no-refetch assertion in await waitFor. Keep the
existing expectation that switching to “incident” does not trigger a Preview
request, while allowing promise-based TanStack Query work to settle before
asserting.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9503388d-e64d-4f5b-9528-1530722794ac

📥 Commits

Reviewing files that changed from the base of the PR and between a9ab918 and 070b263.

📒 Files selected for processing (2)
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.test.tsx
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/csm-portal/webapp/src/features/csm-admin/dashboards/components/WidgetEditorDialog.tsx

…n resourceType change

`fireEvent` only flushes the synchronous React commit, not TanStack Query's
microtask-scheduled fetch machinery, so the prior bare synchronous
`expect(postMock).not.toHaveBeenCalled()` right after the resourceType
switch could pass even if a regression reintroduced a delayed refetch.

A bare `await waitFor(() => expect(postMock).not.toHaveBeenCalled())` isn't
a fix either: waitFor only retries while the assertion keeps failing, so an
already-true negative assertion resolves on the first check and never gives
a delayed call a chance to land. Instead, force a real macrotask turn
(act-wrapped, to avoid an unwrapped-update warning) before asserting, which
fully drains any pending microtask-queued query work ahead of it. Verified
this actually catches a reintroduced delayed-refetch regression before
reverting the probe.
@rksk

rksk commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

@dilshanfardil
dilshanfardil merged commit 03548e3 into wso2-open-operations:main Aug 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants