Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

import { AuthProvider } from "@agent-management-platform/auth";
import { ClientProvider } from "@agent-management-platform/api-client";
import { NotificationProvider, ConfirmationDialogProvider } from "@agent-management-platform/shared-component";
import { OxygenUIThemeProvider } from "@wso2/oxygen-ui";
import { ConfirmationDialogProvider } from "@agent-management-platform/shared-component";

export const GlobalProviders = ({
children,
Expand All @@ -28,11 +28,13 @@ export const GlobalProviders = ({
}) => {
return (
<OxygenUIThemeProvider radialBackground>
<AuthProvider>
<ClientProvider>
<ConfirmationDialogProvider>{children}</ConfirmationDialogProvider>
</ClientProvider>
</AuthProvider>
<NotificationProvider>
<AuthProvider>
<ClientProvider>
<ConfirmationDialogProvider>{children}</ConfirmationDialogProvider>
</ClientProvider>
</AuthProvider>
</NotificationProvider>
</OxygenUIThemeProvider>
);
};
3 changes: 3 additions & 0 deletions console/common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Wrench } from "@wso2/oxygen-ui-icons-react";
import { Box, Button, Typography } from "@wso2/oxygen-ui";
import { FormProvider, useForm } from "react-hook-form";
import { TextInput, DrawerHeader, DrawerContent } from "@agent-management-platform/views";
import { useNotification } from "../providers";

interface BuildPanelProps {
onClose: () => void;
Expand All @@ -41,41 +42,41 @@ export function BuildPanel({
agentName,
}: BuildPanelProps) {
const { mutate: buildAgent, isPending } = useBuildAgent();
const { notify } = useNotification();
const { data: agent, isLoading: isLoadingAgent } = useGetAgent({
orgName,
projName,
agentName,
}); const methods = useForm<BuildFormData>({
});

const methods = useForm<BuildFormData>({
defaultValues: {
branch: "main",
commitId: "",
},
});

const handleBuild = async () => {
try {
const formData = methods.getValues();
buildAgent({
params: {
orgName,
projName,
agentName,
},
query: {
commitId: formData.commitId || "",
},
}, {
onSuccess: () => {
onClose();
},
onError: (error) => {
console.error("Build trigger failed:", error);
},
});
}
catch (error) {
console.error("Build trigger failed:", error);
}
const handleBuild = () => {
const formData = methods.getValues();
buildAgent({
params: {
orgName,
projName,
agentName,
},
query: {
commitId: formData.commitId || "",
},
}, {
onSuccess: () => {
notify('success', 'Build triggered successfully');
onClose();
},
onError: (error) => {
const message = error instanceof Error ? error.message : 'Build trigger failed';
notify('error', message);
},
});
};

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Typography,
CircularProgress,
} from '@wso2/oxygen-ui';

export interface ConfirmDialogProps {
open: boolean;
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
confirmColor?: 'error' | 'primary' | 'secondary';
onConfirm: () => void;
onCancel: () => void;
isLoading?: boolean;
}

/**
* Reusable confirmation dialog for destructive or important actions.
* Use for delete confirmations, irreversible operations, etc.
*/
export function ConfirmDialog({
open,
title,
message,
confirmLabel = 'Confirm',
cancelLabel = 'Cancel',
confirmColor = 'error',
onConfirm,
onCancel,
isLoading = false,
}: ConfirmDialogProps) {
return (
<Dialog open={open} onClose={onCancel} maxWidth="xs" fullWidth>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<Typography variant="body2" color="text.secondary">
{message}
</Typography>
</DialogContent>
<DialogActions>
<Button
onClick={onCancel}
variant="outlined"
disabled={isLoading}
>
{cancelLabel}
</Button>
<Button
onClick={onConfirm}
variant="contained"
color={confirmColor}
disabled={isLoading}
startIcon={isLoading ? <CircularProgress size={16} color="inherit" /> : undefined}
>
{isLoading ? 'Loading...' : confirmLabel}
</Button>
</DialogActions>
</Dialog>
);
}
Comment on lines +29 to +84
Copy link
Contributor

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:

#!/bin/bash
# Check if ConfirmDialog is imported or used anywhere in the codebase
rg -n "ConfirmDialog" --type ts --type tsx -C 2

Repository: wso2/ai-agent-management-platform

Length of output: 104


🏁 Script executed:

#!/bin/bash
# Search for ConfirmDialog in all files, then filter to .ts and .tsx
rg "ConfirmDialog" -C 2 | head -100

Repository: wso2/ai-agent-management-platform

Length of output: 2041


🏁 Script executed:

#!/bin/bash
# Search for ConfirmDialog imports
echo "=== ConfirmDialog imports ==="
rg "import.*ConfirmDialog" -C 2

echo ""
echo "=== ConfirmationDialogProvider usage ==="
rg "ConfirmationDialogProvider|useConfirmationDialog" -C 2 | head -150

Repository: wso2/ai-agent-management-platform

Length of output: 4710


🏁 Script executed:

#!/bin/bash
# Look at the ConfirmationDialogProvider implementation
echo "=== ConfirmationDialogProvider location and structure ==="
find . -name "ConfirmationDialogProvider.tsx" -exec wc -l {} \; -exec head -80 {} \;

Repository: wso2/ai-agent-management-platform

Length of output: 2315


Remove or document the ConfirmDialog component usage.

The ConfirmDialog component is exported but never imported or used anywhere in the codebase. The application already uses ConfirmationDialogProvider with the useConfirmationDialog hook (as seen in GlobalProviders.tsx, AgentsList.tsx, and ProjectList.tsx), which provides a centralized, queue-based confirmation pattern.

If ConfirmDialog serves a different purpose (e.g., direct prop-controlled confirmations vs. global state-managed ones), add documentation clarifying when to use each. Otherwise, remove it to avoid confusion and reduce bundle size.

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { EnvironmentVariable } from "./EnvironmentVariable";
import type { Environment, EnvironmentVariable as EnvVar } from "@agent-management-platform/types";
import { useEffect } from "react";
import { TextInput, DrawerHeader, DrawerContent } from "@agent-management-platform/views";
import { useNotification } from "../providers";

interface DeploymentConfigProps {
onClose: () => void;
Expand All @@ -49,6 +50,7 @@ export function DeploymentConfig({
imageId,
}: DeploymentConfigProps) {
const { mutate: deployAgent, isPending } = useDeployAgent();
const { notify } = useNotification();
const { data: agent, isLoading: isLoadingAgent } = useGetAgent({
orgName,
projName,
Expand Down Expand Up @@ -77,34 +79,36 @@ export function DeploymentConfig({
});
}, [configurations, methods]);

const handleDeploy = async () => {
try {
const formData = methods.getValues();
const handleDeploy = () => {
const formData = methods.getValues();

const envVariables: EnvVar[] = formData.env
.filter((envVar: { key: string; value: string }) => envVar.key && envVar.value)
.map((envVar: { key: string; value: string }) => ({
key: envVar.key,
value: envVar.value,
}));
deployAgent({
params: {
orgName,
projName,
agentName,
},
body: {
imageId: imageId,
env: envVariables.length > 0 ? envVariables : undefined,
},
}, {
onSuccess: () => {
onClose();
},
});
} catch {
// Error handling is done by the mutation
}
const envVariables: EnvVar[] = formData.env
.filter((envVar: { key: string; value: string }) => envVar.key && envVar.value)
.map((envVar: { key: string; value: string }) => ({
key: envVar.key,
value: envVar.value,
}));

deployAgent({
params: {
orgName,
projName,
agentName,
},
body: {
imageId: imageId,
env: envVariables.length > 0 ? envVariables : undefined,
},
}, {
onSuccess: () => {
notify('success', 'Deployment started successfully');
onClose();
},
onError: (error) => {
const message = error instanceof Error ? error.message : 'Deployment failed';
notify('error', message);
},
});
};


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

export * from './BuildLogs';
export * from './BuildPanel';
export * from './ConfirmDialog';
export * from './BuildSteps';
export * from './CodeBlock';
export * from './DeploymentConfig';
Expand Down
1 change: 1 addition & 0 deletions console/workspaces/libs/shared-component/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
*/

export * from './components';
export * from './providers';
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { createContext } from 'react';

// Notification severity types matching MUI Alert
export type NotificationType = 'success' | 'error' | 'warning' | 'info';

// Shape of the context value
export interface NotificationContextType {
notify: (type: NotificationType, message: string) => void;
}

// Context with null default - useNotification hook will throw if used outside provider
export const NotificationContext = createContext<NotificationContextType | null>(null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React, { useCallback, useMemo, useState } from 'react';
import { Alert, Snackbar } from '@wso2/oxygen-ui';
import { NotificationContext, NotificationType } from './NotificationContext';

interface Notification {
id: string;
type: NotificationType;
message: string;
}

interface NotificationProviderProps {
children: React.ReactNode;
}

const AUTO_HIDE_DURATION = 5000;

export function NotificationProvider({ children }: NotificationProviderProps) {
const [notifications, setNotifications] = useState<Notification[]>([]);

// Add a new notification
const notify = useCallback((type: NotificationType, message: string) => {
const id = Date.now().toString();
setNotifications((prev) => [...prev, { id, type, message }]);
}, []);
Comment on lines +39 to +42
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential ID collision with Date.now().

Using Date.now().toString() for notification IDs may result in collisions if multiple notifications are triggered in rapid succession (within the same millisecond).

🔎 Consider using a more robust ID generation:
-  const notify = useCallback((type: NotificationType, message: string) => {
-    const id = Date.now().toString();
+  const notify = useCallback((type: NotificationType, message: string) => {
+    const id = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
     setNotifications((prev) => [...prev, { id, type, message }]);
   }, []);
📝 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
const notify = useCallback((type: NotificationType, message: string) => {
const id = Date.now().toString();
setNotifications((prev) => [...prev, { id, type, message }]);
}, []);
const notify = useCallback((type: NotificationType, message: string) => {
const id = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
setNotifications((prev) => [...prev, { id, type, message }]);
}, []);
🤖 Prompt for AI Agents
In
console/workspaces/libs/shared-component/src/providers/NotificationProvider.tsx
around lines 39-42, the notification ID is generated using Date.now().toString()
which can collide for multiple notifications in the same millisecond; change the
ID generation to a robust method such as crypto.randomUUID() (with a small
fallback to a useRef-based incrementing counter or a lightweight library like
nanoid for environments without crypto.randomUUID), update the function to call
that generator (or fall back) when creating the id, and ensure any TS types
remain compatible.


// Remove a notification by id
const removeNotification = useCallback((id: string) => {
setNotifications((prev) => prev.filter((n) => n.id !== id));
}, []);

// Memoize context value to prevent unnecessary re-renders
const contextValue = useMemo(() => ({ notify }), [notify]);

return (
<NotificationContext.Provider value={contextValue}>
{children}
{/* Render notifications - show only the first one, queue the rest */}
{notifications.length > 0 && (
<Snackbar
open
autoHideDuration={AUTO_HIDE_DURATION}
onClose={() => removeNotification(notifications[0].id)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
>
<Alert
severity={notifications[0].type}
onClose={() => removeNotification(notifications[0].id)}
sx={{ width: '100%' }}
>
{notifications[0].message}
</Alert>
</Snackbar>
)}
</NotificationContext.Provider>
);
}
Loading