Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions config/perses-dashboards.patch.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,18 @@
"component": { "$codeRef": "DashboardPage" }
}
}
},
{
"op": "add",
"path": "/extensions/1",
"value": {
"type": "ols.tool-ui",
"properties": {
"id": "mcp-obs/execute-range-query",
"component": {
"$codeRef": "ols-tool-ui.ExecuteRangeQuery"
}
}
}
}
]
3 changes: 2 additions & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@
"TargetsPage": "./components/targets-page",
"PrometheusRedirectPage": "./components/redirects/prometheus-redirect-page",
"DevRedirects": "./components/redirects/dev-redirects",
"MonitoringContext": "./contexts/MonitoringContext"
"MonitoringContext": "./contexts/MonitoringContext",
"ols-tool-ui": "./components/ols-tool-ui"
},
"dependencies": {
"@console/pluginAPI": "*"
Expand Down
12 changes: 3 additions & 9 deletions web/src/components/dashboards/perses/PersesWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,6 @@ export function useRemotePluginLoader(): PluginLoader {

export function PersesWrapper({ children, project }: PersesWrapperProps) {
const { theme } = usePatternFlyTheme();
const [dashboardName] = useQueryParam(QueryParams.Dashboard, StringParam);
const muiTheme = getTheme(theme, {
shape: {
borderRadius: 6,
Expand Down Expand Up @@ -371,21 +370,16 @@ export function PersesWrapper({ children, project }: PersesWrapperProps) {
variant="default"
>
<PluginRegistry pluginLoader={pluginLoader}>
{!project ? (
<>{children}</>
) : (
<InnerWrapper project={project} dashboardName={dashboardName}>
{children}
</InnerWrapper>
)}
{!project ? <>{children}</> : <InnerWrapper project={project}>{children}</InnerWrapper>}
</PluginRegistry>
</SnackbarProvider>
</ChartsProvider>
</ThemeProvider>
);
}

function InnerWrapper({ children, project, dashboardName }) {
function InnerWrapper({ children, project }) {
const [dashboardName] = useQueryParam(QueryParams.Dashboard, StringParam);
Comment on lines +381 to +382
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.

const { data } = usePluginBuiltinVariableDefinitions();
const { persesDashboard, persesDashboardLoading } = useFetchPersesDashboard(
project,
Expand Down
4 changes: 4 additions & 0 deletions web/src/components/dashboards/perses/dashboard-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ import {
useDiscardChangesConfirmationDialog,
useEditMode,
} from '@perses-dev/dashboards';

import { OCPDashboardToolbar } from './dashboard-toolbar';
import { useUpdateDashboardMutation } from './dashboard-api';
import { useTranslation } from 'react-i18next';
import { useToast } from './ToastProvider';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { useExternalPanelAddition } from './useExternalPanelAddition';

export interface DashboardAppProps {
dashboardResource: DashboardResource | EphemeralDashboardResource;
Expand Down Expand Up @@ -124,6 +126,8 @@ export const OCPDashboardApp = (props: DashboardAppProps): ReactElement => {
}
};

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.


const updateDashboardMutation = useUpdateDashboardMutation();

const onSave = useCallback(
Expand Down
67 changes: 67 additions & 0 deletions web/src/components/dashboards/perses/useExternalPanelAddition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDashboardActions, useDashboardStore } from '@perses-dev/dashboards';
import { dashboardsOpened, dashboardsPersesPanelExternallyAdded } from '../../../store/actions';

interface UseExternalPanelAdditionOptions {
isEditMode: boolean;
onEditButtonClick: () => void;
}

export function useExternalPanelAddition({
isEditMode,
onEditButtonClick,
}: UseExternalPanelAdditionOptions) {
const dispatch = useDispatch();
const addPersesPanelExternally: any = useSelector(
(s: any) => s.plugins?.mp?.dashboards?.addPersesPanelExternally,
);
const { openAddPanel } = useDashboardActions();
const dashboardStore = useDashboardStore();
const [externallyAddedPanel, setExternallyAddedPanel] = useState(null);

const addPanelExternally = (panelDefinition: any): void => {
// Simulate opening a panel to add the pane so that we can use it to programatically
// add a panel to the dashboard from an external source (AI assistant).
if (!isEditMode) {
onEditButtonClick();
}
openAddPanel();
// Wrap the panelDefinition with the groupId structure
const change = {
groupId: 0,
panelDefinition,
};
setExternallyAddedPanel(change);
};

useEffect(() => {
// Listen for external panel addition requests
if (addPersesPanelExternally) {
addPanelExternally(addPersesPanelExternally);
dispatch(dashboardsPersesPanelExternallyAdded());
}

// 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);
}
}, [externallyAddedPanel, addPersesPanelExternally]);

// Advertise when custom dashboard is opened/closed
useEffect(() => {
dispatch(dashboardsOpened(true));
return () => {
dispatch(dashboardsOpened(false));
};
}, [dispatch]);
}
56 changes: 56 additions & 0 deletions web/src/components/ols-tool-ui/AddToDashboardButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import type { PanelDefinition } from '@perses-dev/core';
import { Button } from '@patternfly/react-core';
import { dashboardsAddPersesPanelExternally } from '../../store/actions';

function createPanelDefinition(query: string): PanelDefinition {
return {
kind: 'Panel',
spec: {
display: {
name: '',
},
Comment on lines +11 to +13
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).

plugin: {
kind: 'TimeSeriesChart',
spec: {},
},
queries: [
{
kind: 'TimeSeriesQuery',
spec: {
plugin: {
kind: 'PrometheusTimeSeriesQuery',
spec: {
query: query,
},
},
},
},
],
},
};
}

type AddToDashboardButtonProps = {
query: string;
};

export const AddToDashboardButton: React.FC<AddToDashboardButtonProps> = ({ query }) => {
const dispatch = useDispatch();

const isCustomDashboardOpen: boolean = useSelector(
(s: any) => s.plugins?.mp?.dashboards?.isOpened,
);

const addToPersesDashboard = React.useCallback(() => {
const panelDefinition = createPanelDefinition(query);
dispatch(dashboardsAddPersesPanelExternally(panelDefinition));
}, [query, dispatch]);

if (!isCustomDashboardOpen) {
return null;
}

return <Button onClick={addToPersesDashboard}>Add to dashboard</Button>;
};
65 changes: 65 additions & 0 deletions web/src/components/ols-tool-ui/ExecuteRangeQuery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react';
import { DataQueriesProvider } from '@perses-dev/plugin-system';
import type { DurationString } from '@perses-dev/prometheus-plugin';
import { Panel } from '@perses-dev/dashboards';

import { OlsToolUIPersesWrapper } from './OlsToolUIPersesWrapper';
import { AddToDashboardButton } from './AddToDashboardButton';

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' }}
>
Comment on lines +9 to +37
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.

<Panel
panelOptions={{
hideHeader: false,
}}
definition={{
kind: 'Panel',
spec: {
queries: [],
display: { name: query },
plugin: {
kind: 'TimeSeriesChart',
spec: {
visual: {
stack: 'all',
},
},
},
},
}}
/>
</DataQueriesProvider>
</OlsToolUIPersesWrapper>
<AddToDashboardButton query={query} />
</>
);
};

export default ExecuteRangeQuery;
47 changes: 47 additions & 0 deletions web/src/components/ols-tool-ui/OlsToolUIPersesWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { VariableProvider } from '@perses-dev/dashboards';
import { TimeRangeProviderBasic } from '@perses-dev/plugin-system';
import type { DurationString } from '@perses-dev/prometheus-plugin';

import {
PersesWrapper,
PersesPrometheusDatasourceWrapper,
} from '../dashboards/perses/PersesWrapper';

const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
refetchOnWindowFocus: false,
},
},
});

interface OlsToolUIPersesWrapperProps {
children: React.ReactNode;
height?: string;
initialTimeRange?: {
pastDuration: DurationString;
};
}

export const OlsToolUIPersesWrapper: React.FC<OlsToolUIPersesWrapperProps> = ({
children,
initialTimeRange = { pastDuration: '1h' as DurationString },
height = '300px',
}) => {
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.

<TimeRangeProviderBasic initialTimeRange={initialTimeRange}>
<VariableProvider>
<PersesPrometheusDatasourceWrapper queries={[]}>
<div style={{ width: '100%', height: height }}>{children}</div>
</PersesPrometheusDatasourceWrapper>
</VariableProvider>
</TimeRangeProviderBasic>
</PersesWrapper>
</QueryClientProvider>
);
};
1 change: 1 addition & 0 deletions web/src/components/ols-tool-ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ExecuteRangeQuery } from './ExecuteRangeQuery';
13 changes: 13 additions & 0 deletions web/src/store/actions.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { PanelDefinition } from '@perses-dev/core';
import { action, ActionType as Action } from 'typesafe-actions';

import { Alert, Rule, Silence } from '@openshift-console/dynamic-plugin-sdk';
Expand All @@ -17,6 +18,9 @@ export enum ActionType {
DashboardsSetPollInterval = 'v2/dashboardsSetPollInterval',
DashboardsSetTimespan = 'v2/dashboardsSetTimespan',
DashboardsVariableOptionsLoaded = 'v2/dashboardsVariableOptionsLoaded',
DashboardsOpened = 'dashboardsPersesDashboardsOpened',
DashboardsAddPersesPanelExternally = 'dashboardsAddPersesPanelExternally',
DashboardsPersesPanelExternallyAdded = 'dashboardsPersesPanelExternallyAdded',
QueryBrowserAddQuery = 'queryBrowserAddQuery',
QueryBrowserDuplicateQuery = 'queryBrowserDuplicateQuery',
QueryBrowserDeleteAllQueries = 'queryBrowserDeleteAllQueries',
Expand Down Expand Up @@ -68,6 +72,15 @@ export const dashboardsSetTimespan = (timespan: number) =>
export const dashboardsVariableOptionsLoaded = (key: string, newOptions: string[]) =>
action(ActionType.DashboardsVariableOptionsLoaded, { key, newOptions });

export const dashboardsOpened = (isOpened: boolean) =>
action(ActionType.DashboardsOpened, { isOpened });

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

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

Comment on lines +75 to +83
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.

export const alertingSetLoading = (datasource: string, identifier: string) =>
action(ActionType.AlertingSetLoading, {
datasource,
Expand Down
15 changes: 15 additions & 0 deletions web/src/store/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,21 @@ const monitoringReducer = produce((draft: ObserveState, action: ObserveAction):
break;
}

case ActionType.DashboardsOpened: {
draft.dashboards.isOpened = action.payload.isOpened;
break;
}

case ActionType.DashboardsAddPersesPanelExternally: {
draft.dashboards.addPersesPanelExternally = action.payload.panelDefinition;
break;
}

case ActionType.DashboardsPersesPanelExternallyAdded: {
draft.dashboards.addPersesPanelExternally = null;
break;
}

case ActionType.AlertingSetRulesLoaded: {
const { datasource, identifier, rules, alerts } = action.payload;

Expand Down
3 changes: 3 additions & 0 deletions web/src/store/store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as _ from 'lodash-es';

import type { PanelDefinition } from '@perses-dev/core';
import { MONITORING_DASHBOARDS_DEFAULT_TIMESPAN } from '../components/dashboards/legacy/utils';
import { Alert, PrometheusLabels, Rule } from '@openshift-console/dynamic-plugin-sdk';
import { Silences } from '../components/types';
Expand Down Expand Up @@ -27,6 +28,8 @@ export type ObserveState = {
pollInterval: number;
timespan: number;
variables: Record<string, Variable>;
isOpened: boolean;
addPersesPanelExternally: PanelDefinition;
Comment on lines +31 to +32
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.

};
incidentsData: {
incidents: Array<any>;
Expand Down