Skip to content

mcpToolUI basic implementation for OpenShift Lightpseed + obs-mcp + Perses#797

Draft
iNecas wants to merge 1 commit intoopenshift:mainfrom
iNecas:ols-tool-ui
Draft

mcpToolUI basic implementation for OpenShift Lightpseed + obs-mcp + Perses#797
iNecas wants to merge 1 commit intoopenshift:mainfrom
iNecas:ols-tool-ui

Conversation

@iNecas
Copy link

@iNecas iNecas commented Mar 2, 2026

Provides a hook to visualize an mcp-tool call with Perses elements.

It also adds an ability to add the panel from the OLS chat into opened dashboards.

See openshift/lightspeed-console#1576 for more details.

To test the ExecuteRangeQuery locally, one can feed the following sample data:

const tool = {
    name: 'execute_range_query',
    args: {
      duration: '1h',
      query: 'rate(node_cpu_seconds_total[1m])',
      step: '1m',
    },
    content: 'test',
    status: 'truncated',
    meta: {
      olsUi: {
        id: 'mcp-obs/execute-range-query',
      },
    },
  }
<ExecuteRangeQuery tool={tool} />

Depends on:

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced Execute Range Query tool for displaying Prometheus time-series metrics with configurable time ranges.
    • Added "Add to dashboard" functionality enabling users to save query results directly to Perses dashboards.
  • Refactor

    • Optimized dashboard wrapper to improve query parameter handling and panel management efficiency.

Provides a hook to visualize an mcp-tool call with Perses elements.

It also adds an ability to add the panel from the OLS chat into opened
dashboards.
@openshift-ci openshift-ci bot requested review from etmurasaki and zhuje March 2, 2026 15:45
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 2, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: iNecas
Once this PR has been reviewed and has the lgtm label, please assign jgbernalp for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found 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

@iNecas iNecas marked this pull request as draft March 2, 2026 15:45
@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 2, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

📝 Walkthrough

Walkthrough

This pull request introduces an external panel addition feature enabling Perses dashboards to receive panels from external tools. It adds new Redux state management for dashboard operations, creates OLS tool UI components for executing range queries, and integrates a hook-based system to manage externally added panels alongside existing dashboard infrastructure.

Changes

Cohort / File(s) Summary
Redux State Management
web/src/store/actions.ts, web/src/store/reducers.ts, web/src/store/store.ts
Introduces DashboardsOpened, DashboardsAddPersesPanelExternally, and DashboardsPersesPanelExternallyAdded actions. Adds isOpened and addPersesPanelExternally fields to ObserveState dashboards shape with corresponding reducer cases.
OLS Tool UI Components
web/src/components/ols-tool-ui/*
New components and exports: ExecuteRangeQuery displays Prometheus range queries with TimeSeriesChart; AddToDashboardButton dispatches external panel addition action; OlsToolUIPersesWrapper composes Perses providers with Tanstack Query context.
Dashboard Infrastructure
web/src/components/dashboards/perses/useExternalPanelAddition.ts, web/src/components/dashboards/perses/dashboard-app.tsx, web/src/components/dashboards/perses/PersesWrapper.tsx
New hook useExternalPanelAddition manages external panel addition via Redux store and dashboard panelEditor. Integrates hook into OCPDashboardApp. Refactors dashboardName query param retrieval from PersesWrapper to InnerWrapper.
Module Exposure & Configuration
web/package.json, config/perses-dashboards.patch.json
Exposes ols-tool-ui module in consolePlugin.exposedModules. Adds extension entry for mcp-obs/execute-range-query UI tool referencing ols-tool-ui.ExecuteRangeQuery.

Sequence Diagram(s)

sequenceDiagram
    participant External as External Tool UI
    participant Redux as Redux Store
    participant Hook as useExternalPanelAddition
    participant Dashboard as Dashboard panelEditor
    participant Perses as Perses Dashboard

    External->>Redux: dispatch dashboardsAddPersesPanelExternally(panelDefinition)
    Hook->>Redux: listen for addPersesPanelExternally
    Redux->>Hook: trigger addPanelExternally
    Hook->>Hook: wrap panelDefinition with groupId
    Hook->>Hook: store in local state (externallyAddedPanel)
    alt isEditMode = false
        Hook->>External: call onEditButtonClick
    end
    Hook->>Dashboard: apply change via panelEditor
    Hook->>Redux: dispatch dashboardsPersesPanelExternallyAdded()
    Dashboard->>Perses: update dashboard with new panel
    Hook->>Hook: clear externallyAddedPanel state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A rabbit's delight, so precise and so keen,
Range queries now dance on dashboards so clean,
External panels hop in with Redux's grace,
Perses and hooks waltz through dashboard space! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a basic mcpToolUI implementation for integrating OpenShift Lightspeed with Perses dashboards through obs-mcp.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

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

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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

🧹 Nitpick comments (3)
web/src/components/ols-tool-ui/AddToDashboardButton.tsx (1)

42-44: Type the selector state instead of using any.

Line 42-44 bypasses state-shape validation. Strong typing here helps catch integration regressions early.

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

In `@web/src/components/ols-tool-ui/AddToDashboardButton.tsx` around lines 42 -
44, The selector currently uses an untyped state (s: any) in
AddToDashboardButton.tsx causing loss of type checks; replace the any with the
app/root Redux state type (e.g., RootState or AppState) and/or a narrowed
interface for the plugins slice so useSelector((s: RootState) =>
s.plugins?.mp?.dashboards?.isOpened) is typed; update or import the appropriate
state type used across the app (or declare PluginsState with
mp.dashboards.isOpened) and apply it to the selector to restore compile-time
validation for isCustomDashboardOpen.
web/src/components/dashboards/perses/useExternalPanelAddition.ts (1)

16-24: Replace any in selector/panel path with concrete types.

Line 16-24 uses any for both store read and panel definition. This removes compile-time checks exactly where external payloads enter dashboard mutations.

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

In `@web/src/components/dashboards/perses/useExternalPanelAddition.ts` around
lines 16 - 24, Replace the loose any types with concrete interfaces: change the
selector typed as useSelector((s: AppState) =>
s.plugins?.mp?.dashboards?.addPersesPanelExternally) using your app-wide
AppState (or PluginsState) type and type the returned value as a known
AddPersesPanelExternallyType; replace the panelDefinition parameter in
addPanelExternally(panelDefinition: any) and the externallyAddedPanel state to
use a defined PanelDefinition (or PersesPanel) type; update related uses in
useDashboardActions / openAddPanel and useDashboardStore to accept those
concrete types so compiler checks validate external payload shape and downstream
mutations (referencing addPersesPanelExternally, useSelector,
useDashboardActions, useDashboardStore, addPanelExternally,
externallyAddedPanel, setExternallyAddedPanel).
web/src/store/actions.ts (1)

21-23: Consider namespacing new action type strings consistently.

Line 21-23 uses unscoped literals, while nearby dashboard actions are namespaced (for example v2/...). Keeping these consistent reduces accidental collisions and simplifies debugging.

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

In `@web/src/store/actions.ts` around lines 21 - 23, The three action type strings
(DashboardsOpened, DashboardsAddPersesPanelExternally,
DashboardsPersesPanelExternallyAdded) are not namespaced like the surrounding
dashboard actions; update their string values to follow the same namespace
pattern (e.g., prefix with the existing namespace used by nearby actions such as
"v2/dashboards/" or "dashboards/") so they match the project's convention and
avoid collisions, keeping the enum/member names unchanged and only adjusting the
right-hand string literals in actions.ts.
🤖 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-app.tsx`:
- Line 129: The hook useExternalPanelAddition currently performs state mutations
and dispatches during render (mutating externallyAddedPanel, calling panelEditor
methods, and dispatching actions/state setters); move all conditional logic that
mutates state, calls panelEditor methods, or dispatches actions into one or more
useEffect blocks with correct dependency arrays (include externallyAddedPanel,
isEditMode, onEditButtonClick, and any panelEditor or dispatch refs) so these
side effects run after render; keep pure synchronous reads in the hook body and
ensure effects guard against repeated execution (e.g., check previous values or
use refs) to avoid re-render loops.

In `@web/src/components/dashboards/perses/PersesWrapper.tsx`:
- Around line 381-382: The InnerWrapper component currently destructures untyped
props; add an explicit props type (e.g., declare an interface or type alias
named InnerWrapperProps) that includes children: React.ReactNode and project:
string | undefined (or the specific Project type if one exists), then annotate
the component signature as function InnerWrapper({ children, project }:
InnerWrapperProps) to improve clarity and future-proof against stricter TS
settings; keep existing usage of useQueryParam/QueryParams.Dashboard/StringParam
unchanged.

In `@web/src/components/dashboards/perses/useExternalPanelAddition.ts`:
- Around line 39-56: The code performs mutations during render; move all side
effects into useEffect hooks: wrap the block that applies the
externallyAddedPanel (accessing dashboardStore.panelGroupOrder[0], set
externallyAddedPanel.groupId, call dashboardStore.panelEditor.applyChanges and
.close, then call setExternallyAddedPanel(null)) into a useEffect that depends
on externallyAddedPanel and dashboardStore.panelGroupOrder; similarly wrap the
addPersesPanelExternally handling (call
addPanelExternally(addPersesPanelExternally) and
dispatch(dashboardsPersesPanelExternallyAdded())) into a separate useEffect that
depends on addPersesPanelExternally; ensure effects bail out early if their
trigger values are falsy to avoid running on every render.

In `@web/src/components/ols-tool-ui/AddToDashboardButton.tsx`:
- Around line 11-13: The panel object in AddToDashboardButton.tsx sets
display.name to an empty string, causing externally added panels to be untitled;
update the assignment of display.name to use a meaningful default (for example
derive from the panel's title/label like panel.title || panel.name) and fallback
to a sensible string such as "External Panel" or "Untitled Panel" so that
display.name is never ''. Locate the object where display: { name: '' } is
created in AddToDashboardButton and replace the empty literal with the derived
value (e.g., use props or the panel data available in the component to populate
the name).

In `@web/src/components/ols-tool-ui/ExecuteRangeQuery.tsx`:
- Around line 9-37: The component ExecuteRangeQuery currently hardcodes
persesTimeRange and DataQueriesProvider step; update it to read duration and
step from tool.args (e.g., tool.args.duration and tool.args.step) and fall back
to the existing defaults ('1h' for duration and 15000 for step) so the UI
matches the tool call; replace persesTimeRange with a computed object using the
provided duration and pass the numeric step into DataQueriesProvider options
(mode: 'range', suggestedStepMs: step) and ensure definitions still use
tool.args.query.

In `@web/src/components/ols-tool-ui/OlsToolUIPersesWrapper.tsx`:
- Line 23: The interface in OlsToolUIPersesWrapper has a typo: change the prop
name `hight` to `height` in the props/interface declaration so it matches the
destructured `height` used in the OlsToolUIPersesWrapper component; update any
references to the interface/type (e.g., the props interface where `hight?:
string;` is declared) to use `height?: string;` to ensure the prop is passed
correctly to the component.
- Line 36: Update the PersesWrapperProps interface so its project property
accepts null (change type from string to string | null) to match usage sites
like OlsToolUIPersesWrapper.tsx where <PersesWrapper project={null}> is passed
and the runtime check in PersesWrapper (the if (!project) branch); update any
related type declarations referencing PersesWrapperProps.project to string |
null to keep typing consistent with activeProject passed from
dashboard-frame.tsx.

In `@web/src/store/actions.ts`:
- Around line 75-83: The Actions type map is missing entries for the three new
action creators; update the Actions interface/type (the map around the Actions
type definition) to include dashboardsOpened: typeof dashboardsOpened,
dashboardsPersesPanelExternallyAdded: typeof
dashboardsPersesPanelExternallyAdded, and dashboardsAddPersesPanelExternally:
typeof dashboardsAddPersesPanelExternally so the new action creators
(dashboardsOpened, dashboardsPersesPanelExternallyAdded,
dashboardsAddPersesPanelExternally) are represented for type-safe reducers and
dispatches.

In `@web/src/store/store.ts`:
- Around line 31-32: The state definition is inconsistent: change
addPersesPanelExternally to allow null (PanelDefinition | null) and ensure
defaultObserveState.dashboards initializes both isOpened (boolean, likely false)
and addPersesPanelExternally (null) to match reducers.ts which sets
addPersesPanelExternally to null; update the type in store.ts and the
defaultObserveState.dashboards object so the type contract and reducer behavior
align with the PanelDefinition symbol and isOpened flag.

---

Nitpick comments:
In `@web/src/components/dashboards/perses/useExternalPanelAddition.ts`:
- Around line 16-24: Replace the loose any types with concrete interfaces:
change the selector typed as useSelector((s: AppState) =>
s.plugins?.mp?.dashboards?.addPersesPanelExternally) using your app-wide
AppState (or PluginsState) type and type the returned value as a known
AddPersesPanelExternallyType; replace the panelDefinition parameter in
addPanelExternally(panelDefinition: any) and the externallyAddedPanel state to
use a defined PanelDefinition (or PersesPanel) type; update related uses in
useDashboardActions / openAddPanel and useDashboardStore to accept those
concrete types so compiler checks validate external payload shape and downstream
mutations (referencing addPersesPanelExternally, useSelector,
useDashboardActions, useDashboardStore, addPanelExternally,
externallyAddedPanel, setExternallyAddedPanel).

In `@web/src/components/ols-tool-ui/AddToDashboardButton.tsx`:
- Around line 42-44: The selector currently uses an untyped state (s: any) in
AddToDashboardButton.tsx causing loss of type checks; replace the any with the
app/root Redux state type (e.g., RootState or AppState) and/or a narrowed
interface for the plugins slice so useSelector((s: RootState) =>
s.plugins?.mp?.dashboards?.isOpened) is typed; update or import the appropriate
state type used across the app (or declare PluginsState with
mp.dashboards.isOpened) and apply it to the selector to restore compile-time
validation for isCustomDashboardOpen.

In `@web/src/store/actions.ts`:
- Around line 21-23: The three action type strings (DashboardsOpened,
DashboardsAddPersesPanelExternally, DashboardsPersesPanelExternallyAdded) are
not namespaced like the surrounding dashboard actions; update their string
values to follow the same namespace pattern (e.g., prefix with the existing
namespace used by nearby actions such as "v2/dashboards/" or "dashboards/") so
they match the project's convention and avoid collisions, keeping the
enum/member names unchanged and only adjusting the right-hand string literals in
actions.ts.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between ea010c4 and 312bcf8.

📒 Files selected for processing (12)
  • config/perses-dashboards.patch.json
  • web/package.json
  • web/src/components/dashboards/perses/PersesWrapper.tsx
  • web/src/components/dashboards/perses/dashboard-app.tsx
  • web/src/components/dashboards/perses/useExternalPanelAddition.ts
  • web/src/components/ols-tool-ui/AddToDashboardButton.tsx
  • web/src/components/ols-tool-ui/ExecuteRangeQuery.tsx
  • web/src/components/ols-tool-ui/OlsToolUIPersesWrapper.tsx
  • web/src/components/ols-tool-ui/index.ts
  • web/src/store/actions.ts
  • web/src/store/reducers.ts
  • web/src/store/store.ts

}
};

useExternalPanelAddition({ isEditMode, onEditButtonClick });
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n web/src/components/dashboards/perses/useExternalPanelAddition.ts

Repository: openshift/monitoring-plugin

Length of output: 2689


Move render-phase side effects into useEffect blocks.

The hook implementation (lines 39–56 of useExternalPanelAddition.ts) performs state mutations and dispatches directly during render—outside of any useEffect. Lines 39–50 mutate externallyAddedPanel state and call panelEditor methods conditionally on render, and lines 53–56 dispatch actions and trigger state setters during render. This violates React's rendering model and can cause re-render loops and unpredictable behavior. Move the conditional logic in lines 39–50 and 53–56 into useEffect blocks with appropriate dependencies before using this hook in the dashboard component.

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

In `@web/src/components/dashboards/perses/dashboard-app.tsx` at line 129, The hook
useExternalPanelAddition currently performs state mutations and dispatches
during render (mutating externallyAddedPanel, calling panelEditor methods, and
dispatching actions/state setters); move all conditional logic that mutates
state, calls panelEditor methods, or dispatches actions into one or more
useEffect blocks with correct dependency arrays (include externallyAddedPanel,
isEditMode, onEditButtonClick, and any panelEditor or dispatch refs) so these
side effects run after render; keep pure synchronous reads in the hook body and
ensure effects guard against repeated execution (e.g., check previous values or
use refs) to avoid re-render loops.

Comment on lines +381 to +382
function InnerWrapper({ children, project }) {
const [dashboardName] = useQueryParam(QueryParams.Dashboard, StringParam);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check the current state of the file at the mentioned lines
wc -l web/src/components/dashboards/perses/PersesWrapper.tsx

Repository: openshift/monitoring-plugin

Length of output: 129


🏁 Script executed:

# Check TypeScript strictness settings
fd 'tsconfig.*json$' -type f | head -20

Repository: openshift/monitoring-plugin

Length of output: 241


🏁 Script executed:

# Get context around lines 381-382 in PersesWrapper.tsx
sed -n '375,390p' web/src/components/dashboards/perses/PersesWrapper.tsx

Repository: openshift/monitoring-plugin

Length of output: 535


🏁 Script executed:

# Check TS strictness rules in tsconfig files
fd 'tsconfig.*json$' -type f -exec grep -l 'strict\|noImplicitAny' {} \;

Repository: openshift/monitoring-plugin

Length of output: 241


🏁 Script executed:

# Find tsconfig files and check their content
fd 'tsconfig' | head -10

Repository: openshift/monitoring-plugin

Length of output: 114


🏁 Script executed:

# Check TypeScript strictness in tsconfig files
fd 'tsconfig' | xargs cat

Repository: openshift/monitoring-plugin

Length of output: 660


🏁 Script executed:

# Search for InnerWrapper usage/calls in the file
rg -n 'InnerWrapper' web/src/components/dashboards/perses/PersesWrapper.tsx

Repository: openshift/monitoring-plugin

Length of output: 226


🏁 Script executed:

# Get broader context around InnerWrapper to see how it's used
sed -n '365,420p' web/src/components/dashboards/perses/PersesWrapper.tsx

Repository: openshift/monitoring-plugin

Length of output: 1858


Consider typing InnerWrapper props for better code clarity and maintainability.

Line 381 destructures untyped props. While the current TypeScript configuration (strict: false) allows implicit any types, adding explicit types improves clarity and protects against future stricter TS settings.

🔧 Suggested improvement
+interface InnerWrapperProps {
+  children?: React.ReactNode;
+  project: string;
+}
-
-function InnerWrapper({ children, project }) {
+function InnerWrapper({ children, project }: InnerWrapperProps) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function InnerWrapper({ children, project }) {
const [dashboardName] = useQueryParam(QueryParams.Dashboard, StringParam);
interface InnerWrapperProps {
children?: React.ReactNode;
project: string;
}
function InnerWrapper({ children, project }: InnerWrapperProps) {
const [dashboardName] = useQueryParam(QueryParams.Dashboard, StringParam);
🤖 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 381 -
382, The InnerWrapper component currently destructures untyped props; add an
explicit props type (e.g., declare an interface or type alias named
InnerWrapperProps) that includes children: React.ReactNode and project: string |
undefined (or the specific Project type if one exists), then annotate the
component signature as function InnerWrapper({ children, project }:
InnerWrapperProps) to improve clarity and future-proof against stricter TS
settings; keep existing usage of useQueryParam/QueryParams.Dashboard/StringParam
unchanged.

Comment on lines +39 to +56
if (externallyAddedPanel) {
const groupId = dashboardStore.panelGroupOrder[0];
externallyAddedPanel.groupId = groupId;

// Use the temporary panelEditor to add changes to the dashboard.
const panelEditor = dashboardStore.panelEditor;
panelEditor.applyChanges(externallyAddedPanel);
panelEditor.close();

// Clear the externally added panel after applying changes
setExternallyAddedPanel(null);
}

// Listen for external panel addition requests
if (addPersesPanelExternally) {
addPanelExternally(addPersesPanelExternally);
dispatch(dashboardsPersesPanelExternallyAdded());
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "useExternalPanelAddition.ts" -type f

Repository: openshift/monitoring-plugin

Length of output: 137


🏁 Script executed:

wc -l ./web/src/components/dashboards/perses/useExternalPanelAddition.ts

Repository: openshift/monitoring-plugin

Length of output: 140


🏁 Script executed:

cat -n ./web/src/components/dashboards/perses/useExternalPanelAddition.ts

Repository: openshift/monitoring-plugin

Length of output: 2689


Move side effects into useEffect hooks to prevent update-during-render warnings and infinite loops.

Lines 39-56 execute side effects during render: setExternallyAddedPanel(null), dispatch(dashboardsPersesPanelExternallyAdded()), and panelEditor.applyChanges()/panelEditor.close(). This causes these mutations to run on every render when their conditions are met, potentially triggering re-render cycles that execute the same mutations again.

Suggested fix
-  // Apply externally added panel
-  if (externallyAddedPanel) {
-    const groupId = dashboardStore.panelGroupOrder[0];
-    externallyAddedPanel.groupId = groupId;
-
-    // Use the temporary panelEditor to add changes to the dashboard.
-    const panelEditor = dashboardStore.panelEditor;
-    panelEditor.applyChanges(externallyAddedPanel);
-    panelEditor.close();
-
-    // Clear the externally added panel after applying changes
-    setExternallyAddedPanel(null);
-  }
-
-  // Listen for external panel addition requests
-  if (addPersesPanelExternally) {
-    addPanelExternally(addPersesPanelExternally);
-    dispatch(dashboardsPersesPanelExternallyAdded());
-  }
+  useEffect(() => {
+    if (!addPersesPanelExternally) return;
+    addPanelExternally(addPersesPanelExternally);
+    dispatch(dashboardsPersesPanelExternallyAdded());
+  }, [addPersesPanelExternally, dispatch, isEditMode, onEditButtonClick]);
+
+  useEffect(() => {
+    if (!externallyAddedPanel) return;
+    const groupId = dashboardStore.panelGroupOrder?.[0];
+    const panelEditor = dashboardStore.panelEditor;
+    if (groupId === undefined || !panelEditor) return;
+
+    panelEditor.applyChanges({ ...externallyAddedPanel, groupId });
+    panelEditor.close();
+    setExternallyAddedPanel(null);
+  }, [externallyAddedPanel, dashboardStore]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboards/perses/useExternalPanelAddition.ts` around
lines 39 - 56, The code performs mutations during render; move all side effects
into useEffect hooks: wrap the block that applies the externallyAddedPanel
(accessing dashboardStore.panelGroupOrder[0], set externallyAddedPanel.groupId,
call dashboardStore.panelEditor.applyChanges and .close, then call
setExternallyAddedPanel(null)) into a useEffect that depends on
externallyAddedPanel and dashboardStore.panelGroupOrder; similarly wrap the
addPersesPanelExternally handling (call
addPanelExternally(addPersesPanelExternally) and
dispatch(dashboardsPersesPanelExternallyAdded())) into a separate useEffect that
depends on addPersesPanelExternally; ensure effects bail out early if their
trigger values are falsy to avoid running on every render.

Comment on lines +11 to +13
display: {
name: '',
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Externally added panels will be untitled.

Line 12 sets display.name to '', so added panels are hard to distinguish in dashboards.

✅ Suggested fix
       display: {
-        name: '',
+        name: query,
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
display: {
name: '',
},
display: {
name: query,
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/ols-tool-ui/AddToDashboardButton.tsx` around lines 11 -
13, The panel object in AddToDashboardButton.tsx sets display.name to an empty
string, causing externally added panels to be untitled; update the assignment of
display.name to use a meaningful default (for example derive from the panel's
title/label like panel.title || panel.name) and fallback to a sensible string
such as "External Panel" or "Untitled Panel" so that display.name is never ''.
Locate the object where display: { name: '' } is created in AddToDashboardButton
and replace the empty literal with the derived value (e.g., use props or the
panel data available in the component to populate the name).

Comment on lines +9 to +37
type ExecuteRangeQueryTool = {
name: 'execute_range_query';
args: {
query: string;
};
};

const persesTimeRange = {
pastDuration: '1h' as DurationString,
};

export const ExecuteRangeQuery: React.FC<{ tool: ExecuteRangeQueryTool }> = ({ tool }) => {
const query = tool.args.query;
const definitions = [
{
kind: 'PrometheusTimeSeriesQuery',
spec: {
query: query,
},
},
];

return (
<>
<OlsToolUIPersesWrapper initialTimeRange={persesTimeRange}>
<DataQueriesProvider
definitions={definitions}
options={{ suggestedStepMs: 15000, mode: 'range' }}
>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

execute_range_query arguments are only partially honored.

Line 16-37 hardcodes range (1h) and step (15000ms) instead of using tool-provided duration/step. This can render a chart that does not match the actual tool call.

✅ Suggested fix
 type ExecuteRangeQueryTool = {
   name: 'execute_range_query';
   args: {
     query: string;
+    duration?: DurationString;
+    step?: string;
   };
 };
 
-const persesTimeRange = {
-  pastDuration: '1h' as DurationString,
-};
-
 export const ExecuteRangeQuery: React.FC<{ tool: ExecuteRangeQueryTool }> = ({ tool }) => {
   const query = tool.args.query;
+  const pastDuration = tool.args.duration ?? ('1h' as DurationString);
+  const suggestedStepMs = tool.args.step ? parseInt(tool.args.step, 10) * 1000 : 15000;
@@
-      <OlsToolUIPersesWrapper initialTimeRange={persesTimeRange}>
+      <OlsToolUIPersesWrapper initialTimeRange={{ pastDuration }}>
         <DataQueriesProvider
           definitions={definitions}
-          options={{ suggestedStepMs: 15000, mode: 'range' }}
+          options={{ suggestedStepMs, mode: 'range' }}
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/ols-tool-ui/ExecuteRangeQuery.tsx` around lines 9 - 37,
The component ExecuteRangeQuery currently hardcodes persesTimeRange and
DataQueriesProvider step; update it to read duration and step from tool.args
(e.g., tool.args.duration and tool.args.step) and fall back to the existing
defaults ('1h' for duration and 15000 for step) so the UI matches the tool call;
replace persesTimeRange with a computed object using the provided duration and
pass the numeric step into DataQueriesProvider options (mode: 'range',
suggestedStepMs: step) and ensure definitions still use tool.args.query.


interface OlsToolUIPersesWrapperProps {
children: React.ReactNode;
hight?: string;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n web/src/components/ols-tool-ui/OlsToolUIPersesWrapper.tsx

Repository: openshift/monitoring-plugin

Length of output: 1789


Fix hightheight prop name typo.

Line 23 defines the interface prop as hight, but line 32 destructures height. This prevents the prop from being passed correctly to the component.

Fix
interface OlsToolUIPersesWrapperProps {
  children: React.ReactNode;
-  hight?: string;
+  height?: string;
   initialTimeRange?: {
     pastDuration: DurationString;
   };
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
hight?: string;
interface OlsToolUIPersesWrapperProps {
children: React.ReactNode;
height?: string;
initialTimeRange?: {
pastDuration: DurationString;
};
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/ols-tool-ui/OlsToolUIPersesWrapper.tsx` at line 23, The
interface in OlsToolUIPersesWrapper has a typo: change the prop name `hight` to
`height` in the props/interface declaration so it matches the destructured
`height` used in the OlsToolUIPersesWrapper component; update any references to
the interface/type (e.g., the props interface where `hight?: string;` is
declared) to use `height?: string;` to ensure the prop is passed correctly to
the component.

}) => {
return (
<QueryClientProvider client={queryClient}>
<PersesWrapper project={null}>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n web/src/components/ols-tool-ui/OlsToolUIPersesWrapper.tsx | head -50

Repository: openshift/monitoring-plugin

Length of output: 1789


🏁 Script executed:

# Find PersesWrapper definition
rg -l "PersesWrapper" --type ts --type tsx | head -10

Repository: openshift/monitoring-plugin

Length of output: 98


🏁 Script executed:

# Search for PersesWrapperProps type definition
rg "PersesWrapperProps" --type ts --type tsx -A 5

Repository: openshift/monitoring-plugin

Length of output: 98


🏁 Script executed:

find web/src/components/dashboards/perses -name "*.tsx" -o -name "*.ts" | xargs cat -n

Repository: openshift/monitoring-plugin

Length of output: 50384


🏁 Script executed:

# Try broader search with different approach
rg "PersesWrapper" -A 20 --max-count 5

Repository: openshift/monitoring-plugin

Length of output: 14616


Update PersesWrapperProps.project type to accept null values.

The PersesWrapper component implementation (line 373) checks if (!project) and handles falsy values, yet the PersesWrapperProps.project type is defined as string rather than string | null. This creates a type mismatch with actual usage: OlsToolUIPersesWrapper.tsx passes null, and dashboard-frame.tsx passes activeProject which is typed as string | null.

Change the interface definition instead of the usage:

 interface PersesWrapperProps {
   children?: React.ReactNode;
-  project: string;
+  project: string | null;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/ols-tool-ui/OlsToolUIPersesWrapper.tsx` at line 36, Update
the PersesWrapperProps interface so its project property accepts null (change
type from string to string | null) to match usage sites like
OlsToolUIPersesWrapper.tsx where <PersesWrapper project={null}> is passed and
the runtime check in PersesWrapper (the if (!project) branch); update any
related type declarations referencing PersesWrapperProps.project to string |
null to keep typing consistent with activeProject passed from
dashboard-frame.tsx.

Comment on lines +75 to +83
export const dashboardsOpened = (isOpened: boolean) =>
action(ActionType.DashboardsOpened, { isOpened });

export const dashboardsPersesPanelExternallyAdded = () =>
action(ActionType.DashboardsPersesPanelExternallyAdded, {});

export const dashboardsAddPersesPanelExternally = (panelDefinition: PanelDefinition) =>
action(ActionType.DashboardsAddPersesPanelExternally, { panelDefinition });

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd web/src/store && wc -l actions.ts

Repository: openshift/monitoring-plugin

Length of output: 85


🏁 Script executed:

cd web/src/store && head -100 actions.ts | tail -30

Repository: openshift/monitoring-plugin

Length of output: 1135


🏁 Script executed:

cd web/src/store && sed -n '70,90p' actions.ts

Repository: openshift/monitoring-plugin

Length of output: 894


🏁 Script executed:

cd web/src/store && grep -n "type Actions\|type ObserveAction" actions.ts | head -5

Repository: openshift/monitoring-plugin

Length of output: 140


🏁 Script executed:

cd web/src/store && sed -n '210,240p' actions.ts

Repository: openshift/monitoring-plugin

Length of output: 1909


🏁 Script executed:

cd web/src/store && sed -n '210,254p' actions.ts

Repository: openshift/monitoring-plugin

Length of output: 2523


Actions type map is missing the three new dashboard action creators.

Lines 75-83 introduce dashboardsOpened, dashboardsPersesPanelExternallyAdded, and dashboardsAddPersesPanelExternally action creators, but they are not included in the Actions type definition (starting at line 210). This breaks type safety for reducers and dispatch calls using these actions.

Add the three missing entries to the Actions type:

  • dashboardsOpened: typeof dashboardsOpened;
  • dashboardsPersesPanelExternallyAdded: typeof dashboardsPersesPanelExternallyAdded;
  • dashboardsAddPersesPanelExternally: typeof dashboardsAddPersesPanelExternally;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/store/actions.ts` around lines 75 - 83, The Actions type map is
missing entries for the three new action creators; update the Actions
interface/type (the map around the Actions type definition) to include
dashboardsOpened: typeof dashboardsOpened, dashboardsPersesPanelExternallyAdded:
typeof dashboardsPersesPanelExternallyAdded, and
dashboardsAddPersesPanelExternally: typeof dashboardsAddPersesPanelExternally so
the new action creators (dashboardsOpened, dashboardsPersesPanelExternallyAdded,
dashboardsAddPersesPanelExternally) are represented for type-safe reducers and
dispatches.

Comment on lines +31 to +32
isOpened: boolean;
addPersesPanelExternally: PanelDefinition;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n -C2 'isOpened|addPersesPanelExternally' web/src/store/store.ts web/src/store/reducers.ts web/src/store/actions.ts

Repository: openshift/monitoring-plugin

Length of output: 1650


🏁 Script executed:

rg -n -B5 -A15 'export const defaultObserveState' web/src/store/store.ts

Repository: openshift/monitoring-plugin

Length of output: 703


State type is inconsistent with reducer behavior and defaults.

addPersesPanelExternally is typed as non-null PanelDefinition, but the reducer sets it to null (line 95 in reducers.ts), and defaultObserveState.dashboards does not initialize either isOpened or addPersesPanelExternally. This creates a TypeScript contract mismatch that will prevent compilation.

🐛 Proposed fix
 export type ObserveState = {
   dashboards: {
     endTime: number | null;
     pollInterval: number;
     timespan: number;
     variables: Record<string, Variable>;
     isOpened: boolean;
-    addPersesPanelExternally: PanelDefinition;
+    addPersesPanelExternally: PanelDefinition | null;
   };
@@
 export const defaultObserveState: ObserveState = {
   dashboards: {
     endTime: null,
     pollInterval: 30 * 1000,
     timespan: MONITORING_DASHBOARDS_DEFAULT_TIMESPAN,
     variables: {},
+    isOpened: false,
+    addPersesPanelExternally: null,
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
isOpened: boolean;
addPersesPanelExternally: PanelDefinition;
export type ObserveState = {
dashboards: {
endTime: number | null;
pollInterval: number;
timespan: number;
variables: Record<string, Variable>;
isOpened: boolean;
addPersesPanelExternally: PanelDefinition | null;
};
// ... rest of the type definition
};
export const defaultObserveState: ObserveState = {
dashboards: {
endTime: null,
pollInterval: 30 * 1000,
timespan: MONITORING_DASHBOARDS_DEFAULT_TIMESPAN,
variables: {},
isOpened: false,
addPersesPanelExternally: null,
},
// ... rest of the default state
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/store/store.ts` around lines 31 - 32, The state definition is
inconsistent: change addPersesPanelExternally to allow null (PanelDefinition |
null) and ensure defaultObserveState.dashboards initializes both isOpened
(boolean, likely false) and addPersesPanelExternally (null) to match reducers.ts
which sets addPersesPanelExternally to null; update the type in store.ts and the
defaultObserveState.dashboards object so the type contract and reducer behavior
align with the PanelDefinition symbol and isOpened flag.

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

Labels

do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant