Skip to content

COO-1545: Backport customizable dashboards#775

Open
PeterYurkovich wants to merge 4 commits intoopenshift:release-coo-0.4from
PeterYurkovich:backport-dashboards-to-coo-0.4
Open

COO-1545: Backport customizable dashboards#775
PeterYurkovich wants to merge 4 commits intoopenshift:release-coo-0.4from
PeterYurkovich:backport-dashboards-to-coo-0.4

Conversation

@PeterYurkovich
Copy link
Contributor

@PeterYurkovich PeterYurkovich commented Feb 17, 2026

This PR looks to backport the customizable dashboards available on main to the release-coo-0.4 branch, which still uses Patternfly 5. Most of the components have been kept as close as possible to their current state on the main branch, with some changes needed for the translation to PF-5 and how the monitoring plugin codebase is structured on release-coo-0.4.

Demo

Screencast.From.2026-02-18.10-55-14.mp4

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Feb 17, 2026
@openshift-ci-robot
Copy link

openshift-ci-robot commented Feb 17, 2026

@PeterYurkovich: This pull request references COO-1545 which is a valid jira issue.

Details

In response to this:

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds a Perses-based dashboards feature with new UI components, APIs, permissions, theming, and DevSpace development artifacts; restructures routing and build tooling, introduces many new React modules, removes legacy skeleton and plugin loader, and updates packaging and webpack configuration.

Changes

Cohort / File(s) Summary
DevSpace Development Configuration
/.gitignore, Dockerfile.devspace, Makefile, devspace-deployment.yaml, devspace.yaml, devspace_start.sh
New DevSpace files, Make target, Dockerfile and ignore entries to support containerized dev environment and Helm-based dev deployment.
Console Plugin Routing Patch
config/perses-dashboards.patch.json
Updated console routes and components for dashboards list/view; removed some access flag blocks and added dashboard view routes.
Dashboard Core & App Shell
web/src/components/dashboards/perses/dashboard-page.tsx, web/src/components/dashboards/perses/dashboard-app.tsx, web/src/components/dashboards/perses/dashboard-frame.tsx
New page, application, and frame components handling dashboard lifecycle, edit mode, URL sync, and layout.
Dashboard List & UI
web/src/components/dashboards/perses/dashboard-list-page.tsx, web/src/components/dashboards/perses/dashboard-list-frame.tsx, web/src/components/dashboards/perses/dashboard-list.tsx
New list page and frame plus a full DashboardList component with filtering, sorting, pagination, and per-row actions.
Headers, Padding & Empty States
web/src/components/dashboards/perses/dashboard-header.tsx, web/src/components/dashboards/perses/dashboard-page-padding.tsx, web/src/components/dashboards/perses/emptystates/*
New header components, PagePadding helper, and empty-state updates to use EmptyStateHeader.
Action Modals & Validation
web/src/components/dashboards/perses/dashboard-action-modals.tsx, web/src/components/dashboards/perses/dashboard-action-validations.ts
Added Rename/Duplicate/Delete modals and zod-based validation schemas (including uniqueness checks).
Toolbar & Permissions
web/src/components/dashboards/perses/dashboard-toolbar.tsx, web/src/components/dashboards/perses/dashboard-permissions.ts
New permission-aware toolbar, Edit button and hook to compute edit permissions via access reviews.
Dashboard API & Utilities
web/src/components/dashboards/perses/dashboard-api.ts, web/src/components/dashboards/perses/dashboard-utils.ts, web/src/components/dashboards/perses/dashboard-create-dialog.tsx
Create/update/delete mutations, list hook, name-generation and createNewDashboard utility, and create-dialog UI.
Perses Integration & Theming
web/src/components/dashboards/perses/PersesWrapper.tsx, web/src/perses-config.ts, web/src/index.d.ts, web/src/components/dashboards/perses/ToastProvider.tsx
Theme/token remapping, plugin asset globals, ToastProvider and pluginLoader changes (remote loader instantiation).
Perses Client & Hooks
web/src/components/dashboards/perses/perses-client.ts, web/src/components/dashboards/perses/hooks/usePerses.ts, web/src/components/dashboards/perses/hooks/useDashboardsData.ts, web/src/components/dashboards/perses/datasource-api.ts
Added project-scoped fetch, refactored hooks for project support and memoization, converted datasource methods to async arrow functions.
Project Selection & Nav
web/src/components/dashboards/perses/project/ProjectBar.tsx, .../ProjectDropdown.tsx, .../ProjectMenuToggle.tsx, .../useActiveProject.tsx, .../utils.ts, .../ProjectDropdown.scss
Project bar/dropdown changes: "All Projects" option, nullable activeProject state, URL sync and styling adjustments.
New UI Subcomponents
web/src/components/dashboards/perses/dashboard-header.tsx, web/src/components/dashboards/perses/dashboard-list-frame.tsx, web/src/components/dashboards/perses/dashboard-page-padding.tsx, web/src/components/dashboards/perses/dashboard-frame.tsx
Multiple new supporting components for list/header/layout and page composition.
Build, Package & Tooling
web/package.json, web/webpack.config.ts, web/.swcrc
Updated dependencies (Perses pre-releases, PatternFly data-view, loaders), exposed new modules, DefinePlugin PERSES_PROXY_BASE_URL, switched to swc/esbuild loaders and added SWC config.
Deleted / Removed
web/src/components/dashboards/perses/dashboard-skeleton.tsx, web/src/components/dashboards/perses/persesPluginsLoader.tsx, web/src/components/dashboards/perses/perses-dashboards.tsx
Removed legacy dashboard skeleton, dynamic plugin loader file, and PersesBoard wrapper component.
Cypress Fixture
web/cypress/fixtures/cmo/disable-monitoring.yaml
Added fixture to mark cluster-monitoring-operator unmanaged for tests.
Misc small changes
web/src/components/metrics/promql-expression-input.tsx, web/src/components/dashboards/shared/dashboard-dropdown.tsx, web/src/components/dashboards/perses/perses/datasource-cache-api.ts
Minor type relaxation, small style/class tweaks, and import reformatting.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci openshift-ci bot requested review from jgbernalp and kyoto February 17, 2026 19:27
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 17, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: PeterYurkovich

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Feb 17, 2026
@PeterYurkovich
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 15

Caution

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

⚠️ Outside diff range comments (3)
web/webpack.config.ts (1)

105-128: ⚠️ Potential issue | 🟡 Minor

Configure SWC-loader for development builds.

The dev build uses swc-loader without any inline options and there is no .swcrc file or swc configuration in package.json. SWC-loader requires explicit TypeScript/TSX parsing configuration to function correctly. Either add a .swcrc file, add swc configuration to package.json, or specify loader options inline. The production build's esbuild-loader is configured with explicit options and will work as-is.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/webpack.config.ts` around lines 105 - 128, The development branch
currently unshifts a rule using 'swc-loader' into config.module.rules without
any options, but SWC needs explicit parser config for TypeScript/TSX; update the
rule (the object added in the else branch that matches /\.(jsx?|tsx?)$/ and uses
loader: 'swc-loader') to include an options object (or point to a .swcrc) that
enables jsc.parser.syntax = 'typescript', jsc.parser.tsx = true for TSX support
(and jsc.target or transform.react settings as needed), so SWC can correctly
parse and transpile TS/TSX during development.
web/src/components/dashboards/perses/datasource-api.ts (1)

47-63: ⚠️ Potential issue | 🟠 Major

Return type mismatch with throw behavior.

The return type is Promise<DatasourceResource | undefined> but the function throws an error when no datasource is found instead of returning undefined. Callers expecting undefined on no-match will receive an unhandled exception. Either update the return type to remove | undefined or return undefined instead of throwing.

Option A: Update return type (if throwing is intended)
   getDatasource = async (
     project: string,
     selector: DatasourceSelector,
-  ): Promise<DatasourceResource | undefined> => {
+  ): Promise<DatasourceResource> => {

Apply the same change to getGlobalDatasource at line 67.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboards/perses/datasource-api.ts` around lines 47 - 63,
The current getDatasource implementation declares Promise<DatasourceResource |
undefined> but throws when no match; either make the function consistently throw
by removing | undefined from its return type (update the signature of
getDatasource and also getGlobalDatasource) or change the control flow to return
undefined instead of throwing when fetchDatasourceList returns an
empty/non-array result; locate getDatasource and getGlobalDatasource and adjust
the return type or replace the throw new Error(...) with return undefined (and
ensure callers handle the behavior).
web/src/components/dashboards/perses/PersesWrapper.tsx (1)

378-384: ⚠️ Potential issue | 🔴 Critical

Enable conditional querying based on dashboardName.

The useFetchPersesDashboard hook has enabled: true hardcoded, causing network requests even when dashboardName is undefined (e.g., in list view). Change the enabled flag to enabled: !!dashboardName to prevent invalid fetches and avoid blocking LoadingBox when no dashboard is selected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboards/perses/PersesWrapper.tsx` around lines 378 -
384, The useFetchPersesDashboard call in InnerWrapper is triggering requests
even when dashboardName is falsy; update the options passed to
useFetchPersesDashboard so its enabled flag is conditional (enabled:
!!dashboardName) instead of always true. Locate the useFetchPersesDashboard
invocation in InnerWrapper and change the hook options to set enabled to
!!dashboardName so persesDashboard and persesDashboardLoading only run when a
dashboardName exists.
🧹 Nitpick comments (5)
web/src/components/metrics/promql-expression-input.tsx (1)

268-271: Consider restoring type safety for the fetch response.

Using any bypasses TypeScript's type checking for the API response. Since the code expects response.data to be an array of strings (line 270-271), an inline type or interface would help catch API contract mismatches at compile time.

♻️ Suggested improvement
-    safeFetch<any>(`${PROMETHEUS_BASE_PATH}/${PrometheusEndpoint.LABEL}/__name__/values`)
+    safeFetch<{ data: string[] }>(`${PROMETHEUS_BASE_PATH}/${PrometheusEndpoint.LABEL}/__name__/values`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/metrics/promql-expression-input.tsx` around lines 268 -
271, The fetch currently uses any which erases type safety; define a response
type (e.g. an interface with data: string[]) and use it with safeFetch
(safeFetch<YourResponseType>(...)) when calling safeFetch for
`${PROMETHEUS_BASE_PATH}/${PrometheusEndpoint.LABEL}/__name__/values`, then read
response.data and pass it to setMetricNames; additionally add a minimal runtime
guard (Array.isArray(response.data) && response.data.every(item => typeof item
=== 'string')) before calling setMetricNames to avoid runtime surprises.
web/src/components/dashboards/perses/project/useActiveProject.tsx (1)

38-45: Remove unused dependencies from effect.

persesProjects and perspective are in the dependency array but not used in the effect body. This causes unnecessary re-executions when those values change.

♻️ Suggested cleanup
   }, [
     projectFromUrl,
     activeProject,
-    perspective,
-    persesProjects,
     persesProjectsLoading,
     setProject,
   ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboards/perses/project/useActiveProject.tsx` around
lines 38 - 45, The effect inside useActiveProject includes unnecessary
dependencies causing extra re-runs; remove persesProjects and perspective from
the dependency array and leave only the values actually referenced in the effect
body (projectFromUrl, activeProject, persesProjectsLoading, setProject). Update
the dependency array in the useEffect call inside useActiveProject to match the
variables used, or if ESLint complains, add a clear inline //
eslint-disable-next-line react-hooks/exhaustive-deps comment with an explanation
above the useEffect.
web/src/components/dashboards/perses/hooks/useDashboardsData.ts (1)

53-66: Index-based comparison assumes stable API ordering.

The dashboardsChanged check compares dashboards by array index. If the API returns dashboards in a different order between calls, this will incorrectly report changes and recreate metadata unnecessarily. Consider comparing by a stable key (e.g., metadata.name + metadata.project) using a Set or Map for order-independent comparison.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboards/perses/hooks/useDashboardsData.ts` around lines
53 - 66, The current dashboardsChanged logic compares persesDashboards to
prevDashboardsRef.current by index (in dashboardsChanged), which breaks when API
ordering changes; instead compute a stable key for each dashboard (e.g.,
`${dashboard.metadata.name}::${dashboard.metadata.project}`) for both
persesDashboards and prevDashboardsRef.current, build Sets (or Maps) of those
keys, and compare the Sets for equality (and/or check sizes) to determine real
changes; update the dashboardsChanged calculation and subsequent logic that uses
dashboardsChanged/prevMetadataRef to be order-independent using these key Sets
so metadata is only recreated when keys differ.
web/src/components/dashboards/perses/dashboard-permissions.ts (1)

12-48: Consider batching permission checks for better performance.

The current implementation makes 3 API calls per project sequentially through the loop. For users with many projects, this could result in noticeable latency. Consider batching all permission checks with a single Promise.all across all projects.

♻️ Alternative approach for batching
+const checkProjectPermissions = async (projects: any[]): Promise<string[]> => {
+  if (!projects || projects.length === 0) {
+    return [];
+  }
+
+  const permissionPromises = projects
+    .filter((project) => project?.metadata?.name)
+    .map(async (project) => {
+      const projectName = project.metadata.name;
+      try {
+        const [createResult, updateResult, deleteResult] = await Promise.all([
+          checkAccess({ group: 'perses.dev', resource: 'persesdashboards', verb: 'create', namespace: projectName }),
+          checkAccess({ group: 'perses.dev', resource: 'persesdashboards', verb: 'update', namespace: projectName }),
+          checkAccess({ group: 'perses.dev', resource: 'persesdashboards', verb: 'delete', namespace: projectName }),
+        ]);
+        const canEdit = createResult?.status?.allowed && updateResult?.status?.allowed && deleteResult?.status?.allowed;
+        return canEdit ? projectName : null;
+      } catch {
+        return null;
+      }
+    });
+
+  const results = await Promise.all(permissionPromises);
+  return results.filter((name): name is string => name !== null);
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboards/perses/dashboard-permissions.ts` around lines
12 - 48, The loop currently calls checkAccess three times per project
(create/update/delete) causing many API calls; instead build a single array of
permission promises for all projects using checkAccess (e.g., for each
projectName push promises for verbs 'create','update','delete' into an array),
await Promise.all on that array, then iterate the resolved results to
reconstruct per-project permission triples and compute canEdit to push into
editableProjectNames; preserve the try/catch around the global Promise.all and
include projectName context when mapping results back to each project.
web/src/components/dashboards/perses/dashboard-list.tsx (1)

378-395: Type mismatch: targetedDashboard can be undefined.

The modal components receive targetedDashboard which has type DashboardResource | undefined, but ActionModalProps.dashboard expects DashboardResource. While the modals handle undefined internally, this creates a type safety gap.

♻️ Suggested type alignment

Either update the ActionModalProps interface to accept DashboardResource | undefined:

 interface ActionModalProps {
-  dashboard: DashboardResource;
+  dashboard: DashboardResource | undefined;
   isOpen: boolean;
   onClose: () => void;
   handleModalClose: () => void;
 }

Or only render modals when targetedDashboard is defined.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboards/perses/dashboard-list.tsx` around lines 378 -
395, The prop passed to RenameActionModal, DuplicateActionModal, and
DeleteActionModal, targetedDashboard, is typed as DashboardResource | undefined
but ActionModalProps.dashboard expects DashboardResource; fix by aligning types:
either broaden ActionModalProps.dashboard to accept DashboardResource |
undefined (update the ActionModalProps interface/type used by RenameActionModal,
DuplicateActionModal, DeleteActionModal) or only render those modal components
when targetedDashboard is non-undefined (wrap the three modal JSX elements in a
conditional rendering that checks targetedDashboard). Ensure you update all
related prop types or conditional checks consistently so TypeScript no longer
reports the mismatch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Dockerfile.devspace`:
- Around line 5-26: The Dockerfile currently sets USER 0 and never switches
back, causing the container to run as root; after the build steps (after RUN
make build-backend and before ENTRYPOINT ["make", "start-devspace-backend"]),
create a non-root user/group (or use an existing unprivileged UID/GID), chown
the app directories (/opt/app-root, /.devspace, and any copied files) to that
user, and switch to it via USER <non-root> so runtime executes as a non-root
user; reference the existing targets and symbols like RUN make install-backend,
RUN make build-backend, and ENTRYPOINT to place the user creation, chown, and
USER change in the correct spot.

In `@web/src/components/dashboards/perses/dashboard-action-modals.tsx`:
- Around line 99-104: The onError callback for the rename dashboard mutation is
re-throwing the error after showing an alert (the anonymous onError function
that builds msg via t(...) and calls addAlert(..., AlertVariant.danger)); remove
the trailing "throw err" so the mutation handler does not produce an unhandled
rejection—keep the t(...) message and addAlert(...) call as-is (optionally
replace throw with a debug log if you want to record the error but do not
re-throw).
- Around line 449-454: In the onError callback inside DeleteActionModal (the
onError: (err) => { ... } handler) remove the final "throw err" so the mutation
error is handled locally via addAlert(AlertVariant.danger) and not re-thrown;
simply construct and log the message with t(`Could not delete dashboard.
${err}`) and call addAlert(msg, AlertVariant.danger) then return/exit the
callback without throwing.
- Around line 82-88: processForm is directly mutating the dashboard prop by
assigning to dashboard.spec.display; instead create a new object copy and update
that before calling the mutation. In the
SubmitHandler<RenameDashboardValidationType> processForm, build a newSpec (e.g.,
{ ...dashboard.spec, display: { ...(dashboard.spec?.display || {}), name:
data.dashboardName } }) or a newDashboard (e.g., { ...dashboard, spec: newSpec
}) and pass that to the mutation; do not write to dashboard.spec.display
in-place so React props remain immutable.

In `@web/src/components/dashboards/perses/dashboard-action-validations.ts`:
- Line 90: The final return currently signals loading is still true; update the
returned object so that when returning refinedSchema the loading flag is false —
change isSchemaLoading: true to isSchemaLoading: false in the return that
includes refinedSchema (the object containing { schema: refinedSchema,
isSchemaLoading: ..., hasSchemaError: ... }) so it correctly reflects that
loading has completed.
- Around line 60-65: The early-return treats an empty dashboards array as still
loading; change the check so only undefined/null dashboards trigger the loading
state (e.g., test dashboards === undefined or dashboards == null) while an empty
array returns isSchemaLoading: false; keep schema produced by
createDashboardDialogValidationSchema(t) and hasSchemaError: false but set
isSchemaLoading to false for empty arrays so loading is only true when
dashboards is truly absent.

In `@web/src/components/dashboards/perses/dashboard-create-dialog.tsx`:
- Around line 106-119: The duplicate-name check in dashboard-create-dialog.tsx
compares existing dashboards' metadata names to the raw user input, causing
mismatches (e.g., "Café Test" vs "Cafe Test"); update the logic in the block
that uses dashboards.some(...) to compute the candidate metadata name by calling
generateMetadataName(dashboardName.trim()) and compare that generated name to
d.metadata.name (and ensure you still check d.metadata.project ===
selectedProject), then keep using setFormErrors to report the duplicate when the
generated names match.

In `@web/src/components/dashboards/perses/dashboard-list-frame.tsx`:
- Line 26: The Helmet invocation is passing raw text instead of a <title>
element, so replace the current usage of Helmet wrapping t('Metrics dashboards')
with a Helmet that contains a <title> child (e.g., Helmet -> title with the
result of t('Metrics dashboards')) so the document title is actually set; update
the Helmet usage in the DashboardListFrame (where Helmet and t('Metrics
dashboards') are used) accordingly.

In `@web/src/components/dashboards/perses/dashboard-page.tsx`:
- Around line 41-44: The component currently calls setActiveProject(urlProject)
during render which causes state updates during render; move this logic into a
useEffect that runs when urlProject or activeProject change: create a useEffect
in the dashboard component that checks if urlProject is truthy and different
from activeProject, then calls setActiveProject(urlProject). Reference the
existing identifiers urlProject, activeProject, and setActiveProject and ensure
the effect has [urlProject, activeProject] as dependencies to avoid
render-looping state updates.
- Around line 102-112: Remove the redundant outer ToastProvider wrapping in the
DashboardPage component: edit the DashboardPage React component to eliminate the
outer <ToastProvider> (the one that wraps <DashboardPage_ />) so only the inner
ToastProvider in DashboardFrame supplies the toast context; ensure
QueryParamProvider and QueryClientProvider remain intact and that DashboardPage_
(or DashboardFrame) continues to render unchanged.

In `@web/src/components/dashboards/perses/dashboard-permissions.ts`:
- Around line 38-39: The calculation of canEdit assumes
createResult.status.allowed (and same for updateResult/deleteResult) always
exist; add defensive validation before accessing nested properties: for
createResult, updateResult, deleteResult and their .status fields (results of
checkAccess), ensure each object is non-null and has a boolean allowed (e.g.,
typeof result?.status?.allowed === 'boolean') and treat missing/invalid shapes
as denied (false); update the canEdit assignment to use these guarded checks or
helper function (e.g., isAllowed(result)) so unexpected response shapes or
network errors won't throw.

In `@web/src/components/dashboards/perses/dashboard-toolbar.tsx`:
- Around line 199-201: The Alert in dashboard-toolbar.tsx contains a hardcoded
English message; wrap that string with the app's i18n translation function
(e.g., t('dashboard.managedViaCodeWarning') or i18n.t(...)) instead of the
literal text so it is localizable; update the JSX Alert (the Alert element
rendering "Dashboard managed via code only. Download JSON and commit changes to
save.") to call the translation key and add the new key to your translation
resource files, keeping the same message as the default value.

In `@web/src/components/dashboards/perses/hooks/usePerses.ts`:
- Around line 52-58: When a project is selected the code should prefer
project-specific query results over potentially stale all-dashboards cache;
update the returned fields so that when project is truthy you use
persesProjectDashboards (not persesDashboards) and likewise prefer
persesProjectDashboardsError and persesProjectDashboardsLoading; specifically
change persesDashboards to use (project ? persesProjectDashboards ?? [] :
persesDashboards ?? []) and change persesDashboardsError and
persesDashboardsLoading to choose
persesProjectDashboardsError/persesProjectDashboardsLoading when project is set
(falling back to the all-dashboards values otherwise).

In `@web/src/components/dashboards/perses/project/ProjectBar.tsx`:
- Around line 20-28: When building the URL in the project change handler, avoid
appending a bare question mark when there are no query params: only append
`?${params.toString()}` if `params.toString()` is non-empty. Update the logic
around `params`, `newProject`, `setActiveProject` and the call to `history.push`
(the block that constructs `const url =
`${getDashboardsListUrl(perspective)}?${params.toString()}`;`) so that it uses
the base `getDashboardsListUrl(perspective)` alone when `params` is empty, and
appends the `?` + params only when `params.toString()` yields a non-empty
string.

In `@web/src/components/dashboards/perses/ToastProvider.tsx`:
- Around line 51-52: The Alert is using a lookup AlertVariant[variant] which can
yield undefined for undefined or unexpected variant values; change the component
to pass the incoming variant prop directly to Alert (use variant rather than
AlertVariant[variant]) so the prop retains its correct AlertProps['variant']
type and avoids runtime errors; update the JSX where Alert is rendered in
ToastProvider.tsx (referencing the Alert component and the variant prop) to pass
variant directly and remove the indexed lookup.

---

Outside diff comments:
In `@web/src/components/dashboards/perses/datasource-api.ts`:
- Around line 47-63: The current getDatasource implementation declares
Promise<DatasourceResource | undefined> but throws when no match; either make
the function consistently throw by removing | undefined from its return type
(update the signature of getDatasource and also getGlobalDatasource) or change
the control flow to return undefined instead of throwing when
fetchDatasourceList returns an empty/non-array result; locate getDatasource and
getGlobalDatasource and adjust the return type or replace the throw new
Error(...) with return undefined (and ensure callers handle the behavior).

In `@web/src/components/dashboards/perses/PersesWrapper.tsx`:
- Around line 378-384: The useFetchPersesDashboard call in InnerWrapper is
triggering requests even when dashboardName is falsy; update the options passed
to useFetchPersesDashboard so its enabled flag is conditional (enabled:
!!dashboardName) instead of always true. Locate the useFetchPersesDashboard
invocation in InnerWrapper and change the hook options to set enabled to
!!dashboardName so persesDashboard and persesDashboardLoading only run when a
dashboardName exists.

In `@web/webpack.config.ts`:
- Around line 105-128: The development branch currently unshifts a rule using
'swc-loader' into config.module.rules without any options, but SWC needs
explicit parser config for TypeScript/TSX; update the rule (the object added in
the else branch that matches /\.(jsx?|tsx?)$/ and uses loader: 'swc-loader') to
include an options object (or point to a .swcrc) that enables jsc.parser.syntax
= 'typescript', jsc.parser.tsx = true for TSX support (and jsc.target or
transform.react settings as needed), so SWC can correctly parse and transpile
TS/TSX during development.

---

Nitpick comments:
In `@web/src/components/dashboards/perses/dashboard-list.tsx`:
- Around line 378-395: The prop passed to RenameActionModal,
DuplicateActionModal, and DeleteActionModal, targetedDashboard, is typed as
DashboardResource | undefined but ActionModalProps.dashboard expects
DashboardResource; fix by aligning types: either broaden
ActionModalProps.dashboard to accept DashboardResource | undefined (update the
ActionModalProps interface/type used by RenameActionModal, DuplicateActionModal,
DeleteActionModal) or only render those modal components when targetedDashboard
is non-undefined (wrap the three modal JSX elements in a conditional rendering
that checks targetedDashboard). Ensure you update all related prop types or
conditional checks consistently so TypeScript no longer reports the mismatch.

In `@web/src/components/dashboards/perses/dashboard-permissions.ts`:
- Around line 12-48: The loop currently calls checkAccess three times per
project (create/update/delete) causing many API calls; instead build a single
array of permission promises for all projects using checkAccess (e.g., for each
projectName push promises for verbs 'create','update','delete' into an array),
await Promise.all on that array, then iterate the resolved results to
reconstruct per-project permission triples and compute canEdit to push into
editableProjectNames; preserve the try/catch around the global Promise.all and
include projectName context when mapping results back to each project.

In `@web/src/components/dashboards/perses/hooks/useDashboardsData.ts`:
- Around line 53-66: The current dashboardsChanged logic compares
persesDashboards to prevDashboardsRef.current by index (in dashboardsChanged),
which breaks when API ordering changes; instead compute a stable key for each
dashboard (e.g., `${dashboard.metadata.name}::${dashboard.metadata.project}`)
for both persesDashboards and prevDashboardsRef.current, build Sets (or Maps) of
those keys, and compare the Sets for equality (and/or check sizes) to determine
real changes; update the dashboardsChanged calculation and subsequent logic that
uses dashboardsChanged/prevMetadataRef to be order-independent using these key
Sets so metadata is only recreated when keys differ.

In `@web/src/components/dashboards/perses/project/useActiveProject.tsx`:
- Around line 38-45: The effect inside useActiveProject includes unnecessary
dependencies causing extra re-runs; remove persesProjects and perspective from
the dependency array and leave only the values actually referenced in the effect
body (projectFromUrl, activeProject, persesProjectsLoading, setProject). Update
the dependency array in the useEffect call inside useActiveProject to match the
variables used, or if ESLint complains, add a clear inline //
eslint-disable-next-line react-hooks/exhaustive-deps comment with an explanation
above the useEffect.

In `@web/src/components/metrics/promql-expression-input.tsx`:
- Around line 268-271: The fetch currently uses any which erases type safety;
define a response type (e.g. an interface with data: string[]) and use it with
safeFetch (safeFetch<YourResponseType>(...)) when calling safeFetch for
`${PROMETHEUS_BASE_PATH}/${PrometheusEndpoint.LABEL}/__name__/values`, then read
response.data and pass it to setMetricNames; additionally add a minimal runtime
guard (Array.isArray(response.data) && response.data.every(item => typeof item
=== 'string')) before calling setMetricNames to avoid runtime surprises.

@PeterYurkovich
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 4

Caution

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

⚠️ Outside diff range comments (1)
web/src/components/dashboards/perses/datasource-api.ts (1)

47-63: ⚠️ Potential issue | 🔴 Critical

Add error handling for the new exception behavior.

The getDatasource method now throws when no datasource is found, but CachedDatasourceAPI.getDatasource (line 137) calls it without a .catch() handler. The promise rejection will propagate uncaught since:

  • The cache logic at lines 138-145 explicitly checks for undefined returns (old behavior)
  • The external DatasourceStoreProvider library (from @perses-dev/dashboards) was written expecting the original undefined-based contract, not exceptions
  • No error boundaries exist at call sites

Either add .catch() error handling at line 137, or reconsider the exception-based approach to align with the existing API contract and consumer expectations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboards/perses/datasource-api.ts` around lines 47 - 63,
CachedDatasourceAPI.getDatasource currently calls getDatasource (which now
throws when no datasource is found) without handling rejections; update
CachedDatasourceAPI.getDatasource to call this.getDatasource(...).catch(err => {
if (err.message?.includes('No matching datasource') || isNotFoundError(err))
return undefined; throw err; }) so it converts the new exception case back to
the original undefined contract while still rethrowing unexpected errors;
reference the methods getDatasource and CachedDatasourceAPI.getDatasource when
making the change.
🧹 Nitpick comments (1)
web/src/components/dashboards/perses/dashboard-toolbar.tsx (1)

39-97: EditButton’s public props are defined but ignored.

label, disabled, disabledTooltip, and loading are part of EditButtonProps yet not used, which makes the API misleading and harder to maintain. Either wire them through or remove them.

♻️ Suggested wiring for the props
-export const EditButton = ({ onClick, activeProject }: EditButtonProps): React.ReactElement => {
+export const EditButton = ({
+  onClick,
+  activeProject,
+  label,
+  disabled: disabledProp,
+  disabledTooltip,
+  loading: loadingProp,
+}: EditButtonProps): React.ReactElement => {
   const { t } = useTranslation(process.env.I18N_NAMESPACE);
-  const { canEdit, loading } = usePersesEditPermissions(activeProject);
-  const disabled = !canEdit;
+  const { canEdit, loading: permissionsLoading } = usePersesEditPermissions(activeProject);
+  const loading = loadingProp ?? permissionsLoading;
+  const disabled = disabledProp ?? !canEdit;

   const button = (
     <Button
       onClick={onClick}
       startIcon={<PencilIcon />}
       variant="outlined"
       color="secondary"
       disabled={disabled || loading}
       sx={{ whiteSpace: 'nowrap', minWidth: 'auto' }}
     >
-      {loading ? t('Loading...') : t('Edit')}
+      {loading ? t('Loading...') : label ?? t('Edit')}
     </Button>
   );

   if (disabled && !loading) {
     return (
-      <Tooltip title={t("You don't have permission to edit this dashboard")} arrow>
+      <Tooltip title={disabledTooltip ?? t("You don't have permission to edit this dashboard")} arrow>
         <span>{button}</span>
       </Tooltip>
     );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboards/perses/dashboard-toolbar.tsx` around lines 39 -
97, The props declared on EditButtonProps (label, disabled, disabledTooltip,
loading, activeProject) are defined but not used; update the EditButton
component to respect them: accept all props from EditButtonProps, use the label
prop instead of the hard-coded t('Edit'), use the loading prop to override the
loading value returned by usePersesEditPermissions (fall back to hook value when
prop is undefined), use the disabled prop to override the computed disabled
(computedDisabled = typeof disabled === 'boolean' ? disabled : !canEdit), and
use disabledTooltip if provided for the Tooltip text (fall back to the current
permission message); keep activeProject passed into usePersesEditPermissions and
preserve other behavior (startIcon, variant, disabled state combining
computedDisabled || loading).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/components/dashboards/perses/dashboard-action-modals.tsx`:
- Around line 70-113: The form's defaultValues passed to useForm (in
useForm<RenameDashboardValidationType>) are only read on initial render so when
dashboard or isOpen changes the input can be stale; call form.reset(...) with
the current dashboard name when dashboard or isOpen changes (e.g. inside a
useEffect that depends on [dashboard, isOpen]) to set the dashboardName to
getResourceDisplayName(dashboard) (or '' when no dashboard), while keeping
existing handleClose which already calls form.reset().

In `@web/src/components/dashboards/perses/dashboard-action-validations.ts`:
- Around line 35-93: The code currently allows t to be undefined and then calls
it unguarded; fix by ensuring a safe fallback translator is used inside
useDashboardValidationSchema: create a local safeT = t ?? ((key: string, opts?:
any) => { if (!opts) return key; replace placeholders in key with values from
opts or return a simple interpolated string }); then pass safeT into
createDashboardDialogValidationSchema and use safeT in the refine message
construction (replace all uses of t with safeT) so callers can omit t without
runtime errors; reference useDashboardValidationSchema,
createDashboardDialogValidationSchema, and the refine message callback when
applying the change.

In `@web/src/components/dashboards/perses/dashboard-list.tsx`:
- Around line 45-93: DashboardActionsCell currently calls
usePersesEditPermissions(project) per row causing many redundant permission
checks; remove that hook from DashboardActionsCell and instead accept permission
props (e.g., canEdit: boolean and optionally loading: boolean) so the parent
DashboardsTable can call useProjectPermissions once and pass per-row canEdit.
Concretely: update DashboardActionsCell's props to include canEdit (and loading
if needed), delete the usePersesEditPermissions call and the derived disabled
variable, use the incoming canEdit/loading to decide whether to render the
Tooltip + disabled ActionsColumn or the enabled ActionsColumn, and finally
update DashboardsTable to call useProjectPermissions(project) once, derive
canEdit for the table rows, and pass it into each DashboardActionsCell instance.
Ensure rowSpecificActions, ActionsColumn, and Tooltip behavior stay the same.

In `@web/src/components/dashboards/perses/dashboard-toolbar.tsx`:
- Around line 128-201: The component OCPDashboardToolbar uses t(...) but never
defines it; fix by importing useTranslation from 'react-i18next' and calling
const { t } = useTranslation() inside OCPDashboardToolbar (e.g., alongside
useEditMode/useTimeZoneParams hooks) so t is defined before the JSX renders;
ensure the import and the hook call are added to the top of the component file.

---

Outside diff comments:
In `@web/src/components/dashboards/perses/datasource-api.ts`:
- Around line 47-63: CachedDatasourceAPI.getDatasource currently calls
getDatasource (which now throws when no datasource is found) without handling
rejections; update CachedDatasourceAPI.getDatasource to call
this.getDatasource(...).catch(err => { if (err.message?.includes('No matching
datasource') || isNotFoundError(err)) return undefined; throw err; }) so it
converts the new exception case back to the original undefined contract while
still rethrowing unexpected errors; reference the methods getDatasource and
CachedDatasourceAPI.getDatasource when making the change.

---

Duplicate comments:
In `@web/src/components/dashboards/perses/datasource-api.ts`:
- Around line 65-77: getGlobalDatasource currently assumes
fetchGlobalDatasourceList returns a non-empty array; mirror the same
contract/verification used elsewhere by explicitly validating the response from
fetchGlobalDatasourceList(selector.kind, selector.name ? undefined : true,
selector.name) (check Array.isArray(list) and list.length > 0) and, on failure,
throw the same translated Error via this.t('No matching datasource found'); also
ensure the returned value matches the expected GlobalDatasourceResource type by
selecting list[0] and consider adding a guard/error if multiple entries are
returned when selector.name was provided so the function's contract remains
consistent with other datasource lookup helpers (refer to getGlobalDatasource
and fetchGlobalDatasourceList).

In `@web/src/components/dashboards/perses/project/ProjectBar.tsx`:
- Around line 7-9: Update ProjectBarProps so activeProject and its setter are
nullable (use string | null): change setActiveProject to
React.Dispatch<React.SetStateAction<string | null>> and activeProject to string
| null so calling setActiveProject(null) is type-safe; and fix the URL building
where params.toString() is appended (look for code using params.toString() in
ProjectBar or handlers) to prepend a '?' only when the query string is non-empty
(e.g., const qs = params.toString(); const path = base + (qs ? '?' + qs : '')).

---

Nitpick comments:
In `@web/src/components/dashboards/perses/dashboard-toolbar.tsx`:
- Around line 39-97: The props declared on EditButtonProps (label, disabled,
disabledTooltip, loading, activeProject) are defined but not used; update the
EditButton component to respect them: accept all props from EditButtonProps, use
the label prop instead of the hard-coded t('Edit'), use the loading prop to
override the loading value returned by usePersesEditPermissions (fall back to
hook value when prop is undefined), use the disabled prop to override the
computed disabled (computedDisabled = typeof disabled === 'boolean' ? disabled :
!canEdit), and use disabledTooltip if provided for the Tooltip text (fall back
to the current permission message); keep activeProject passed into
usePersesEditPermissions and preserve other behavior (startIcon, variant,
disabled state combining computedDisabled || loading).

@PeterYurkovich PeterYurkovich force-pushed the backport-dashboards-to-coo-0.4 branch from 3cb2368 to 4dd6f18 Compare February 17, 2026 20:51
@openshift-ci-robot
Copy link

openshift-ci-robot commented Feb 18, 2026

@PeterYurkovich: This pull request references COO-1545 which is a valid jira issue.

Details

In response to this:

This PR looks to backport the customizable dashboards available on main to the release-coo-0.4 branch, which still uses Patternfly 5. Most of the components have been kept as close as possible to their current state on the main branch, with some changes needed for the translation to PF-5 and how the monitoring plugin codebase is structured on release-coo-0.4.

Demo

Screencast.From.2026-02-18.10-55-14.mp4

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

1 similar comment
@openshift-ci-robot
Copy link

openshift-ci-robot commented Feb 18, 2026

@PeterYurkovich: This pull request references COO-1545 which is a valid jira issue.

Details

In response to this:

This PR looks to backport the customizable dashboards available on main to the release-coo-0.4 branch, which still uses Patternfly 5. Most of the components have been kept as close as possible to their current state on the main branch, with some changes needed for the translation to PF-5 and how the monitoring plugin codebase is structured on release-coo-0.4.

Demo

Screencast.From.2026-02-18.10-55-14.mp4

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 18, 2026

@PeterYurkovich: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-hypershift-conformance 5731a34 link false /test e2e-hypershift-conformance

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments