diff --git a/.nvmrc b/.nvmrc
index 8fdd954df..cabf43b5d 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-22
\ No newline at end of file
+24
\ No newline at end of file
diff --git a/README.md b/README.md
index b83bb2748..d5fe12ff2 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ Run tests:
npm run test
```
-Start dev server:
+Start dev server (with Web distribution):
```shell
npm run start
@@ -42,21 +42,28 @@ Supported `platform` values:
Build of the package will be in the `./dist` directory
-## Environment variables
+## Environment variables for Web distribution
To set environment variables use .env file
-| Name | Type | Default | Description |
-| ----------------- | ------ | ------- | --------------------------------------------------------- |
-| PORT | number | 3000 | Port (for dev server) |
-| UI_BASE_URL | string | - | Base URL to proxy requests to ingress (for dev server) |
-| JAEGER_API_PATH | string | - | URL path to proxy requests to Jaeger UI (for dev server ) |
-| API_BASE_URL | string | - | Base URL to proxy Digma API requests (for dev server) |
-| AUTH_API_BASE_URL | string | - | Base URL to proxy auth API requests (for dev server) |
-| API_TOKEN | string | - | API token (for dev server) |
-| USERNAME | string | - | User login (for dev server) |
-| PASSWORD | string | - | User password (for dev server) |
-| JAEGER_UI_PATH | string | - | Path to custom Jaeger UI build |
+| Name | Type | Default | Description |
+| ------------------------------- | ------- | ------- | -------------------------------------------------------- |
+| PORT | number | 3000 | Port (for dev server) |
+| UI_BASE_URL | string | - | Base URL to proxy requests to ingress (for dev server) |
+| JAEGER_API_PATH | string | - | URL path to proxy requests to Jaeger UI (for dev server) |
+| API_BASE_URL | string | - | Base URL to proxy Digma API requests (for dev server) |
+| AUTH_API_BASE_URL | string | - | Base URL to proxy auth API requests (for dev server) |
+| API_TOKEN | string | - | API token (for dev server) |
+| LOGIN | string | - | User login (for dev server) |
+| PASSWORD | string | - | User password (for dev server) |
+| IS_JAEGER_ENABLED | boolean | false | Enable links to Jaeger |
+| JAEGER_UI_PATH | string | - | Path to custom Jaeger UI build |
+| IS_SANDBOX_ENABLED | boolean | false | Enable Sandbox (demo) mode |
+| ARE_INSIGHT_SUGGESTIONS_ENABLED | boolean | false | Enable insight suggestions |
+| GOOGLE_CLIENT_ID | string | - | Google client ID |
+| POSTHOG_API_KEY | string | - | PostHog API key |
+| POSTHOG_URL | string | - | PostHog URL |
+| PRODUCT_FRUITS_WORKSPACE_CODE | string | - | Product Fruits workspace code |
## Jaeger UI
diff --git a/package-lock.json b/package-lock.json
index 2ae4adfdf..6c2e80cc8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "digma-ui",
- "version": "15.2.1",
+ "version": "15.3.0-alpha.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "digma-ui",
- "version": "15.2.1",
+ "version": "15.3.0-alpha.3",
"license": "MIT",
"dependencies": {
"@floating-ui/react": "^0.25.1",
diff --git a/package.json b/package.json
index 236b9c429..5f08fdbed 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "digma-ui",
- "version": "15.2.1",
+ "version": "15.3.0-alpha.3",
"description": "Digma UI",
"scripts": {
"lint:eslint": "eslint --cache .",
diff --git a/src/components/Admin/Environments/ActionsMenuButton/index.tsx b/src/components/Admin/Environments/ActionsMenuButton/index.tsx
index ce52255e8..d7c8cea00 100644
--- a/src/components/Admin/Environments/ActionsMenuButton/index.tsx
+++ b/src/components/Admin/Environments/ActionsMenuButton/index.tsx
@@ -1,50 +1,25 @@
-import { useState } from "react";
import { useAdminDispatch } from "../../../../containers/Admin/hooks";
import { setEnvironmentToDelete } from "../../../../redux/slices/environmentsManagerSlice";
import { TrashBinIcon } from "../../../common/icons/16px/TrashBinIcon";
-import { ThreeDotsVerticalIcon } from "../../../common/icons/ThreeDotsVerticalIcon";
-import { NewPopover } from "../../../common/NewPopover";
-import { NewIconButton } from "../../../common/v3/NewIconButton";
-import { MenuList } from "../../../Navigation/common/MenuList";
-import * as s from "./styles";
+import { KebabMenu } from "../../../common/KebabMenu";
+import type { MenuItem } from "../../../Navigation/common/MenuList/types";
import type { ActionMenuButtonProps } from "./types";
export const ActionsMenuButton = ({ environment }: ActionMenuButtonProps) => {
- const [isKebabButtonMenuOpen, setIsKebabButtonMenuOpen] = useState(false);
const dispatch = useAdminDispatch();
const handleDeleteMenuItemClick = () => {
dispatch(setEnvironmentToDelete(environment.id));
};
- const handleKebabMenuOpenChange = (isOpen: boolean) => {
- setIsKebabButtonMenuOpen(isOpen);
- };
+ const items: MenuItem[] = [
+ {
+ id: "delete",
+ icon: ,
+ label: "Delete",
+ onClick: handleDeleteMenuItemClick
+ }
+ ];
- return (
-
- ,
- label: "Delete",
- onClick: handleDeleteMenuItemClick
- }
- ]}
- />
-
- }
- onOpenChange={handleKebabMenuOpenChange}
- isOpen={isKebabButtonMenuOpen}
- placement={"bottom-end"}
- >
-
-
- );
+ return ;
};
diff --git a/src/components/Agentic/IncidentDetails/AgentEvents/index.tsx b/src/components/Agentic/IncidentDetails/AgentEvents/index.tsx
index c0efcffbc..b15e90d1a 100644
--- a/src/components/Agentic/IncidentDetails/AgentEvents/index.tsx
+++ b/src/components/Agentic/IncidentDetails/AgentEvents/index.tsx
@@ -80,7 +80,10 @@ export const AgentEvents = () => {
);
const isAgentRunning = useMemo(
- () => Boolean(agentsData?.agents.find((x) => x.name === agentId)?.running),
+ () =>
+ Boolean(
+ agentsData?.agents.find((x) => x.name === agentId)?.status === "running"
+ ),
[agentsData, agentId]
);
@@ -104,15 +107,22 @@ export const AgentEvents = () => {
speed={shouldShowTypingForEvent(i) ? TYPING_SPEED : undefined}
/>
);
- case "tool":
+ case "tool": {
+ let toolName = event.tool_name;
+
+ if (event.mcp_name) {
+ toolName += ` ${[event.mcp_name, "MCP tool"]
+ .filter(Boolean)
+ .join(" ")})`;
+ }
+
return (
}
/>
);
+ }
default:
return null;
}
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/AgentFlowChartNodeToolbar/index.tsx b/src/components/Agentic/IncidentDetails/AgentFlowChart/AgentFlowChartNodeToolbar/index.tsx
index d6895e679..20d2afc90 100644
--- a/src/components/Agentic/IncidentDetails/AgentFlowChart/AgentFlowChartNodeToolbar/index.tsx
+++ b/src/components/Agentic/IncidentDetails/AgentFlowChart/AgentFlowChartNodeToolbar/index.tsx
@@ -1,6 +1,6 @@
import { Position } from "@xyflow/react";
import { PlusIcon } from "../../../../common/icons/16px/PlusIcon";
-import { MCPServersSideContainer } from "../MCPServersSideContainer";
+import { MCPServersContainer } from "../MCPServersContainer";
import { MCPServersToolbar } from "../MCPServersToolbar";
import * as s from "./styles";
import type { AgentFlowChartNodeToolbarProps } from "./types";
@@ -10,15 +10,20 @@ export const AgentFlowChartNodeToolbar = ({
position,
isEditMode,
onAddMCPServer,
- onEditMCPServers,
+ onEditMCPServer,
+ onDeleteMCPServer,
showPlusButton
}: AgentFlowChartNodeToolbarProps) => {
const handleAddMCPServer = () => {
- onAddMCPServer(position);
+ onAddMCPServer();
};
- const handleEditMCPServers = () => {
- onEditMCPServers(position);
+ const handleEditMCPServer = (server: string) => {
+ onEditMCPServer(server);
+ };
+
+ const handleDeleteMCPServer = (server: string) => {
+ onDeleteMCPServer(server);
};
const toolbarItems = [
@@ -27,8 +32,8 @@ export const AgentFlowChartNodeToolbar = ({
]
: []),
@@ -50,11 +55,9 @@ export const AgentFlowChartNodeToolbar = ({
return (
<>
{isEditMode ? (
- [Position.Top, Position.Bottom].includes(position) && (
- {sortedToolbarItems}
- )
+ {sortedToolbarItems}
) : (
-
+
)}
>
);
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/AgentFlowChartNodeToolbar/types.ts b/src/components/Agentic/IncidentDetails/AgentFlowChart/AgentFlowChartNodeToolbar/types.ts
index 2d404286f..267c4dd31 100644
--- a/src/components/Agentic/IncidentDetails/AgentFlowChart/AgentFlowChartNodeToolbar/types.ts
+++ b/src/components/Agentic/IncidentDetails/AgentFlowChart/AgentFlowChartNodeToolbar/types.ts
@@ -5,7 +5,8 @@ export interface AgentFlowChartNodeToolbarProps {
isEditMode?: boolean;
position: Position;
servers: ExtendedAgentMCPServer[];
- onAddMCPServer: (position: Position) => void;
- onEditMCPServers: (position: Position) => void;
+ onAddMCPServer: () => void;
+ onEditMCPServer: (server: string) => void;
+ onDeleteMCPServer: (server: string) => void;
showPlusButton?: boolean;
}
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServerIcon/index.tsx b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServerIcon/index.tsx
index c31713e43..963320f85 100644
--- a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServerIcon/index.tsx
+++ b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServerIcon/index.tsx
@@ -27,9 +27,7 @@ export const MCPServerIcon = ({
themeKind={isActive ? "light" : "dark"}
/>
);
- case "mcp":
- return ;
default:
- return null;
+ return ;
}
};
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersContainer/index.tsx b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersContainer/index.tsx
new file mode 100644
index 000000000..303bf8c88
--- /dev/null
+++ b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersContainer/index.tsx
@@ -0,0 +1,32 @@
+import { useViewport } from "@xyflow/react";
+import { Tooltip } from "../../../../common/v3/Tooltip";
+import { MCPServerIcon } from "../MCPServerIcon";
+import * as s from "./styles";
+import type { MCPServersContainerProps } from "./types";
+
+const DEFAULT_ICON_SIZE = 27; // in pixels
+
+export const MCPServersContainer = ({ servers }: MCPServersContainerProps) => {
+ const viewport = useViewport();
+ const zoomLevel = viewport.zoom;
+
+ if (!servers || servers.length === 0) {
+ return null;
+ }
+
+ return (
+
+ {servers?.map((x) => (
+
+
+
+
+
+ ))}
+
+ );
+};
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersSideContainer/styles.ts b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersContainer/styles.ts
similarity index 100%
rename from src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersSideContainer/styles.ts
rename to src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersContainer/styles.ts
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersSideContainer/types.ts b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersContainer/types.ts
similarity index 83%
rename from src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersSideContainer/types.ts
rename to src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersContainer/types.ts
index d09e89417..dbd06fb09 100644
--- a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersSideContainer/types.ts
+++ b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersContainer/types.ts
@@ -1,6 +1,6 @@
import type { ExtendedAgentMCPServer } from "../types";
-export interface MCPServersSideContainerProps {
+export interface MCPServersContainerProps {
servers: ExtendedAgentMCPServer[];
}
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersSideContainer/index.tsx b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersSideContainer/index.tsx
deleted file mode 100644
index 6e5505b79..000000000
--- a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersSideContainer/index.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { useViewport } from "@xyflow/react";
-import { MCPServerIcon } from "../MCPServerIcon";
-import * as s from "./styles";
-import type { MCPServersSideContainerProps } from "./types";
-
-const DEFAULT_ICON_SIZE = 27; // in pixels
-
-export const MCPServersSideContainer = ({
- servers
-}: MCPServersSideContainerProps) => {
- const viewport = useViewport();
- const zoomLevel = viewport.zoom;
-
- if (!servers || servers.length === 0) {
- return null;
- }
-
- return (
-
- {servers?.map((x) => (
-
-
-
- ))}
-
- );
-};
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersToolbar/index.tsx b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersToolbar/index.tsx
index 99a74fc0f..b0953aae3 100644
--- a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersToolbar/index.tsx
+++ b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersToolbar/index.tsx
@@ -1,10 +1,10 @@
import { useNodeId, useViewport } from "@xyflow/react";
import { useState } from "react";
import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent";
-import { PlusIcon } from "../../../../common/icons/16px/PlusIcon";
+import { TrashBinIcon } from "../../../../common/icons/16px/TrashBinIcon";
import { WrenchIcon } from "../../../../common/icons/16px/WrenchIcon";
-import { ThreeDotsVerticalIcon } from "../../../../common/icons/ThreeDotsVerticalIcon";
import { NewPopover } from "../../../../common/NewPopover";
+import { Tooltip } from "../../../../common/v3/Tooltip";
import { MenuList } from "../../../../Navigation/common/MenuList";
import type { MenuItem } from "../../../../Navigation/common/MenuList/types";
import { Popup } from "../../../../Navigation/common/Popup";
@@ -15,40 +15,51 @@ import type { MCPServersToolbarProps } from "./types";
export const MCPServersToolbar = ({
servers,
- onAddMCPServer,
- onEditMCPServers
+ onEditMCPServer,
+ onDeleteMCPServer
}: MCPServersToolbarProps) => {
const [isKebabMenuOpen, setIsKebabMenuOpen] = useState(false);
+ const [selectedMCPServer, setSelectedMCPServer] = useState();
const id = useNodeId();
const viewport = useViewport();
const zoomLevel = viewport.zoom;
- const handleKebabMenuOpenChange = (isOpen: boolean) => {
- sendUserActionTrackingEvent(
- trackingEvents.FLOW_CHART_NODE_MCP_TOOLBAR_MENU_CLICKED,
- {
- id,
- isOpen
- }
- );
+ const handleKebabMenuOpenChange = (server: string) => (isOpen: boolean) => {
+ if (isOpen) {
+ sendUserActionTrackingEvent(
+ trackingEvents.FLOW_CHART_NODE_MCP_TOOLBAR_SERVER_ICON_CLICKED,
+ {
+ id
+ }
+ );
+ setSelectedMCPServer(server);
+ } else {
+ setSelectedMCPServer(undefined);
+ }
setIsKebabMenuOpen(isOpen);
};
const handleKebabMenuItemClick = (id: string) => {
sendUserActionTrackingEvent(
- trackingEvents.FLOW_CHART_NODE_MCP_TOOLBAR_MENU_ITEM_CLICKED,
+ trackingEvents.FLOW_CHART_NODE_MCP_TOOLBAR_SERVER_ICON_MENU_ITEM_CLICKED,
{
id
}
);
switch (id) {
- case "edit":
- onEditMCPServers?.();
+ case "edit": {
+ if (selectedMCPServer) {
+ onEditMCPServer(selectedMCPServer);
+ }
break;
- case "add":
- onAddMCPServer?.();
+ }
+ case "delete": {
+ if (selectedMCPServer) {
+ onDeleteMCPServer(selectedMCPServer);
+ }
break;
+ }
}
setIsKebabMenuOpen(false);
@@ -58,41 +69,42 @@ export const MCPServersToolbar = ({
{
id: "edit",
icon: ,
- label: "Edit MCPs",
+ label: "Edit",
onClick: () => handleKebabMenuItemClick("edit")
},
{
- id: "add",
- icon: ,
- label: "Add new MCP",
- onClick: () => handleKebabMenuItemClick("add")
+ id: "delete",
+ icon: ,
+ label: "Delete",
+ onClick: () => handleKebabMenuItemClick("delete")
}
];
return (
{servers.map((x) => (
-
+ placement={"bottom-end"}
+ content={
+
+
+
+ }
+ isOpen={Boolean(
+ isKebabMenuOpen && selectedMCPServer === x.name && x.isEditable
+ )}
+ onOpenChange={handleKebabMenuOpenChange(x.name)}
+ >
+
+
+
+
+
+
+
+
))}
-
-
-
- }
- isOpen={isKebabMenuOpen}
- onOpenChange={handleKebabMenuOpenChange}
- >
-
-
-
-
);
};
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersToolbar/styles.ts b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersToolbar/styles.ts
index ef0a895db..c3a6efd1a 100644
--- a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersToolbar/styles.ts
+++ b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersToolbar/styles.ts
@@ -1,5 +1,5 @@
import styled from "styled-components";
-import type { ContainerProps } from "./types";
+import type { ContainerProps, MCPServerIconContainerProps } from "./types";
export const Container = styled.div`
display: flex;
@@ -18,6 +18,13 @@ export const Container = styled.div`
);
`;
+export const MCPServerIconContainer = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: ${({ $isEditable }) => ($isEditable ? "pointer" : "default")};
+`;
+
export const KebabMenuButton = styled.button`
border: none;
display: flex;
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersToolbar/types.ts b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersToolbar/types.ts
index 01a22108d..cdbe0e81b 100644
--- a/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersToolbar/types.ts
+++ b/src/components/Agentic/IncidentDetails/AgentFlowChart/MCPServersToolbar/types.ts
@@ -2,10 +2,14 @@ import type { ExtendedAgentMCPServer } from "../types";
export interface MCPServersToolbarProps {
servers: ExtendedAgentMCPServer[];
- onAddMCPServer: () => void;
- onEditMCPServers: () => void;
+ onEditMCPServer: (server: string) => void;
+ onDeleteMCPServer: (server: string) => void;
}
export interface ContainerProps {
$zoomLevel?: number;
}
+
+export interface MCPServerIconContainerProps {
+ $isEditable?: boolean;
+}
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/index.tsx b/src/components/Agentic/IncidentDetails/AgentFlowChart/index.tsx
index b623bb37c..96db408be 100644
--- a/src/components/Agentic/IncidentDetails/AgentFlowChart/index.tsx
+++ b/src/components/Agentic/IncidentDetails/AgentFlowChart/index.tsx
@@ -1,6 +1,5 @@
import { Position, type Edge } from "@xyflow/react";
import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent";
-import { groupBy } from "../../../../utils/groupBy";
import { FlowChart } from "../../common/FlowChart";
import type {
FlowChartNode,
@@ -16,21 +15,22 @@ const getFlowChartNodeData = ({
isInteractive,
isEditMode,
onAddMCPServer,
- onEditMCPServers
+ onEditMCPServer,
+ onDeleteMCPServer
}: {
agent?: ExtendedAgent;
isInteractive?: boolean;
isSelected?: boolean;
isEditMode?: boolean;
- onAddMCPServer?: (agentName: string, position: Position) => void;
- onEditMCPServers?: (agentName: string, position: Position) => void;
+ onAddMCPServer?: (agentName: string) => void;
+ onEditMCPServer?: (agentName: string, server: string) => void;
+ onDeleteMCPServer?: (agentName: string, server: string) => void;
}): Partial => {
- const handleAddMCPServer = (position: Position) => () => {
+ const handleAddMCPServer = () => {
sendUserActionTrackingEvent(
trackingEvents.AGENT_FLOW_CHART_NODE_ADD_MCP_SERVER_BUTTON_CLICKED,
{
- agentName: agent?.name,
- position
+ agentName: agent?.name
}
);
@@ -38,54 +38,54 @@ const getFlowChartNodeData = ({
return;
}
- onAddMCPServer?.(agent.name, position);
+ onAddMCPServer?.(agent.name);
};
- const handleEditMCPServers = (position: Position) => () => {
+ const handleEditMCPServer = (server: string) => {
if (!agent) {
return;
}
- onEditMCPServers?.(agent.name, position);
+ onEditMCPServer?.(agent.name, server);
};
- const serverGroups = groupBy(
- agent?.mcp_servers ?? [],
- (server) => server.position ?? Position.Top
- );
+ const handleDeleteMCPServer = (server: string) => {
+ if (!agent) {
+ return;
+ }
+
+ onDeleteMCPServer?.(agent.name, server);
+ };
+
+ const sideContainerPosition =
+ agent?.name === "code_resolver" ? Position.Bottom : Position.Top;
return agent
? {
label: agent.display_name,
isActive: isSelected,
- isRunning: agent.running,
+ isRunning: agent.status === "running",
+ isPending: agent.status === "pending",
isInteractive,
- isDisabled: agent.status === "inactive",
- sideContainers: Object.values(Position).map((position) => ({
- isVisible: Boolean(
- serverGroups[position]?.length > 0 ||
- (isEditMode && [Position.Top, Position.Bottom].includes(position))
- ),
- position,
- element: (
-
- ),
- isKebabMenuVisible: isEditMode
- }))
+ isDisabled: agent.status === "skipped",
+ sideContainers: [
+ {
+ isVisible: Boolean(agent.mcp_servers.length > 0 || isEditMode),
+ position: sideContainerPosition,
+ element: (
+
+ )
+ }
+ ],
+ isKebabMenuVisible: isEditMode
}
: {};
};
@@ -97,33 +97,28 @@ export const AgentFlowChart = ({
className,
isEditMode,
onAddMCPServer,
- onEditMCPServers
+ onEditMCPServer,
+ onDeleteMCPServer
}: AgentFlowChartProps) => {
const extendedAgents: ExtendedAgent[] = [
{
name: "digma",
display_name: "Digma",
description: "Digma",
- running: false,
- status: "active",
+ status: "waiting",
mcp_servers: []
},
...agents.map((agent) => ({
...agent,
mcp_servers: agent.mcp_servers.map((server) => ({
- ...server,
- position:
- agent.name === "code_resolver" && !isEditMode
- ? Position.Bottom
- : server.position
+ ...server
}))
})),
{
name: "validator",
display_name: "Validator",
description: "Validator",
- running: false,
- status: "active",
+ status: "waiting",
mcp_servers: []
}
];
@@ -142,7 +137,7 @@ export const AgentFlowChart = ({
case "code_resolver":
{
if (
- extendedAgents?.find((a) => a.name === id)?.status === "inactive"
+ extendedAgents?.find((a) => a.name === id)?.status === "skipped"
) {
break;
}
@@ -179,10 +174,11 @@ export const AgentFlowChart = ({
isSelected: "watchman" === selectedAgentId,
isInteractive:
extendedAgents?.find((a) => a.name === "watchman")?.status !==
- "inactive",
+ "skipped",
isEditMode,
onAddMCPServer,
- onEditMCPServers
+ onEditMCPServer,
+ onDeleteMCPServer
})
}
},
@@ -195,10 +191,11 @@ export const AgentFlowChart = ({
isSelected: "triager" === selectedAgentId,
isInteractive:
extendedAgents?.find((a) => a.name === "triager")?.status !==
- "inactive",
+ "skipped",
isEditMode,
onAddMCPServer,
- onEditMCPServers
+ onEditMCPServer,
+ onDeleteMCPServer
})
}
},
@@ -211,10 +208,11 @@ export const AgentFlowChart = ({
isSelected: "infra_resolver" === selectedAgentId,
isInteractive:
extendedAgents?.find((a) => a.name === "infra_resolver")?.status !==
- "inactive",
+ "skipped",
isEditMode,
onAddMCPServer,
- onEditMCPServers
+ onEditMCPServer,
+ onDeleteMCPServer
})
}
},
@@ -227,10 +225,11 @@ export const AgentFlowChart = ({
isSelected: "code_resolver" === selectedAgentId,
isInteractive:
extendedAgents?.find((a) => a.name === "code_resolver")?.status !==
- "inactive",
+ "skipped",
isEditMode,
onAddMCPServer,
- onEditMCPServers
+ onEditMCPServer,
+ onDeleteMCPServer
})
}
},
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/mockData.ts b/src/components/Agentic/IncidentDetails/AgentFlowChart/mockData.ts
index 412c36ac3..e5f345a52 100644
--- a/src/components/Agentic/IncidentDetails/AgentFlowChart/mockData.ts
+++ b/src/components/Agentic/IncidentDetails/AgentFlowChart/mockData.ts
@@ -5,16 +5,14 @@ export const mockedAgents: Agent[] = [
name: "digma",
display_name: "Digma",
description: "Digma",
- running: false,
- status: "active",
+ status: "waiting",
mcp_servers: []
},
{
name: "watchman",
display_name: "Watchman",
description: "Watchman",
- running: true,
- status: "active",
+ status: "waiting",
mcp_servers: [
{
name: "github",
@@ -37,7 +35,6 @@ export const mockedAgents: Agent[] = [
name: "triager",
display_name: "Triage",
description: "Triage",
- running: false,
status: "pending",
mcp_servers: [
{
@@ -61,7 +58,6 @@ export const mockedAgents: Agent[] = [
name: "infra_resolver",
display_name: "Infra Resolution",
description: "Infra Resolution",
- running: false,
status: "pending",
mcp_servers: [
{
@@ -85,8 +81,7 @@ export const mockedAgents: Agent[] = [
name: "code_resolver",
display_name: "Code Resolution",
description: "Code Resolution",
- running: false,
- status: "inactive",
+ status: "skipped",
mcp_servers: [
{
name: "github",
@@ -109,7 +104,6 @@ export const mockedAgents: Agent[] = [
name: "validator",
display_name: "Validator",
description: "Validator",
- running: false,
status: "pending",
mcp_servers: [
{
diff --git a/src/components/Agentic/IncidentDetails/AgentFlowChart/types.ts b/src/components/Agentic/IncidentDetails/AgentFlowChart/types.ts
index 8d81155ff..316bac055 100644
--- a/src/components/Agentic/IncidentDetails/AgentFlowChart/types.ts
+++ b/src/components/Agentic/IncidentDetails/AgentFlowChart/types.ts
@@ -1,4 +1,3 @@
-import type { Position } from "@xyflow/react";
import type { Agent, AgentMCPServer } from "../../../../redux/services/types";
export interface AgentFlowChartProps {
@@ -7,12 +6,13 @@ export interface AgentFlowChartProps {
selectedAgentId: string | null;
className?: string;
isEditMode?: boolean;
- onAddMCPServer?: (agentId: string, position: Position) => void;
- onEditMCPServers?: (agentId: string) => void;
+ onAddMCPServer?: (agentId: string) => void;
+ onEditMCPServer?: (agentId: string, server: string) => void;
+ onDeleteMCPServer?: (agentId: string, server: string) => void;
}
export interface ExtendedAgentMCPServer extends AgentMCPServer {
- position?: Position;
+ isEditable?: boolean;
}
export interface ExtendedAgent extends Agent {
diff --git a/src/components/Agentic/IncidentDetails/IncidentMetaData/index.tsx b/src/components/Agentic/IncidentDetails/IncidentMetaData/index.tsx
index f76bff834..cc0ebeeb0 100644
--- a/src/components/Agentic/IncidentDetails/IncidentMetaData/index.tsx
+++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/index.tsx
@@ -1,7 +1,9 @@
import { format } from "date-fns";
import { useMemo } from "react";
import { useParams } from "react-router";
+import { useAgenticDispatch } from "../../../../containers/Agentic/hooks";
import { useGetIncidentQuery } from "../../../../redux/services/digma";
+import { setIncidentToClose } from "../../../../redux/slices/incidentsSlice";
import { Tooltip } from "../../../common/v3/Tooltip";
import { Divider } from "./Divider";
import * as s from "./styles";
@@ -13,6 +15,7 @@ const REFRESH_INTERVAL = 10 * 1000; // in milliseconds
export const IncidentMetaData = () => {
const params = useParams();
const incidentId = params.id;
+ const dispatch = useAgenticDispatch();
const { data } = useGetIncidentQuery(
{ id: incidentId ?? "" },
@@ -32,52 +35,83 @@ export const IncidentMetaData = () => {
[data]
);
+ const handleCloseButtonClick = () => {
+ if (!incidentId) {
+ return;
+ }
+
+ dispatch(setIncidentToClose(incidentId));
+ };
+
if (!data) {
return ;
}
return (
-
- Incident start time:
-
- {format(data.created_at, DATE_FORMAT)}
-
-
- {data.closed_at && (
- <>
-
-
-
-
- Incident close time:
-
- {format(data.closed_at, DATE_FORMAT)}
+
+ {data.status_timestamps.active && (
+
+ Incident start time:
+
+
+ {format(data.status_timestamps.active, DATE_FORMAT)}
+
-
- >
- )}
- {data.affected_services.length > 0 && (
- <>
-
-
-
-
- Affected services:
- {serviceTagsToShow.map((x) => (
-
- {x}
-
- ))}
- {hiddenServices.length > 0 && (
-
-
- +{hiddenServices.length}
-
+
+ )}
+ {data.status_timestamps.closed && (
+ <>
+
+
+
+
+ Incident close time:
+
+
+ {format(data.status_timestamps.closed, DATE_FORMAT)}
+
- )}
-
- >
+
+ >
+ )}
+ {data.affected_services.length > 0 && (
+ <>
+
+
+
+
+ Affected services:
+ {serviceTagsToShow.map((x) => (
+
+ {x}
+
+ ))}
+ {hiddenServices.length > 0 && (
+
+
+ +{hiddenServices.length}
+
+
+ )}
+
+ >
+ )}
+
+
+
+
+ Status:
+ {data.status}
+
+
+ {data.status === "pending" && (
+
)}
);
diff --git a/src/components/Agentic/IncidentDetails/IncidentMetaData/styles.ts b/src/components/Agentic/IncidentDetails/IncidentMetaData/styles.ts
index c00192f1a..40a4ef50a 100644
--- a/src/components/Agentic/IncidentDetails/IncidentMetaData/styles.ts
+++ b/src/components/Agentic/IncidentDetails/IncidentMetaData/styles.ts
@@ -1,12 +1,19 @@
import styled from "styled-components";
import { subheading1RegularTypography } from "../../../common/App/typographies";
+import { NewButton } from "../../../common/v3/NewButton";
export const Container = styled.div`
display: flex;
- align-items: center;
padding-top: 24px;
- height: 51px;
+ min-height: 51px;
flex-shrink: 0;
+ align-items: start;
+`;
+
+export const AttributesList = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
`;
export const DividerContainer = styled.div`
@@ -15,7 +22,14 @@ export const DividerContainer = styled.div`
display: flex;
`;
-export const DateAttribute = styled.div`
+export const CloseIncidentButton = styled(NewButton)`
+ flex-shrink: 0;
+ margin-top: 12px;
+ margin-left: auto;
+ margin-right: 16px;
+`;
+
+export const Attribute = styled.div`
${subheading1RegularTypography}
display: flex;
gap: 12px;
@@ -23,14 +37,18 @@ export const DateAttribute = styled.div`
padding: 16px;
`;
-export const DateLabel = styled.span`
+export const AttributeLabel = styled.span`
color: ${({ theme }) => theme.colors.v3.text.tertiary};
`;
-export const DateValue = styled.span`
+export const AttributeValue = styled.span`
color: ${({ theme }) => theme.colors.v3.text.primary};
`;
+export const StatusAttributeValue = styled(AttributeValue)`
+ text-transform: capitalize;
+`;
+
export const ServicesContainer = styled.div`
${subheading1RegularTypography}
display: flex;
diff --git a/src/components/Agentic/IncidentDetails/index.tsx b/src/components/Agentic/IncidentDetails/index.tsx
index 4d8957a4d..75d738ed8 100644
--- a/src/components/Agentic/IncidentDetails/index.tsx
+++ b/src/components/Agentic/IncidentDetails/index.tsx
@@ -91,7 +91,7 @@ export const IncidentDetails = () => {
return null;
}
- const incidentStatus = incidentData?.status;
+ const incidentStatus = incidentData?.status_description;
const agentName = agentsData?.agents.find(
(agent) => agent.name === agentId
diff --git a/src/components/Agentic/IncidentDetails/mockData.ts b/src/components/Agentic/IncidentDetails/mockData.ts
index f8f0f24e5..66de72034 100644
--- a/src/components/Agentic/IncidentDetails/mockData.ts
+++ b/src/components/Agentic/IncidentDetails/mockData.ts
@@ -1,16 +1,18 @@
-import type { GetIncidentResponse } from "../../../redux/services/types";
+import { type GetIncidentResponse } from "../../../redux/services/types";
import { mockedArtifacts } from "./AdditionalInfo/Artifacts/mockData";
import { mockedIncidentIssues } from "./AdditionalInfo/RelatedIssues/mockData";
export const mockedIncident: GetIncidentResponse = {
id: "incident-123",
name: "Sample Incident",
- active_status: "active",
+ status_description: "active",
status: "active",
- created_at: "2023-10-01T12:00:00Z",
- closed_at: "2023-10-01T12:30:00Z",
affected_services: ["service-1", "service-2", "service-3", "service-4"],
summary: "This is a summary of the incident.",
related_issues: mockedIncidentIssues,
- related_artifacts: mockedArtifacts
+ related_artifacts: mockedArtifacts,
+ status_timestamps: {
+ active: "2023-10-01T12:00:00Z",
+ closed: "2023-10-01T12:30:00Z"
+ }
};
diff --git a/src/components/Agentic/IncidentDetails/styles.ts b/src/components/Agentic/IncidentDetails/styles.ts
index 7ecb8e2b0..100a88115 100644
--- a/src/components/Agentic/IncidentDetails/styles.ts
+++ b/src/components/Agentic/IncidentDetails/styles.ts
@@ -69,10 +69,12 @@ export const SummaryContainer = styled.div`
padding: 24px;
flex-direction: column;
flex-grow: 1;
+ flex-shrink: 1;
+ min-width: 0;
+ overflow: hidden;
gap: 24px;
border-radius: 16px;
background: ${({ theme }) => theme.colors.v3.surface.primary};
- width: 60%;
`;
export const SummaryContainerToolbar = styled.div`
@@ -161,7 +163,8 @@ export const AdditionalInfoContainer = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
- width: 40%;
+ width: 30%;
+ min-width: 401px;
border-radius: 16px;
background: ${({ theme }) => theme.colors.v3.surface.primary};
flex-shrink: 0;
diff --git a/src/components/Agentic/IncidentDirectives/index.tsx b/src/components/Agentic/IncidentDirectives/index.tsx
new file mode 100644
index 000000000..1e4d92417
--- /dev/null
+++ b/src/components/Agentic/IncidentDirectives/index.tsx
@@ -0,0 +1,337 @@
+import {
+ createColumnHelper,
+ flexRender,
+ getCoreRowModel,
+ getSortedRowModel,
+ useReactTable
+} from "@tanstack/react-table";
+import { useMemo, useState } from "react";
+import {
+ useDeleteIncidentAgentDirectiveMutation,
+ useGetIncidentAgentDirectivesQuery
+} from "../../../redux/services/digma";
+import { CancelConfirmation } from "../../common/CancelConfirmation";
+import { SortIcon } from "../../common/icons/16px/SortIcon";
+import { TrashBinIcon } from "../../common/icons/16px/TrashBinIcon";
+import { Direction } from "../../common/icons/types";
+import { KebabMenu } from "../../common/KebabMenu";
+import { Checkmark } from "../../common/v3/Checkmark";
+import type { MenuItem } from "../../Navigation/common/MenuList/types";
+import * as s from "./styles";
+import type { ColumnMeta, ExtendedDirective } from "./types";
+
+const REFRESH_INTERVAL = 10 * 1000; // in milliseconds
+
+const columnHelper = createColumnHelper();
+
+export const IncidentDirectives = () => {
+ const [searchInputValue, setSearchInputValue] = useState("");
+ const [selectedConditions, setSelectedConditions] = useState([]);
+ const [directiveToDelete, setDirectiveToDelete] = useState();
+
+ const { data } = useGetIncidentAgentDirectivesQuery(
+ {
+ search_term: searchInputValue || undefined
+ },
+ {
+ pollingInterval: REFRESH_INTERVAL
+ }
+ );
+
+ const [deleteIncidentAgentDirective] =
+ useDeleteIncidentAgentDirectiveMutation();
+
+ const handleSearchInputChange = (value: string) => {
+ setSearchInputValue(value);
+ };
+
+ const handleCheckboxChange = (id: string) => (value: boolean) => {
+ setSelectedConditions((prev) =>
+ value ? [...prev, id] : prev.filter((x) => x !== id)
+ );
+ };
+
+ const handleDeleteDirectiveDialogConfirm = () => {
+ if (directiveToDelete) {
+ void deleteIncidentAgentDirective({
+ id: directiveToDelete
+ });
+ }
+ setDirectiveToDelete(undefined);
+ };
+
+ const handleDeleteDirectiveDialogClose = () => {
+ setDirectiveToDelete(undefined);
+ };
+
+ const handleMessageSend = () => {
+ // TODO: implement
+ };
+
+ const handleSelectedConditionTagClick = (id: string) => () => {
+ setSelectedConditions((prev) => prev.filter((x) => x !== id));
+ };
+
+ const items = useMemo(
+ () =>
+ data?.directives?.map((item, index) => ({
+ ...item,
+ number: index + 1,
+ isSelected: selectedConditions.includes(item.id)
+ })) ?? [],
+ [selectedConditions, data]
+ );
+
+ const columns = [
+ columnHelper.accessor((x) => x, {
+ id: "selector",
+ header: "",
+ meta: {
+ width: "5%",
+ minWidth: 60,
+ textAlign: "center"
+ },
+ cell: (info) => {
+ const value = info.getValue();
+
+ return (
+
+ );
+ }
+ }),
+ columnHelper.accessor("number", {
+ header: "#",
+ meta: {
+ width: "5%",
+ minWidth: 60,
+ textAlign: "center"
+ },
+ cell: (info) => {
+ const value = info.getValue();
+ return {value};
+ },
+ enableSorting: true,
+ sortingFn: (rowA, rowB) => {
+ return rowA.original.number - rowB.original.number;
+ }
+ }),
+ columnHelper.accessor("condition", {
+ header: "Condition",
+ meta: {
+ width: "35%",
+ minWidth: 100
+ },
+ cell: (info) => {
+ const value = info.getValue();
+ return {value};
+ },
+ enableSorting: true,
+ sortingFn: (rowA, rowB) => {
+ const a = rowA.original.condition.toLowerCase();
+ const b = rowB.original.condition.toLowerCase();
+ return a.localeCompare(b);
+ }
+ }),
+ columnHelper.accessor("directive", {
+ header: "Directive",
+ meta: {
+ width: "35%",
+ minWidth: 100
+ },
+ cell: (info) => {
+ const value = info.getValue();
+ return {value};
+ },
+ enableSorting: true,
+ sortingFn: (rowA, rowB) => {
+ const a = rowA.original.directive.toLowerCase();
+ const b = rowB.original.directive.toLowerCase();
+ return a.localeCompare(b);
+ }
+ }),
+ columnHelper.accessor("agents", {
+ header: "Agents",
+ meta: {
+ width: "15%",
+ minWidth: 60
+ },
+ cell: (info) => {
+ const value = info.getValue();
+ return {value.join(", ")};
+ },
+ enableSorting: true,
+ sortingFn: (rowA, rowB) => {
+ const a = rowA.original.agents.join(", ").toLowerCase();
+ const b = rowB.original.agents.join(", ").toLowerCase();
+ return a.localeCompare(b);
+ }
+ }),
+ columnHelper.accessor((x) => x, {
+ header: "Actions",
+ meta: {
+ width: "5%",
+ minWidth: 100,
+ textAlign: "center"
+ },
+ cell: (info) => {
+ const value = info.getValue();
+
+ const handleDeleteMenuItemClick = () => {
+ setDirectiveToDelete(value.id);
+ };
+
+ const items: MenuItem[] = [
+ {
+ id: "delete",
+ icon: ,
+ label: "Delete",
+ onClick: handleDeleteMenuItemClick
+ }
+ ];
+
+ return ;
+ }
+ })
+ ];
+
+ const table = useReactTable({
+ data: items,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ enableSortingRemoval: false
+ });
+
+ return (
+
+
+ Directives
+
+
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ const meta = header.column.columnDef.meta as ColumnMeta;
+
+ return (
+
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+ {header.column.columnDef.enableSorting &&
+ {
+ asc: (
+
+
+
+ ),
+ desc: (
+
+
+
+ )
+ }[header.column.getIsSorted() as string]}
+
+
+ );
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => {
+ const meta = cell.column.columnDef.meta as ColumnMeta;
+
+ return (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+
+ );
+ })}
+
+ ))}
+
+
+
+ 0 && (
+
+ {selectedConditions.map((x) => (
+
+ #{x}
+
+ ))}
+
+ )
+ }
+ />
+ {directiveToDelete && (
+
+
+
+ )}
+
+ );
+};
diff --git a/src/components/Agentic/IncidentDirectives/styles.ts b/src/components/Agentic/IncidentDirectives/styles.ts
new file mode 100644
index 000000000..e3fd68406
--- /dev/null
+++ b/src/components/Agentic/IncidentDirectives/styles.ts
@@ -0,0 +1,179 @@
+import styled from "styled-components";
+import {
+ bodyRegularTypography,
+ heading2BoldTypography,
+ subheading1BoldTypography,
+ subheading1RegularTypography,
+ subscriptRegularTypography
+} from "../../common/App/typographies";
+import { Overlay } from "../../common/Overlay";
+import { AgentChat } from "../common/AgentChat";
+import { Form, TextArea } from "../common/PromptInput/styles";
+import { SearchInput } from "../common/SearchInput";
+import type { TableCellContentProps } from "./types";
+
+export const Container = styled.div`
+ padding: 24px;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ gap: 24px;
+`;
+
+export const Header = styled.header`
+ ${heading2BoldTypography}
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 32px 24px 24px;
+ gap: 24px;
+ color: ${({ theme }) => theme.colors.v3.text.primary};
+`;
+
+export const StyledSearchInput = styled(SearchInput)`
+ width: 251px;
+ flex-grow: 0;
+`;
+
+export const TableContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ overflow-y: auto;
+`;
+
+export const Table = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+`;
+
+export const TableHead = styled.div`
+ ${subheading1BoldTypography}
+ display: flex;
+ color: ${({ theme }) => theme.colors.v3.text.tertiary};
+ height: 70px;
+`;
+
+export const TableHeadRow = styled.div`
+ display: flex;
+ align-items: center;
+ width: 100%;
+`;
+
+export const TableHeaderCell = styled.div`
+ box-sizing: border-box;
+ padding: 0 16px;
+`;
+
+export const TableHeaderCellContent = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: ${({ $align }) => $align ?? "left"};
+ gap: 4px;
+ ${({ onClick }) => (onClick ? "cursor: pointer;" : "")}
+`;
+
+export const SortingOrderIconContainer = styled.div`
+ display: flex;
+`;
+
+export const TableBody = styled.div`
+ ${bodyRegularTypography}
+ color: ${({ theme }) => theme.colors.v3.text.primary};
+ display: flex;
+ flex-direction: column;
+`;
+
+export const TableBodyRow = styled.div`
+ display: flex;
+ height: 70px;
+ box-sizing: border-box;
+ border-top: 1px solid
+ ${({ theme }) => theme.colors.v3.surface.sidePanelHeader};
+
+ &:nth-child(even) {
+ background: ${({ theme }) => theme.colors.v3.surface.primary};
+ }
+`;
+
+export const TableBodyCell = styled.div`
+ display: flex;
+ align-items: center;
+ overflow: hidden;
+ box-sizing: border-box;
+ padding: 0 16px;
+ border-left: 1px solid
+ ${({ theme }) => theme.colors.v3.surface.sidePanelHeader};
+ border-right: 1px solid
+ ${({ theme }) => theme.colors.v3.surface.sidePanelHeader};
+
+ &:first-child {
+ border-left: none;
+ }
+
+ &:last-child {
+ border-right: none;
+ }
+`;
+
+export const RecordNumber = styled.span`
+ ${subscriptRegularTypography}
+ color: ${({ theme }) => theme.colors.v3.text.tertiary};
+`;
+
+export const Condition = styled.span`
+ ${subheading1BoldTypography}
+ color: ${({ theme }) => theme.colors.v3.text.primary};
+`;
+
+export const Directive = styled.span`
+ ${subheading1RegularTypography}
+ color: ${({ theme }) => theme.colors.v3.text.secondary};
+`;
+
+export const SelectedConditionsContainer = styled.div`
+ display: flex;
+ gap: 6px;
+ flex-wrap: wrap;
+`;
+
+export const SelectedConditionTag = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 6px 8px;
+ background: ${({ theme }) => theme.colors.v3.surface.primary};
+ border: 1px solid ${({ theme }) => theme.colors.v3.stroke.dark};
+ border-radius: 5px;
+ color: ${({ theme }) => theme.colors.v3.text.primary};
+ font-size: 16px;
+ line-height: 20px;
+ box-shadow: 0 3px 5px 0 rgb(0 0 0 / 13%);
+ cursor: pointer;
+`;
+
+export const StyledAgentChat = styled(AgentChat)`
+ ${/* TODO: change to color from the theme */ ""}
+ background: #000;
+ border-radius: 8px;
+ padding: 24px;
+ gap: 12px;
+ max-height: 306px;
+
+ & ${Form} {
+ height: 117px;
+ ${/* TODO: change to color from the theme */ ""}
+ border: 1px solid #6063f6;
+
+ & ${TextArea} {
+ ${subheading1RegularTypography}
+ color: ${({ theme }) => theme.colors.v3.text.primary};
+ height: 100%;
+ }
+ }
+`;
+
+export const StyledOverlay = styled(Overlay)`
+ align-items: center;
+`;
diff --git a/src/components/Agentic/IncidentDirectives/types.ts b/src/components/Agentic/IncidentDirectives/types.ts
new file mode 100644
index 000000000..0e56ac949
--- /dev/null
+++ b/src/components/Agentic/IncidentDirectives/types.ts
@@ -0,0 +1,18 @@
+import type { Directive } from "../../../redux/services/types";
+
+export interface ExtendedDirective extends Directive {
+ number: number;
+ isSelected: boolean;
+}
+
+export type ContentAlignment = "left" | "center" | "right";
+
+export interface ColumnMeta {
+ width: string | number;
+ minWidth?: string | number;
+ textAlign?: ContentAlignment;
+}
+
+export interface TableCellContentProps {
+ $align?: ContentAlignment;
+}
diff --git a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ToolsStep/index.tsx b/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ToolsStep/index.tsx
deleted file mode 100644
index bba3e7d49..000000000
--- a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ToolsStep/index.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import { useState, type ChangeEvent, type MouseEvent } from "react";
-import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent";
-import { CrossIcon } from "../../../../common/icons/12px/CrossIcon";
-import { MagnifierIcon } from "../../../../common/icons/MagnifierIcon";
-import { NewButton } from "../../../../common/v3/NewButton";
-import { trackingEvents } from "../../../tracking";
-import * as s from "./styles";
-import type { ToolsStepProps } from "./types";
-
-const initialTools = [
- "create_issue",
- "list_issues",
- "update_issue",
- "list_commits",
- "get_me",
- "search_users",
- "list_secret_scanning_alerts",
- "push_files",
- "get_file_contents",
- "get_commit",
- "fork_repository"
-];
-
-export const ToolsStep = ({ onCancel, onSave }: ToolsStepProps) => {
- const [textAreaValue, setTextAreaValue] = useState("");
- const [tools, setTools] = useState(initialTools);
- const [selectedTools, setSelectedTools] = useState([]);
- const [searchInputValue, setSearchInputValue] = useState("");
-
- const handleTextAreaChange = (e: ChangeEvent) => {
- setTextAreaValue(e.target.value);
- };
-
- const handleSaveButtonClick = () => {
- sendUserActionTrackingEvent(
- trackingEvents.INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_SAVE_BUTTON_CLICKED
- );
- onSave(selectedTools, textAreaValue);
- };
-
- const handleCancelButtonClick = () => {
- sendUserActionTrackingEvent(
- trackingEvents.INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_CANCEL_BUTTON_CLICKED
- );
- onCancel();
- };
-
- const handleSearchInputChange = (e: ChangeEvent) => {
- setSearchInputValue(e.target.value);
- };
-
- const handleSelectAllToggleChange = (value: boolean) => {
- sendUserActionTrackingEvent(
- trackingEvents.INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_SELECT_ALL_TOGGLE_CHANGED,
- { value }
- );
- setSelectedTools(value ? [...tools] : []);
- };
-
- const handleToolTagClick = (tool: string) => () => {
- sendUserActionTrackingEvent(
- trackingEvents.INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_TOOL_TAG_CLICKED
- );
- setSelectedTools((prev) =>
- prev.includes(tool) ? prev.filter((x) => x !== tool) : [...prev, tool]
- );
- };
-
- const handleToolTagDeleteButtonClick =
- (tool: string) => (e: MouseEvent) => {
- sendUserActionTrackingEvent(
- trackingEvents.INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_TOOL_TAG_DELETE_BUTTON_CLICKED
- );
-
- e.stopPropagation();
-
- setTools((prev) => prev.filter((x) => x !== tool));
- };
-
- const filteredTools = tools.filter((tool) =>
- tool.toLowerCase().includes(searchInputValue.toLowerCase())
- );
- const areAllSelected = tools.every((x) => selectedTools.includes(x));
-
- const isSaveButtonEnabled = selectedTools.length > 0;
-
- return (
-
-
-
- Tools
-
-
-
-
-
-
-
-
- {filteredTools.length > 0 && (
-
- {filteredTools.map((x) => (
-
- {x}
-
-
-
-
- ))}
-
- )}
-
-
-
-
-
-
-
- );
-};
diff --git a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/index.tsx b/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/index.tsx
deleted file mode 100644
index 30bd5936f..000000000
--- a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/index.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { useState } from "react";
-import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent";
-import { Dialog } from "../../common/Dialog";
-import { trackingEvents } from "../../tracking";
-import { ServerStep } from "./ServerStep";
-import { ToolsStep } from "./ToolsStep";
-import type { AddMCPServerDialogProps } from "./types";
-
-export const AddMCPServerDialog = ({
- onClose,
- onComplete
-}: AddMCPServerDialogProps) => {
- const [currentStep, setCurrentStep] = useState(0);
- const [connectionSettings, setConnectionSettings] = useState("");
-
- const handleServerStepConnectionSettingsChange = (settings: string) => {
- setConnectionSettings(settings);
- };
-
- const handleServerStepConnect = (settings: string) => {
- setConnectionSettings(settings);
- setCurrentStep((prev) => prev + 1);
- };
-
- const handleToolsStepSave = (tools: string[], instructions: string) => {
- onComplete(connectionSettings, tools, instructions);
- };
-
- const handleToolsStepCancel = () => {
- setCurrentStep((prev) => prev - 1);
- };
-
- const steps = [
- ,
-
- ];
-
- const handleDialogClose = () => {
- sendUserActionTrackingEvent(
- trackingEvents.INCIDENT_TEMPLATE_ADD_MCP_DIALOG_CLOSED
- );
- onClose();
- };
-
- return (
-
- );
-};
diff --git a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/types.ts b/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/types.ts
deleted file mode 100644
index ff4e98891..000000000
--- a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/types.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export interface AddMCPServerDialogProps {
- onClose: () => void;
- onComplete: (text: string, tools: string[], instructions: string) => void;
-}
diff --git a/src/components/Agentic/IncidentTemplate/MCPServerDialog/Footer/index.tsx b/src/components/Agentic/IncidentTemplate/MCPServerDialog/Footer/index.tsx
new file mode 100644
index 000000000..af9133f5b
--- /dev/null
+++ b/src/components/Agentic/IncidentTemplate/MCPServerDialog/Footer/index.tsx
@@ -0,0 +1,21 @@
+import { Spinner } from "../../../../common/v3/Spinner";
+import * as s from "./styles";
+import type { FooterProps } from "./types";
+
+export const Footer = ({
+ isLoading,
+ loadingMessage,
+ errorMessage,
+ buttons
+}: FooterProps) => (
+
+ {isLoading && (
+
+
+ {loadingMessage}
+
+ )}
+ {errorMessage && {errorMessage}}
+ {buttons}
+
+);
diff --git a/src/components/Agentic/IncidentTemplate/MCPServerDialog/Footer/styles.ts b/src/components/Agentic/IncidentTemplate/MCPServerDialog/Footer/styles.ts
new file mode 100644
index 000000000..8bb3051ea
--- /dev/null
+++ b/src/components/Agentic/IncidentTemplate/MCPServerDialog/Footer/styles.ts
@@ -0,0 +1,24 @@
+import styled from "styled-components";
+
+export const Container = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+`;
+
+export const LoadingMessage = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 8px;
+`;
+
+export const ErrorMessage = styled.span`
+ color: ${({ theme }) => theme.colors.v3.status.high};
+`;
+
+export const ButtonsContainer = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-left: auto;
+`;
diff --git a/src/components/Agentic/IncidentTemplate/MCPServerDialog/Footer/types.ts b/src/components/Agentic/IncidentTemplate/MCPServerDialog/Footer/types.ts
new file mode 100644
index 000000000..b7f88e73d
--- /dev/null
+++ b/src/components/Agentic/IncidentTemplate/MCPServerDialog/Footer/types.ts
@@ -0,0 +1,8 @@
+import type { ReactNode } from "react";
+
+export interface FooterProps {
+ isLoading?: boolean;
+ loadingMessage?: string;
+ errorMessage?: string;
+ buttons: ReactNode;
+}
diff --git a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ServerStep/index.tsx b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ServerStep/index.tsx
similarity index 64%
rename from src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ServerStep/index.tsx
rename to src/components/Agentic/IncidentTemplate/MCPServerDialog/ServerStep/index.tsx
index d2e309f29..4f62e5777 100644
--- a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ServerStep/index.tsx
+++ b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ServerStep/index.tsx
@@ -2,13 +2,16 @@ import { type ChangeEvent } from "react";
import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent";
import { NewButton } from "../../../../common/v3/NewButton";
import { trackingEvents } from "../../../tracking";
+import { Footer } from "../Footer";
import * as s from "./styles";
import type { ServerStepProps } from "./types";
export const ServerStep = ({
onConnect,
connectionSettings,
- onConnectionSettingsChange
+ onConnectionSettingsChange,
+ isLoading,
+ error
}: ServerStepProps) => {
const handleTextAreaChange = (e: ChangeEvent) => {
onConnectionSettingsChange(e.target.value);
@@ -21,18 +24,24 @@ export const ServerStep = ({
onConnect(connectionSettings);
};
- const isConnectButtonEnabled = connectionSettings.trim().length > 0;
+ const isConnectButtonEnabled =
+ connectionSettings.trim().length > 0 && !isLoading;
return (
-
-
-
+
+ }
+ />
);
};
diff --git a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ServerStep/styles.ts b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ServerStep/styles.ts
similarity index 85%
rename from src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ServerStep/styles.ts
rename to src/components/Agentic/IncidentTemplate/MCPServerDialog/ServerStep/styles.ts
index 9b1f8b35a..e197c51f2 100644
--- a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ServerStep/styles.ts
+++ b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ServerStep/styles.ts
@@ -26,10 +26,3 @@ export const TextArea = styled.textarea`
font-weight: 500;
resize: none;
`;
-
-export const Footer = styled.div`
- display: flex;
- align-items: center;
- gap: 8px;
- justify-content: flex-end;
-`;
diff --git a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ServerStep/types.ts b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ServerStep/types.ts
similarity index 80%
rename from src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ServerStep/types.ts
rename to src/components/Agentic/IncidentTemplate/MCPServerDialog/ServerStep/types.ts
index b222c106b..143ee5f9b 100644
--- a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ServerStep/types.ts
+++ b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ServerStep/types.ts
@@ -2,4 +2,6 @@ export interface ServerStepProps {
onConnect: (settings: string) => void;
connectionSettings: string;
onConnectionSettingsChange: (settings: string) => void;
+ isLoading?: boolean;
+ error?: string;
}
diff --git a/src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/index.tsx b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/index.tsx
new file mode 100644
index 000000000..65b89928c
--- /dev/null
+++ b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/index.tsx
@@ -0,0 +1,130 @@
+import { useState, type ChangeEvent } from "react";
+import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent";
+import { NewButton } from "../../../../common/v3/NewButton";
+import { SearchInput } from "../../../common/SearchInput";
+import { trackingEvents } from "../../../tracking";
+import { Footer } from "../Footer";
+import * as s from "./styles";
+import type { ToolsStepProps } from "./types";
+
+export const ToolsStep = ({
+ onCancel,
+ onSave,
+ tools,
+ selectedTools: initialSelectedTools = [],
+ isLoading,
+ instructions = "",
+ error
+}: ToolsStepProps) => {
+ const [instructionsTextAreaValue, setInstructionsTextAreaValue] =
+ useState(instructions);
+ const [selectedTools, setSelectedTools] = useState(initialSelectedTools);
+ const [searchInputValue, setSearchInputValue] = useState("");
+
+ const handleInstructionsTextAreaChange = (
+ e: ChangeEvent
+ ) => {
+ setInstructionsTextAreaValue(e.target.value);
+ };
+
+ const handleSaveButtonClick = () => {
+ sendUserActionTrackingEvent(
+ trackingEvents.INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_SAVE_BUTTON_CLICKED
+ );
+ onSave(selectedTools, instructionsTextAreaValue);
+ };
+
+ const handleCancelButtonClick = () => {
+ sendUserActionTrackingEvent(
+ trackingEvents.INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_CANCEL_BUTTON_CLICKED
+ );
+ onCancel();
+ };
+
+ const handleSearchInputChange = (value: string) => {
+ setSearchInputValue(value);
+ };
+
+ const handleSelectAllToggleChange = (value: boolean) => {
+ sendUserActionTrackingEvent(
+ trackingEvents.INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_SELECT_ALL_TOGGLE_CHANGED,
+ { value }
+ );
+ setSelectedTools(value ? [...tools] : []);
+ };
+
+ const handleToolTagClick = (tool: string) => () => {
+ sendUserActionTrackingEvent(
+ trackingEvents.INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_TOOL_TAG_CLICKED
+ );
+ setSelectedTools((prev) =>
+ prev.includes(tool) ? prev.filter((x) => x !== tool) : [...prev, tool]
+ );
+ };
+
+ const filteredTools = tools.filter((tool) =>
+ tool.toLowerCase().includes(searchInputValue.toLowerCase())
+ );
+ const areAllSelected = tools.every((x) => selectedTools.includes(x));
+
+ const isSaveButtonEnabled = selectedTools.length > 0 && !isLoading;
+
+ return (
+
+
+
+ Tools
+
+
+
+ {filteredTools.length > 0 && (
+
+ {filteredTools.map((x) => (
+
+ {x}
+
+ ))}
+
+ )}
+
+
+
+ );
+};
diff --git a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ToolsStep/styles.ts b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/styles.ts
similarity index 87%
rename from src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ToolsStep/styles.ts
rename to src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/styles.ts
index 57cf399dd..b9c74da80 100644
--- a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ToolsStep/styles.ts
+++ b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/styles.ts
@@ -2,7 +2,7 @@ import styled from "styled-components";
import { getCodeFont } from "../../../../common/App/styles";
import { subscriptRegularTypography } from "../../../../common/App/typographies";
import { ToggleSwitch } from "../../../../common/v3/ToggleSwitch";
-import type { ToolTagDeleteButtonProps, ToolTagProps } from "./types";
+import type { ToolTagProps } from "./types";
export const Container = styled.div`
display: flex;
@@ -91,21 +91,7 @@ export const ToolTag = styled.div`
user-select: none;
`;
-export const ToolTagDeleteButton = styled.button`
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 0;
- border: none;
- background: none;
- cursor: pointer;
- color: ${({ theme, $isHighlighted }) =>
- $isHighlighted
- ? theme.colors.v3.icon.primary
- : theme.colors.v3.stroke.primary};
-`;
-
-export const TextArea = styled.textarea`
+export const InstructionsTextArea = styled.textarea`
${({ theme }) => getCodeFont(theme.codeFont)}
display: flex;
flex-grow: 1;
@@ -128,8 +114,14 @@ export const TextArea = styled.textarea`
`;
export const Footer = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+`;
+
+export const ButtonsContainer = styled.div`
display: flex;
align-items: center;
gap: 8px;
- justify-content: flex-end;
+ margin-left: auto;
`;
diff --git a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ToolsStep/types.ts b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/types.ts
similarity index 69%
rename from src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ToolsStep/types.ts
rename to src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/types.ts
index ace8e1ff6..0b5c9083c 100644
--- a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/ToolsStep/types.ts
+++ b/src/components/Agentic/IncidentTemplate/MCPServerDialog/ToolsStep/types.ts
@@ -1,6 +1,11 @@
export interface ToolsStepProps {
onCancel: () => void;
onSave: (tools: string[], instructions: string) => void;
+ tools: string[];
+ selectedTools?: string[];
+ instructions?: string;
+ isLoading?: boolean;
+ error?: string;
}
export interface ToolTagProps {
diff --git a/src/components/Agentic/IncidentTemplate/MCPServerDialog/index.tsx b/src/components/Agentic/IncidentTemplate/MCPServerDialog/index.tsx
new file mode 100644
index 000000000..c3028abad
--- /dev/null
+++ b/src/components/Agentic/IncidentTemplate/MCPServerDialog/index.tsx
@@ -0,0 +1,163 @@
+import type { FetchBaseQueryError } from "@reduxjs/toolkit/query";
+import { useMemo, useState } from "react";
+import {
+ useAddIncidentAgentMCPServerMutation,
+ useTestIncidentAgentMCPServerMutation,
+ useUpdateIncidentAgentMCPServerMutation
+} from "../../../../redux/services/digma";
+import type { AddMCPServerPayload } from "../../../../redux/services/types";
+import { isObject } from "../../../../typeGuards/isObject";
+import { isString } from "../../../../typeGuards/isString";
+import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent";
+import { Dialog } from "../../common/Dialog";
+import { trackingEvents } from "../../tracking";
+import { ServerStep } from "./ServerStep";
+import { ToolsStep } from "./ToolsStep";
+import type { MCPServerDialogProps } from "./types";
+
+export const MCPServerDialog = ({
+ agentId,
+ serverData,
+ onClose,
+ onComplete
+}: MCPServerDialogProps) => {
+ const [currentStep, setCurrentStep] = useState(0);
+ const [connectionSettings, setConnectionSettings] = useState(
+ serverData?.config ?? ""
+ );
+ const [testServerError, setTestServerError] = useState();
+ const [addServerError, setAddServerError] = useState();
+
+ const [testMCPServer, testMCPServerResult] =
+ useTestIncidentAgentMCPServerMutation();
+ const [addMCPServer, addMCPServerResult] =
+ useAddIncidentAgentMCPServerMutation();
+ const [updateMCPServer, updateMCPServerResult] =
+ useUpdateIncidentAgentMCPServerMutation();
+
+ const handleServerStepConnectionSettingsChange = (settings: string) => {
+ setConnectionSettings(settings);
+ };
+
+ const handleServerStepConnect = (settings: string) => {
+ setTestServerError(undefined);
+
+ void testMCPServer({
+ config_json: settings
+ })
+ .unwrap()
+ .then(() => {
+ setCurrentStep((prev) => prev + 1);
+ })
+ .catch((error: FetchBaseQueryError) => {
+ setTestServerError(`Failed to test MCP server: ${String(error.data)}`);
+ });
+ };
+
+ const handleToolsStepSave = (tools: string[], instructions: string) => {
+ setAddServerError(undefined);
+
+ const payload: AddMCPServerPayload = {
+ agent: agentId,
+ config_json: connectionSettings,
+ selected_tools: tools,
+ instructions_prompt: instructions
+ };
+
+ if (!serverData?.uid) {
+ // Add new MCP server
+ void addMCPServer(payload)
+ .unwrap()
+ .then(() => {
+ onComplete();
+ })
+ .catch((error: FetchBaseQueryError) => {
+ const errorPrefix = "Failed to add MCP server";
+
+ let errorDetails = "";
+ if (isString(error.data)) {
+ errorDetails = error.data;
+ }
+
+ if (isObject(error.data) && isString(error.data.detail)) {
+ errorDetails = error.data.detail;
+ }
+
+ setAddServerError(
+ [errorPrefix, errorDetails].filter(Boolean).join(": ")
+ );
+ });
+ } else {
+ // Update existing MCP server
+ void updateMCPServer({
+ id: serverData.uid,
+ data: payload
+ })
+ .unwrap()
+ .then(() => {
+ onComplete();
+ })
+ .catch((error: FetchBaseQueryError) => {
+ const errorPrefix = "Failed to add MCP server";
+
+ let errorDetails = "";
+ if (isString(error.data)) {
+ errorDetails = error.data;
+ }
+
+ if (isObject(error.data) && isString(error.data.detail)) {
+ errorDetails = error.data.detail;
+ }
+
+ setAddServerError(
+ [errorPrefix, errorDetails].filter(Boolean).join(": ")
+ );
+ });
+ }
+ };
+
+ const handleToolsStepCancel = () => {
+ setCurrentStep((prev) => prev - 1);
+ };
+
+ const tools = useMemo(
+ () => testMCPServerResult.data?.tools ?? [],
+ [testMCPServerResult.data]
+ );
+
+ const steps = [
+ ,
+
+ ];
+
+ const handleDialogClose = () => {
+ sendUserActionTrackingEvent(
+ trackingEvents.INCIDENT_TEMPLATE_ADD_MCP_DIALOG_CLOSED
+ );
+ onClose();
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Agentic/IncidentTemplate/AddMCPServerDialog/styles.ts b/src/components/Agentic/IncidentTemplate/MCPServerDialog/styles.ts
similarity index 100%
rename from src/components/Agentic/IncidentTemplate/AddMCPServerDialog/styles.ts
rename to src/components/Agentic/IncidentTemplate/MCPServerDialog/styles.ts
diff --git a/src/components/Agentic/IncidentTemplate/MCPServerDialog/types.ts b/src/components/Agentic/IncidentTemplate/MCPServerDialog/types.ts
new file mode 100644
index 000000000..242f70e09
--- /dev/null
+++ b/src/components/Agentic/IncidentTemplate/MCPServerDialog/types.ts
@@ -0,0 +1,8 @@
+import type { MCPServerData } from "../../../../redux/services/types";
+
+export interface MCPServerDialogProps {
+ agentId: string;
+ serverData?: MCPServerData;
+ onClose: () => void;
+ onComplete: () => void;
+}
diff --git a/src/components/Agentic/IncidentTemplate/index.tsx b/src/components/Agentic/IncidentTemplate/index.tsx
index 21e8e3b43..fd4a061a7 100644
--- a/src/components/Agentic/IncidentTemplate/index.tsx
+++ b/src/components/Agentic/IncidentTemplate/index.tsx
@@ -1,8 +1,12 @@
-import { Position } from "@xyflow/react";
-import { useState } from "react";
+import { useEffect, useState } from "react";
+import {
+ useDeleteIncidentAgentMCPServerMutation,
+ useGetIncidentAgentMCPServersQuery
+} from "../../../redux/services/digma";
+import { CancelConfirmation } from "../../common/CancelConfirmation";
import { Overlay } from "../../common/Overlay";
import type { ExtendedAgent } from "../IncidentDetails/AgentFlowChart/types";
-import { AddMCPServerDialog } from "./AddMCPServerDialog";
+import { MCPServerDialog } from "./MCPServerDialog";
import * as s from "./styles";
const initialAgents: ExtendedAgent[] = [
@@ -10,135 +14,61 @@ const initialAgents: ExtendedAgent[] = [
name: "digma",
display_name: "Digma",
description: "Digma",
- running: false,
- status: "active",
+ status: "waiting",
mcp_servers: []
},
{
name: "watchman",
display_name: "Watchman",
description: "Watchman",
- running: false,
- status: "active",
- mcp_servers: [
- {
- name: "postgres",
- display_name: "Postgres",
- active: true,
- position: Position.Bottom
- },
- {
- name: "digma",
- display_name: "Digma",
- active: true,
- position: Position.Bottom
- },
- {
- name: "github",
- display_name: "GitHub",
- active: true,
- position: Position.Bottom
- }
- ]
+ status: "waiting",
+ mcp_servers: []
},
{
name: "triager",
display_name: "Triager",
description: "Triager",
- running: false,
- status: "active",
- mcp_servers: [
- {
- name: "postgres",
- display_name: "Postgres",
- active: true,
- position: Position.Bottom
- },
- {
- name: "digma",
- display_name: "Digma",
- active: true,
- position: Position.Bottom
- },
- {
- name: "github",
- display_name: "GitHub",
- active: true,
- position: Position.Bottom
- }
- ]
+ status: "waiting",
+ mcp_servers: []
},
{
name: "code_resolver",
display_name: "Code Resolver",
description: "Code Resolver",
- running: false,
- status: "active",
- mcp_servers: [
- {
- name: "postgres",
- display_name: "Postgres",
- active: true,
- position: Position.Bottom
- },
- {
- name: "digma",
- display_name: "Digma",
- active: true,
- position: Position.Bottom
- },
- {
- name: "github",
- display_name: "GitHub",
- active: true,
- position: Position.Bottom
- }
- ]
+ status: "waiting",
+ mcp_servers: []
},
{
name: "infra_resolver",
display_name: "Infrastructure Resolver",
description: "Infrastructure Resolver",
- running: false,
- status: "active",
- mcp_servers: [
- {
- name: "postgres",
- display_name: "Postgres",
- active: true,
- position: Position.Top
- },
- {
- name: "digma",
- display_name: "Digma",
- active: true,
- position: Position.Top
- },
- {
- name: "github",
- display_name: "GitHub",
- active: true,
- position: Position.Top
- }
- ]
+ status: "waiting",
+ mcp_servers: []
},
{
name: "validator",
display_name: "Validator",
description: "Validator",
- running: false,
- status: "active",
+ status: "waiting",
mcp_servers: []
}
];
+const REFRESH_INTERVAL = 10 * 1000; // in milliseconds
+
export const IncidentTemplate = () => {
const [agentId, setAgentId] = useState("watchman");
const [agents, setAgents] = useState(initialAgents);
- const [agentToUpdate, setAgentToUpdate] = useState();
- const [isAddMCPServerDialogOpen, setIsAddMCPServerDialogOpen] =
- useState(false);
+ const [agentIdToUpdate, setAgentIdToUpdate] = useState();
+ const [mcpServerIdToUpdate, setMCPServerIdToUpdate] = useState();
+ const [mcpServerIdToDelete, setMCPServerIdToDelete] = useState();
+
+ const { data: mcpServers } = useGetIncidentAgentMCPServersQuery(undefined, {
+ pollingInterval: REFRESH_INTERVAL
+ });
+
+ const [deleteMCPServer] = useDeleteIncidentAgentMCPServerMutation();
const handleInputChange = () => {
return undefined;
@@ -148,56 +78,78 @@ export const IncidentTemplate = () => {
return undefined;
};
- const handleAddMCPServer = (agentId: string, position: Position) => {
- const agent = agents.find((agent) => agent.name === agentId);
+ const handleAddMCPServer = (agentId: string) => {
+ setAgentIdToUpdate(agentId);
+ };
+
+ const handleEditMCPServer = (agentId: string, serverName: string) => {
+ const serverId = mcpServers?.mcps.find((x) => x.name === serverName)?.uid;
- if (!agent) {
+ if (!serverId) {
return;
}
- setAgentToUpdate({
- ...agent,
- mcp_servers: [
- ...agent.mcp_servers,
- {
- name: "mcp",
- display_name: "MCP Server",
- active: true,
- position
- }
- ]
- });
-
- setIsAddMCPServerDialogOpen(true);
+ setAgentIdToUpdate(agentId);
+ setMCPServerIdToUpdate(serverId);
};
- const handleEditMCPServers = () => {
- setIsAddMCPServerDialogOpen(true);
- };
+ const handleDeleteMCPServer = (_: string, serverName: string) => {
+ const serverId = mcpServers?.mcps.find((x) => x.name === serverName)?.uid;
- const handleAddMCPServerDialogComplete = () => {
- if (!agentToUpdate) {
+ if (!serverId) {
return;
}
- const updatedAgents = agents.map((agent) => {
- if (agent.name === agentToUpdate.name) {
- return {
- ...agent,
- mcp_servers: agentToUpdate.mcp_servers
- };
- }
- return agent;
- });
-
- setAgents(updatedAgents);
- setIsAddMCPServerDialogOpen(false);
+ setMCPServerIdToDelete(serverId);
+ };
+
+ const handleDeleteMCPServerDialogConfirm = () => {
+ if (mcpServerIdToDelete) {
+ void deleteMCPServer({ id: mcpServerIdToDelete });
+ }
+ setMCPServerIdToDelete(undefined);
+ };
+
+ const handleDeleteMCPServerDialogClose = () => {
+ setMCPServerIdToDelete(undefined);
};
- const handleAddMCPServerDialogClose = () => {
- setIsAddMCPServerDialogOpen(false);
+ const handleMCPServerDialogComplete = () => {
+ setAgentIdToUpdate(undefined);
+ setMCPServerIdToUpdate(undefined);
};
+ const handleMCPServerDialogClose = () => {
+ setAgentIdToUpdate(undefined);
+ setMCPServerIdToUpdate(undefined);
+ };
+
+ const serverDataToUpdate = mcpServers?.mcps.find(
+ (x) => x.uid === mcpServerIdToUpdate
+ );
+
+ useEffect(() => {
+ if (mcpServers) {
+ setAgents((prev) =>
+ prev.map((agent) => {
+ const agentMCPServers = mcpServers.mcps.filter((x) =>
+ x.agents.includes(agent.name)
+ );
+
+ return {
+ ...agent,
+ mcp_servers: agentMCPServers.map((x) => ({
+ name: x.name,
+ display_name: x.name,
+ active: true,
+ isEditable: x.editable
+ }))
+ };
+ })
+ );
+ }
+ }, [mcpServers]);
+
return (
Template
@@ -216,7 +168,8 @@ export const IncidentTemplate = () => {
selectedAgentId={agentId}
isEditMode={true}
onAddMCPServer={handleAddMCPServer}
- onEditMCPServers={handleEditMCPServers}
+ onEditMCPServer={handleEditMCPServer}
+ onDeleteMCPServer={handleDeleteMCPServer}
/>
{
isDisabled={true}
placeholder={"Enter a custom prompt"}
/>
- {isAddMCPServerDialogOpen && (
+ {agentIdToUpdate && (
-
)}
+ {mcpServerIdToDelete && (
+
+
+
+ )}
);
};
diff --git a/src/components/Agentic/IncidentTemplate/styles.ts b/src/components/Agentic/IncidentTemplate/styles.ts
index 8deb2388f..fafa7ce44 100644
--- a/src/components/Agentic/IncidentTemplate/styles.ts
+++ b/src/components/Agentic/IncidentTemplate/styles.ts
@@ -1,7 +1,9 @@
import styled from "styled-components";
import { heading2BoldTypography } from "../../common/App/typographies";
+import { Overlay } from "../../common/Overlay";
import { AgentFlowChart } from "../IncidentDetails/AgentFlowChart";
import { PromptInput } from "../common/PromptInput";
+import { TextArea } from "../common/PromptInput/styles";
export const Container = styled.div`
padding: 24px;
@@ -15,9 +17,7 @@ export const Header = styled.header`
display: flex;
padding: 32px 24px 24px;
- align-items: flex-start;
- gap: var(--spacing-guides-715-rem-24-px, 24px);
- align-self: stretch;
+ gap: 24px;
color: ${({ theme }) => theme.colors.v3.text.primary};
`;
@@ -35,7 +35,7 @@ export const StyledAgentFlowChart = styled(AgentFlowChart)`
export const StyledIncidentPromptInput = styled(PromptInput)`
height: 105px;
- & > textarea {
+ & ${TextArea} {
height: 100%;
&::placeholder {
@@ -49,7 +49,7 @@ export const StyledAgentPromptInput = styled(PromptInput)`
${/* TODO: change to color from the theme */ ""}
border: 1px solid #6063f6;
- & > textarea {
+ & ${TextArea} {
height: 100%;
&::placeholder {
@@ -57,3 +57,7 @@ export const StyledAgentPromptInput = styled(PromptInput)`
}
}
`;
+
+export const StyledOverlay = styled(Overlay)`
+ align-items: center;
+`;
diff --git a/src/components/Agentic/IncidentsContainer/CloseIncidentDialogOverlay/index.tsx b/src/components/Agentic/IncidentsContainer/CloseIncidentDialogOverlay/index.tsx
new file mode 100644
index 000000000..cd150d131
--- /dev/null
+++ b/src/components/Agentic/IncidentsContainer/CloseIncidentDialogOverlay/index.tsx
@@ -0,0 +1,42 @@
+import {
+ useAgenticDispatch,
+ useAgenticSelector
+} from "../../../../containers/Agentic/hooks";
+import { useCloseIncidentMutation } from "../../../../redux/services/digma";
+import { setIncidentToClose } from "../../../../redux/slices/incidentsSlice";
+import { CancelConfirmation } from "../../../common/CancelConfirmation";
+import * as s from "./styles";
+
+export const CloseIncidentDialogOverlay = () => {
+ const incidentId = useAgenticSelector(
+ (state) => state.incidents.incidentToClose
+ );
+ const dispatch = useAgenticDispatch();
+
+ const [closeIncident] = useCloseIncidentMutation();
+
+ const handleCloseConfirmationDialogClose = () => {
+ dispatch(setIncidentToClose(null));
+ };
+
+ const handleCloseConfirmationDialogConfirm = () => {
+ if (incidentId) {
+ void closeIncident({ id: incidentId });
+ }
+
+ dispatch(setIncidentToClose(null));
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/src/components/Agentic/IncidentsContainer/CloseIncidentDialogOverlay/styles.ts b/src/components/Agentic/IncidentsContainer/CloseIncidentDialogOverlay/styles.ts
new file mode 100644
index 000000000..9df556038
--- /dev/null
+++ b/src/components/Agentic/IncidentsContainer/CloseIncidentDialogOverlay/styles.ts
@@ -0,0 +1,6 @@
+import styled from "styled-components";
+import { Overlay } from "../../../common/Overlay";
+
+export const StyledOverlay = styled(Overlay)`
+ align-items: center;
+`;
diff --git a/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx b/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx
index d6babed5e..3ef139eea 100644
--- a/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx
+++ b/src/components/Agentic/IncidentsContainer/CreateIncidentChatOverlay/index.tsx
@@ -132,6 +132,9 @@ export const CreateIncidentChatOverlay = () => {
// eslint-disable-next-line no-console
console.error("Unknown error starting incident creation chat");
}
+ },
+ onclose: () => {
+ abortControllerRef.current = null;
}
}
);
@@ -201,6 +204,7 @@ export const CreateIncidentChatOverlay = () => {
description={
"Are you sure that you want to stop creating the new incident?"
}
+ confirmBtnText={"Yes, close"}
onClose={handleCloseConfirmationDialogClose}
onConfirm={handleCloseConfirmationDialogConfirm}
/>
diff --git a/src/components/Agentic/IncidentsContainer/index.tsx b/src/components/Agentic/IncidentsContainer/index.tsx
index 365986d1c..0bd5cc483 100644
--- a/src/components/Agentic/IncidentsContainer/index.tsx
+++ b/src/components/Agentic/IncidentsContainer/index.tsx
@@ -1,5 +1,6 @@
import { Outlet } from "react-router";
import { useAgenticSelector } from "../../../containers/Agentic/hooks";
+import { CloseIncidentDialogOverlay } from "./CloseIncidentDialogOverlay";
import { CreateIncidentChatOverlay } from "./CreateIncidentChatOverlay";
import * as s from "./styles";
@@ -8,10 +9,15 @@ export const IncidentsContainer = () => {
(state) => state.incidents.isCreateIncidentChatOpen
);
+ const incidentToClose = useAgenticSelector(
+ (state) => state.incidents.incidentToClose
+ );
+
return (
{isCreateIncidentChatOpen && }
+ {incidentToClose && }
);
};
diff --git a/src/components/Agentic/Sidebar/index.tsx b/src/components/Agentic/Sidebar/index.tsx
index 41a8ed0b7..0e37a3607 100644
--- a/src/components/Agentic/Sidebar/index.tsx
+++ b/src/components/Agentic/Sidebar/index.tsx
@@ -12,6 +12,7 @@ import type { IncidentResponseItem } from "../../../redux/services/types";
import { setIsCreateIncidentChatOpen } from "../../../redux/slices/incidentsSlice";
import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
import { getThemeKind } from "../../common/App/styles";
+import { PauseIcon } from "../../common/icons/12px/PauseIcon";
import { LogoutIcon } from "../../common/icons/16px/LogoutIcon";
import { NewPopover } from "../../common/NewPopover";
import { NewButton } from "../../common/v3/NewButton";
@@ -23,9 +24,11 @@ import * as s from "./styles";
const REFRESH_INTERVAL = 10 * 1000; // in milliseconds
-const isIncidentActive = (incident: IncidentResponseItem): boolean => {
- return incident.active_status === "active";
-};
+const isIncidentActive = (incident: IncidentResponseItem): boolean =>
+ incident.status === "active";
+
+const isIncidentPending = (incident: IncidentResponseItem): boolean =>
+ incident.status === "pending";
export const Sidebar = () => {
const theme = useTheme();
@@ -60,6 +63,13 @@ export const Sidebar = () => {
dispatch(setIsCreateIncidentChatOpen(true));
};
+ // const handleDirectivesButtonClick = () => {
+ // sendUserActionTrackingEvent(
+ // trackingEvents.SIDEBAR_DIRECTIVES_BUTTON_CLICKED
+ // );
+ // void navigate("/incidents/directives");
+ // };
+
const handleTemplateButtonClick = () => {
sendUserActionTrackingEvent(trackingEvents.SIDEBAR_TEMPLATE_BUTTON_CLICKED);
void navigate("/incidents/template");
@@ -98,19 +108,14 @@ export const Sidebar = () => {
};
}, [posthog, result.isSuccess]);
- const sortedIncidents = useMemo(
- () =>
- [...(data?.items ?? [])].sort(
- (a, b) =>
- new Date(b.created_at).valueOf() - new Date(a.created_at).valueOf()
- ),
- [data]
- );
+ const incidents = useMemo(() => data?.items ?? [], [data]);
const user = useAgenticSelector((state) => state.auth.user);
const userInitial = (user?.email[0] ?? "").toLocaleUpperCase();
const isTemplateButtonHighlighted =
location.pathname === "/incidents/template";
+ // const isDirectivesButtonHighlighted =
+ // location.pathname === "/incidents/directives";
return (
@@ -127,7 +132,7 @@ export const Sidebar = () => {
- {sortedIncidents.map((incident) => (
+ {incidents.map((incident) => (
{
{incident.name}
+ {isIncidentPending(incident) && (
+
+
+
+ )}
))}
- */}
+ theme.colors.v3.status.high};
`;
-export const TemplateButton = styled(NewButton)`
+export const PauseIconContainer = styled.div`
+ display: flex;
+ flex-shrink: 0;
+`;
+
+export const LinkButton = styled(NewButton)`
color: ${({ theme }) => theme.colors.v3.text.white};
width: 100%;
font-size: 18px;
diff --git a/src/components/Agentic/common/AgentChat/index.tsx b/src/components/Agentic/common/AgentChat/index.tsx
index b623b2e82..c8a882e32 100644
--- a/src/components/Agentic/common/AgentChat/index.tsx
+++ b/src/components/Agentic/common/AgentChat/index.tsx
@@ -2,6 +2,7 @@ import { Fragment, useEffect, useMemo, useState } from "react";
import type { IncidentAgentEvent } from "../../../../redux/services/types";
import { isNumber } from "../../../../typeGuards/isNumber";
import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent";
+import { MagicWandIcon } from "../../../common/icons/16px/MagicWandIcon";
import { Chat } from "../../common/Chat";
import { Accordion } from "../../IncidentDetails/AgentEvents/Accordion";
import { TypingMarkdown } from "../../IncidentDetails/TypingMarkdown";
@@ -20,7 +21,8 @@ export const AgentChat = ({
className,
data,
isDataLoading,
- onNavigateToIncident
+ onNavigateToIncident,
+ attachmentsComponent
}: AgentChatProps) => {
const [initialEventsCount, setInitialEventsCount] = useState();
const [eventsVisibleCount, setEventsVisibleCount] = useState();
@@ -95,15 +97,22 @@ export const AgentChat = ({
speed={shouldShowTypingForEvent(i) ? TYPING_SPEED : undefined}
/>
);
- case "tool":
+ case "tool": {
+ let toolName = event.tool_name;
+
+ if (event.mcp_name) {
+ toolName += ` ${[event.mcp_name, "MCP tool"]
+ .filter(Boolean)
+ .join(" ")})`;
+ }
+
return (
}
/>
);
+ }
case "human":
return {event.message};
case "agent_end": {
@@ -119,6 +128,13 @@ export const AgentChat = ({
}
break;
}
+ case "memory_update":
+ return (
+
+
+ Updated saved memory
+
+ );
default:
return null;
}
@@ -126,6 +142,7 @@ export const AgentChat = ({
return (
theme.colors.v3.text.tertiary};
+ display: flex;
+ align-items: center;
+ gap: 4px;
+`;
diff --git a/src/components/Agentic/common/AgentChat/types.ts b/src/components/Agentic/common/AgentChat/types.ts
index 8e8aff202..64215ade7 100644
--- a/src/components/Agentic/common/AgentChat/types.ts
+++ b/src/components/Agentic/common/AgentChat/types.ts
@@ -1,3 +1,4 @@
+import type { ReactNode } from "react";
import type { IncidentAgentEvent } from "../../../../redux/services/types";
export interface AgentChatProps {
@@ -10,4 +11,5 @@ export interface AgentChatProps {
data?: IncidentAgentEvent[];
isDataLoading: boolean;
onNavigateToIncident?: (incidentId: string) => void;
+ attachmentsComponent?: ReactNode;
}
diff --git a/src/components/Agentic/common/Chat/index.tsx b/src/components/Agentic/common/Chat/index.tsx
index 406b13d87..6a82343c6 100644
--- a/src/components/Agentic/common/Chat/index.tsx
+++ b/src/components/Agentic/common/Chat/index.tsx
@@ -12,6 +12,7 @@ export const Chat = ({
onMessageSend,
chatContent,
className,
+ attachmentsComponent,
promptFontSize
}: ChatProps) => {
const [inputValue, setInputValue] = useState("");
@@ -42,6 +43,7 @@ export const Chat = ({
isSubmitting={isMessageSending}
placeholder={"Write your prompt here"}
fontSize={promptFontSize}
+ attachmentsComponent={attachmentsComponent}
/>
);
diff --git a/src/components/Agentic/common/Chat/types.ts b/src/components/Agentic/common/Chat/types.ts
index b1550ec4d..77e055c4b 100644
--- a/src/components/Agentic/common/Chat/types.ts
+++ b/src/components/Agentic/common/Chat/types.ts
@@ -7,4 +7,5 @@ export interface ChatProps {
chatContent: ReactNode;
className?: string;
promptFontSize?: number; // in pixels
+ attachmentsComponent?: ReactNode;
}
diff --git a/src/components/Agentic/common/FlowChart/FlowChartNode/index.tsx b/src/components/Agentic/common/FlowChart/FlowChartNode/index.tsx
index db1a733fd..edea359c9 100644
--- a/src/components/Agentic/common/FlowChart/FlowChartNode/index.tsx
+++ b/src/components/Agentic/common/FlowChart/FlowChartNode/index.tsx
@@ -6,6 +6,7 @@ import {
} from "@xyflow/react";
import { useState, type ReactNode } from "react";
import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent";
+import { PauseIcon } from "../../../../common/icons/12px/PauseIcon";
import { ChevronIcon } from "../../../../common/icons/16px/ChevronIcon";
import { CopyIcon } from "../../../../common/icons/16px/CopyIcon";
import { RefreshIcon } from "../../../../common/icons/16px/RefreshIcon";
@@ -22,7 +23,7 @@ import * as s from "./styles";
export type Orientation = "horizontal" | "vertical";
-// TODO: Fix types
+// TODO: fix types
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type FlowChartNodeData = {
label?: string;
@@ -30,6 +31,7 @@ export type FlowChartNodeData = {
type?: "default" | "input" | "output";
isActive?: boolean;
isRunning?: boolean;
+ isPending?: boolean;
isDisabled?: boolean;
isInteractive?: boolean;
sideContainers?: {
@@ -112,6 +114,11 @@ export const FlowChartNode = ({ id, data }: NodeProps) => {
{data.label}
)}
{data.isRunning && }
+ {data.isPending && (
+
+
+
+ )}
{data.type !== "input" && (
theme.colors.v3.surface.brandPrimary};
`;
+export const PauseIconContainer = styled.div`
+ display: flex;
+ flex-shrink: 0;
+`;
+
export const InputHandle = styled(Handle)`
height: 24px;
width: 24px;
diff --git a/src/components/Agentic/common/PromptInput/index.tsx b/src/components/Agentic/common/PromptInput/index.tsx
index 543ce6dd3..af0a7422b 100644
--- a/src/components/Agentic/common/PromptInput/index.tsx
+++ b/src/components/Agentic/common/PromptInput/index.tsx
@@ -18,7 +18,8 @@ export const PromptInput = ({
className,
placeholder,
isDisabled,
- fontSize = s.TEXT_AREA_DEFAULT_FONT_SIZE
+ fontSize = s.TEXT_AREA_DEFAULT_FONT_SIZE,
+ attachmentsComponent
}: PromptInputProps) => {
const isSubmittingDisabled = isSubmitting ?? value.trim() === "";
const formRef = useRef(null);
@@ -88,16 +89,19 @@ export const PromptInput = ({
$height={formHeight}
className={className}
>
-
+
+ {attachmentsComponent}
+
+
`
flex-shrink: 0;
`;
+export const TextAreaContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ flex-grow: 1;
+`;
+
export const TextArea = styled.textarea`
color: ${({ theme }) => theme.colors.v3.text.tertiary};
font-size: ${({ $fontSize }) => $fontSize}px;
diff --git a/src/components/Agentic/common/PromptInput/types.ts b/src/components/Agentic/common/PromptInput/types.ts
index 60bfacc9b..7d2a017e2 100644
--- a/src/components/Agentic/common/PromptInput/types.ts
+++ b/src/components/Agentic/common/PromptInput/types.ts
@@ -1,3 +1,5 @@
+import type { ReactNode } from "react";
+
export interface PromptInputProps {
value: string;
onChange: (value: string) => void;
@@ -7,6 +9,7 @@ export interface PromptInputProps {
placeholder?: string;
isDisabled?: boolean;
fontSize?: number; // in pixels
+ attachmentsComponent?: ReactNode;
}
export interface FormProps {
diff --git a/src/components/Agentic/common/SearchInput/index.tsx b/src/components/Agentic/common/SearchInput/index.tsx
new file mode 100644
index 000000000..2648d50ac
--- /dev/null
+++ b/src/components/Agentic/common/SearchInput/index.tsx
@@ -0,0 +1,28 @@
+import type { ChangeEvent } from "react";
+import { MagnifierIcon } from "../../../common/icons/MagnifierIcon";
+import * as s from "./styles";
+import type { SearchInputProps } from "./types";
+
+export const SearchInput = ({
+ onChange,
+ value,
+ placeholder = "Search",
+ className
+}: SearchInputProps) => {
+ const handleChange = (e: ChangeEvent) => {
+ onChange(e.target.value);
+ };
+
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/Agentic/common/SearchInput/styles.ts b/src/components/Agentic/common/SearchInput/styles.ts
new file mode 100644
index 000000000..383838f21
--- /dev/null
+++ b/src/components/Agentic/common/SearchInput/styles.ts
@@ -0,0 +1,36 @@
+import styled from "styled-components";
+import { subscriptRegularTypography } from "../../../common/App/typographies";
+
+export const Container = styled.div`
+ position: relative;
+ display: flex;
+ flex-grow: 1;
+ height: 30px;
+`;
+
+export const Input = styled.input`
+ ${subscriptRegularTypography}
+ color: ${({ theme }) => theme.colors.v3.text.primary};
+ height: 100%;
+ width: 100%;
+ border: 1px solid ${({ theme }) => theme.colors.v3.stroke.dark};
+ background: ${({ theme }) => theme.colors.v3.surface.primary};
+ box-shadow: 0 2px 4px 0 rgb(0 0 0 / 13%);
+ border-radius: 4px;
+ box-sizing: border-box;
+ padding: 6px 8px 6px 24px;
+
+ &::placeholder {
+ color: ${({ theme }) => theme.colors.v3.text.primary};
+ }
+`;
+
+export const IconContainer = styled.div`
+ position: absolute;
+ left: 8px;
+ top: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ color: ${({ theme }) => theme.colors.v3.icon.tertiary};
+`;
diff --git a/src/components/Agentic/common/SearchInput/types.ts b/src/components/Agentic/common/SearchInput/types.ts
new file mode 100644
index 000000000..ae38132a5
--- /dev/null
+++ b/src/components/Agentic/common/SearchInput/types.ts
@@ -0,0 +1,6 @@
+export interface SearchInputProps {
+ onChange: (value: string) => void;
+ value: string;
+ placeholder?: string;
+ className?: string;
+}
diff --git a/src/components/Agentic/tracking.ts b/src/components/Agentic/tracking.ts
index 51ced1caf..e3dcad002 100644
--- a/src/components/Agentic/tracking.ts
+++ b/src/components/Agentic/tracking.ts
@@ -9,6 +9,7 @@ export const trackingEvents = addPrefix(
SIDEBAR_USER_MENU_ITEM_CLICKED: "sidebar user menu item clicked",
SIDEBAR_INCIDENTS_LIST_ITEM_CLICKED: "sidebar incidents list item clicked",
SIDEBAR_CREATE_BUTTON_CLICKED: "sidebar create button clicked",
+ SIDEBAR_DIRECTIVES_BUTTON_CLICKED: "sidebar directives button clicked",
SIDEBAR_TEMPLATE_BUTTON_CLICKED: "sidebar template button clicked",
INCIDENT_CREATION_CHAT_DIALOG_CLOSED:
"incident creation chat dialog closed",
@@ -17,10 +18,10 @@ export const trackingEvents = addPrefix(
FLOW_CHART_NODE_KEBAB_MENU_CLICKED: "flow chart node kebab menu clicked",
FLOW_CHART_NODE_KEBAB_MENU_ITEM_CLICKED:
"flow chart node kebab menu item clicked",
- FLOW_CHART_NODE_MCP_TOOLBAR_MENU_CLICKED:
- "flow chart node mcp toolbar menu clicked",
- FLOW_CHART_NODE_MCP_TOOLBAR_MENU_ITEM_CLICKED:
- "flow chart node mcp toolbar menu item clicked",
+ FLOW_CHART_NODE_MCP_TOOLBAR_SERVER_ICON_CLICKED:
+ "flow chart node mcp toolbar server icon clicked",
+ FLOW_CHART_NODE_MCP_TOOLBAR_SERVER_ICON_MENU_ITEM_CLICKED:
+ "flow chart node mcp toolbar server icon menu item clicked",
AGENT_FLOW_CHART_NODE_ADD_MCP_SERVER_BUTTON_CLICKED:
"flow chart node add mcp server button clicked",
INCIDENT_TEMPLATE_ADD_MCP_DIALOG_CONNECT_BUTTON_CLICKED:
@@ -41,8 +42,6 @@ export const trackingEvents = addPrefix(
"incident template edit mcp dialog select all toggle changed",
INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_TOOL_TAG_CLICKED:
"incident template edit mcp dialog tool tag clicked",
- INCIDENT_TEMPLATE_EDIT_MCP_DIALOG_TOOL_TAG_DELETE_BUTTON_CLICKED:
- "incident template edit mcp dialog tool tag delete button clicked",
INCIDENT_HOME_BREADCRUMB_CLICKED: "incident home breadcrumb clicked",
INCIDENT_AGENT_VIEW_MODE_TOGGLE_CHANGED:
"incident agent view mode toggle changed",
diff --git a/src/components/Insights/InsightsCatalog/PromotionSection/Promotions/EarlyAccessPromotion/index.tsx b/src/components/Insights/InsightsCatalog/PromotionSection/Promotions/EarlyAccessPromotion/index.tsx
index ae5ba099e..a02a993be 100644
--- a/src/components/Insights/InsightsCatalog/PromotionSection/Promotions/EarlyAccessPromotion/index.tsx
+++ b/src/components/Insights/InsightsCatalog/PromotionSection/Promotions/EarlyAccessPromotion/index.tsx
@@ -97,7 +97,7 @@ export const EarlyAccessPromotion = ({
description={
"Are you sure you want to miss out on this exclusive, limited-time offer?"
}
- cancelBtnText={"Yes, discard"}
+ confirmBtnText={"Yes, discard"}
onClose={handleCancelConfirmationClose}
onConfirm={handleCancelConfirmationAccept}
/>
diff --git a/src/components/Insights/InsightsCatalog/PromotionSection/Promotions/UdemyPromotion/index.tsx b/src/components/Insights/InsightsCatalog/PromotionSection/Promotions/UdemyPromotion/index.tsx
index a94f946d4..24bc109a7 100644
--- a/src/components/Insights/InsightsCatalog/PromotionSection/Promotions/UdemyPromotion/index.tsx
+++ b/src/components/Insights/InsightsCatalog/PromotionSection/Promotions/UdemyPromotion/index.tsx
@@ -93,7 +93,7 @@ export const UdemyPromotion = ({
description={
"Are you sure you want to miss out on this exclusive, limited-time offer?"
}
- cancelBtnText={"Yes, discard"}
+ confirmBtnText={"Yes, discard"}
onClose={handleCancelConfirmationClose}
onConfirm={handleCancelConfirmationAccept}
/>
diff --git a/src/components/RecentActivity/CreateEnvironmentWizard/CancelConfirmationDialog/index.tsx b/src/components/RecentActivity/CreateEnvironmentWizard/CancelConfirmationDialog/index.tsx
index 41ab77b5d..1dbdef3a7 100644
--- a/src/components/RecentActivity/CreateEnvironmentWizard/CancelConfirmationDialog/index.tsx
+++ b/src/components/RecentActivity/CreateEnvironmentWizard/CancelConfirmationDialog/index.tsx
@@ -8,6 +8,7 @@ export const CancelConfirmationDialog = ({
diff --git a/src/components/common/CancelConfirmation/index.tsx b/src/components/common/CancelConfirmation/index.tsx
index a9db0513d..634a5b0aa 100644
--- a/src/components/common/CancelConfirmation/index.tsx
+++ b/src/components/common/CancelConfirmation/index.tsx
@@ -8,7 +8,8 @@ export const CancelConfirmation = ({
onClose,
header,
description,
- cancelBtnText
+ cancelBtnText = "No, continue",
+ confirmBtnText = "Yes, cancel"
}: CancelConfirmationProps) => {
const handleConfirmButtonClick = () => {
onConfirm();
@@ -34,12 +35,12 @@ export const CancelConfirmation = ({
diff --git a/src/components/common/CancelConfirmation/types.ts b/src/components/common/CancelConfirmation/types.ts
index 70ed407d8..74e616d48 100644
--- a/src/components/common/CancelConfirmation/types.ts
+++ b/src/components/common/CancelConfirmation/types.ts
@@ -1,6 +1,7 @@
export interface CancelConfirmationProps {
header: string;
description: string;
+ confirmBtnText?: string;
cancelBtnText?: string;
onClose: () => void;
onConfirm: () => void;
diff --git a/src/components/common/KebabMenu/index.tsx b/src/components/common/KebabMenu/index.tsx
new file mode 100644
index 000000000..a2ab196fa
--- /dev/null
+++ b/src/components/common/KebabMenu/index.tsx
@@ -0,0 +1,33 @@
+import { useState } from "react";
+import { MenuList } from "../../Navigation/common/MenuList";
+import { ThreeDotsVerticalIcon } from "../icons/ThreeDotsVerticalIcon";
+import { NewPopover } from "../NewPopover";
+import { NewIconButton } from "../v3/NewIconButton";
+import * as s from "./styles";
+import type { KebabMenuProps } from "./types";
+
+export const KebabMenu = ({ items }: KebabMenuProps) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const handleKebabMenuOpenChange = (isOpen: boolean) => {
+ setIsOpen(isOpen);
+ };
+
+ return (
+
+
+
+ }
+ onOpenChange={handleKebabMenuOpenChange}
+ isOpen={isOpen}
+ placement={"bottom-end"}
+ >
+
+
+ );
+};
diff --git a/src/components/common/KebabMenu/styles.ts b/src/components/common/KebabMenu/styles.ts
new file mode 100644
index 000000000..5972832e1
--- /dev/null
+++ b/src/components/common/KebabMenu/styles.ts
@@ -0,0 +1,9 @@
+import styled from "styled-components";
+import { Popup as CommonPopup } from "../../Navigation/common/Popup";
+import { ContentContainer } from "../../Navigation/common/Popup/styles";
+
+export const Popup = styled(CommonPopup)`
+ & > ${ContentContainer} {
+ padding: 3px 8px;
+ }
+`;
diff --git a/src/components/common/KebabMenu/types.ts b/src/components/common/KebabMenu/types.ts
new file mode 100644
index 000000000..42af6a241
--- /dev/null
+++ b/src/components/common/KebabMenu/types.ts
@@ -0,0 +1,5 @@
+import type { MenuItem } from "../../Navigation/common/MenuList/types";
+
+export interface KebabMenuProps {
+ items: MenuItem[];
+}
diff --git a/src/components/common/icons/12px/PauseIcon.tsx b/src/components/common/icons/12px/PauseIcon.tsx
new file mode 100644
index 000000000..9f5916fa7
--- /dev/null
+++ b/src/components/common/icons/12px/PauseIcon.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+import { useIconProps } from "../hooks";
+import type { IconProps } from "../types";
+
+const PauseIconComponent = (props: IconProps) => {
+ const { size, color } = useIconProps(props);
+
+ return (
+
+ );
+};
+
+export const PauseIcon = React.memo(PauseIconComponent);
diff --git a/src/components/common/v3/Checkmark/index.tsx b/src/components/common/v3/Checkmark/index.tsx
index 1bdd64c36..653d62a55 100644
--- a/src/components/common/v3/Checkmark/index.tsx
+++ b/src/components/common/v3/Checkmark/index.tsx
@@ -3,7 +3,12 @@ import { CheckmarkIcon } from "../../icons/CheckmarkIcon";
import * as s from "./styles";
import type { CheckmarkProps } from "./types";
-export const Checkmark = ({ onChange, value, disabled }: CheckmarkProps) => {
+export const Checkmark = ({
+ onChange,
+ value,
+ disabled,
+ size = "small"
+}: CheckmarkProps) => {
const handleChange = (e: ChangeEvent) => {
onChange(e.target.checked);
};
@@ -15,10 +20,11 @@ export const Checkmark = ({ onChange, value, disabled }: CheckmarkProps) => {
type={"checkbox"}
onChange={handleChange}
checked={value}
+ $size={size}
/>
{value && (
-
+
)}
diff --git a/src/components/common/v3/Checkmark/styles.ts b/src/components/common/v3/Checkmark/styles.ts
index 4c543aaa1..1b589e545 100644
--- a/src/components/common/v3/Checkmark/styles.ts
+++ b/src/components/common/v3/Checkmark/styles.ts
@@ -1,4 +1,5 @@
import styled from "styled-components";
+import type { CheckmarkComponentProps } from "./types";
export const Container = styled.div`
position: relative;
@@ -13,16 +14,17 @@ export const CheckmarkContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
+ pointer-events: none;
`;
-export const Checkmark = styled.input`
+export const Checkmark = styled.input`
appearance: none;
- margin: 1px;
cursor: pointer;
+ margin: 0;
border: 1px solid ${({ theme }) => theme.colors.v3.surface.highlight};
- width: 12px;
- height: 12px;
- border-radius: 4px;
+ width: ${({ $size }) => ($size === "small" ? 12 : 16)}px;
+ height: ${({ $size }) => ($size === "small" ? 12 : 16)}px;
+ border-radius: ${({ $size }) => ($size === "small" ? 4 : 6)}px;
padding: 1px;
background: ${({ theme }) => theme.colors.v3.surface.highlight};
diff --git a/src/components/common/v3/Checkmark/types.ts b/src/components/common/v3/Checkmark/types.ts
index 0028a5d55..49a110be7 100644
--- a/src/components/common/v3/Checkmark/types.ts
+++ b/src/components/common/v3/Checkmark/types.ts
@@ -1,5 +1,12 @@
+type CheckmarkSize = "small" | "large";
+
export interface CheckmarkProps {
value: boolean;
disabled?: boolean;
onChange: (value: boolean) => void;
+ size?: CheckmarkSize;
+}
+
+export interface CheckmarkComponentProps {
+ $size?: CheckmarkSize;
}
diff --git a/src/containers/Agentic/router.tsx b/src/containers/Agentic/router.tsx
index 0ee6ca5da..2cfbbf85d 100644
--- a/src/containers/Agentic/router.tsx
+++ b/src/containers/Agentic/router.tsx
@@ -2,6 +2,7 @@ import type { RouteObject } from "react-router";
import { createBrowserRouter, Navigate, useRouteError } from "react-router";
import { Agentic } from "../../components/Agentic";
import { IncidentDetails } from "../../components/Agentic/IncidentDetails";
+import { IncidentDirectives } from "../../components/Agentic/IncidentDirectives";
import { Incidents } from "../../components/Agentic/Incidents";
import { IncidentsContainer } from "../../components/Agentic/IncidentsContainer";
import { IncidentTemplate } from "../../components/Agentic/IncidentTemplate";
@@ -26,6 +27,7 @@ export const routes: RouteObject[] = [
index: true,
element:
},
+ { path: "directives", element: },
{ path: "template", element: },
{
path: ":id",
diff --git a/src/redux/services/digma.ts b/src/redux/services/digma.ts
index efc340179..b97cd4691 100644
--- a/src/redux/services/digma.ts
+++ b/src/redux/services/digma.ts
@@ -1,10 +1,14 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { isString } from "../../typeGuards/isString";
import type {
+ AddMCPServerPayload,
+ CloseIncidentPayload,
CreateEnvironmentPayload,
CreateEnvironmentResponse,
DeleteEnvironmentPayload,
DeleteEnvironmentResponse,
+ DeleteIncidentAgentDirectivePayload,
+ DeleteMCPServerPayload,
DismissErrorPayload,
DismissUndismissInsightPayload,
ExtendedGetInsightsPayload,
@@ -20,6 +24,8 @@ import type {
GetAssetsResponse,
GetBlockedTracesPayload,
GetBlockedTracesResponse,
+ GetDirectivesPayload,
+ GetDirectivesResponse,
GetEndpointsIssuesPayload,
GetEnvironmentServicesPayload,
GetEnvironmentServicesResponse,
@@ -54,6 +60,7 @@ import type {
GetIssuesFiltersResponse,
GetIssuesPayload,
GetIssuesResponse,
+ GetMCPServersResponse,
GetMetricsReportDataPayloadV1,
GetMetricsReportDataPayloadV2,
GetPerformanceHighlightsPayload,
@@ -92,9 +99,12 @@ import type {
SetMetricsReportDataPayload,
SetServiceEndpointsPayload,
SetServiceEnvironmentsPayload,
+ TestMCPServerPayload,
+ TestMCPServerResponse,
UndismissErrorPayload,
UnlinkTicketFromIssuePayload,
- UnpinErrorPayload
+ UnpinErrorPayload,
+ UpdateMCPServerPayload
} from "./types";
export const digmaApi = createApi({
@@ -105,8 +115,11 @@ export const digmaApi = createApi({
"Error",
"Insight",
"RecentActivity",
+ "Incident",
"IncidentAgentChatEvent",
- "IncidentEntryAgentChatEvent"
+ "IncidentEntryAgentChatEvent",
+ "IncidentAgentDirective",
+ "IncidentAgentMCPServer"
],
reducerPath: "digmaApi",
baseQuery: fetchBaseQuery({
@@ -557,12 +570,21 @@ export const digmaApi = createApi({
getIncidents: builder.query({
query: () => ({
url: "Agentic/incidents"
- })
+ }),
+ providesTags: ["Incident"]
}),
getIncident: builder.query({
query: ({ id }) => ({
url: `Agentic/incidents/${window.encodeURIComponent(id)}`
- })
+ }),
+ providesTags: ["Incident"]
+ }),
+ closeIncident: builder.mutation({
+ query: ({ id }) => ({
+ url: `Agentic/incidents/${window.encodeURIComponent(id)}/close`,
+ method: "PUT"
+ }),
+ invalidatesTags: ["Incident"]
}),
getIncidentAgents: builder.query<
GetIncidentAgentsResponse,
@@ -617,6 +639,71 @@ export const digmaApi = createApi({
body: data
}),
invalidatesTags: ["IncidentEntryAgentChatEvent"]
+ }),
+ getIncidentAgentDirectives: builder.query<
+ GetDirectivesResponse,
+ GetDirectivesPayload
+ >({
+ query: (data) => ({
+ url: "Agentic/directives",
+ params: data
+ }),
+ providesTags: ["IncidentAgentDirective"]
+ }),
+ deleteIncidentAgentDirective: builder.mutation<
+ void,
+ DeleteIncidentAgentDirectivePayload
+ >({
+ query: ({ id }) => ({
+ url: `Agentic/directives/${window.encodeURIComponent(id)}`,
+ method: "DELETE"
+ }),
+ invalidatesTags: ["IncidentAgentDirective"]
+ }),
+ getIncidentAgentMCPServers: builder.query({
+ query: () => ({
+ url: "Agentic/mcps"
+ }),
+ providesTags: ["IncidentAgentMCPServer"]
+ }),
+ testIncidentAgentMCPServer: builder.mutation<
+ TestMCPServerResponse,
+ TestMCPServerPayload
+ >({
+ query: (data) => ({
+ url: `Agentic/mcps/test`,
+ method: "POST",
+ body: data
+ })
+ }),
+ addIncidentAgentMCPServer: builder.mutation({
+ query: (data) => ({
+ url: `Agentic/mcps`,
+ method: "POST",
+ body: data
+ }),
+ invalidatesTags: ["IncidentAgentMCPServer"]
+ }),
+ updateIncidentAgentMCPServer: builder.mutation<
+ void,
+ UpdateMCPServerPayload
+ >({
+ query: ({ id, data }) => ({
+ url: `Agentic/mcps/${window.encodeURIComponent(id)}`,
+ method: "PUT",
+ body: data
+ }),
+ invalidatesTags: ["IncidentAgentMCPServer"]
+ }),
+ deleteIncidentAgentMCPServer: builder.mutation<
+ void,
+ DeleteMCPServerPayload
+ >({
+ query: ({ id }) => ({
+ url: `Agentic/mcps/${window.encodeURIComponent(id)}`,
+ method: "DELETE"
+ }),
+ invalidatesTags: ["IncidentAgentMCPServer"]
})
})
});
@@ -673,9 +760,17 @@ export const {
useGetIssueRecommendationsQuery,
useGetIncidentsQuery,
useGetIncidentQuery,
+ useCloseIncidentMutation,
useGetIncidentAgentsQuery,
useGetIncidentAgentEventsQuery,
useGetIncidentAgentChatEventsQuery,
useSendMessageToIncidentAgentChatMutation,
- useSendMessageToIncidentCreationChatMutation
+ useSendMessageToIncidentCreationChatMutation,
+ useGetIncidentAgentDirectivesQuery,
+ useDeleteIncidentAgentDirectiveMutation,
+ useGetIncidentAgentMCPServersQuery,
+ useTestIncidentAgentMCPServerMutation,
+ useAddIncidentAgentMCPServerMutation,
+ useUpdateIncidentAgentMCPServerMutation,
+ useDeleteIncidentAgentMCPServerMutation
} = digmaApi;
diff --git a/src/redux/services/types.ts b/src/redux/services/types.ts
index 103a7a046..eb08d3033 100644
--- a/src/redux/services/types.ts
+++ b/src/redux/services/types.ts
@@ -1085,14 +1085,12 @@ export interface GetBlockedTracesResponse {
total: number;
}
-export type IncidentActivityStatus = "active" | "paused" | "closed";
+export type IncidentStatus = "active" | "pending" | "closed";
export interface IncidentResponseItem {
id: string;
name: string;
- active_status: IncidentActivityStatus;
- created_at: string;
- closed_at: string | null;
+ status: IncidentStatus;
}
export interface GetIncidentsResponse {
@@ -1103,6 +1101,10 @@ export interface GetIncidentPayload {
id: string;
}
+export interface CloseIncidentPayload {
+ id: string;
+}
+
export interface IncidentIssue {
issue_id: string;
span_uid: string | null;
@@ -1134,17 +1136,21 @@ export interface IncidentArtifact {
export interface GetIncidentResponse {
id: string;
name: string;
- status: string;
+ status_description: string;
summary: string;
- active_status: IncidentActivityStatus;
+ status: IncidentStatus;
related_issues: GenericIncidentIssue[];
related_artifacts: IncidentArtifact[];
affected_services: string[];
- created_at: string;
- closed_at: string | null;
+ status_timestamps: Partial>;
}
-export type AgentStatus = "pending" | "active" | "inactive";
+export type AgentStatus =
+ | "waiting"
+ | "running"
+ | "skipped"
+ | "pending"
+ | "completed";
export interface AgentMCPServer {
name: string;
@@ -1156,7 +1162,6 @@ export interface Agent {
name: string;
display_name: string;
description: string;
- running: boolean;
status: AgentStatus;
mcp_servers: AgentMCPServer[];
}
@@ -1182,7 +1187,8 @@ export interface IncidentAgentEvent {
| "tool"
| "error"
| "agent_end"
- | "input_user_required";
+ | "input_user_required"
+ | "memory_update";
message: string;
agent_name: string;
tool_name?: string | null;
@@ -1208,3 +1214,61 @@ export interface SendMessageToIncidentCreationChatPayload {
incidentId: string;
data: { text: string };
}
+
+export interface GetDirectivesPayload {
+ search_term?: string;
+}
+
+export interface Directive {
+ id: string;
+ directive: string;
+ condition: string;
+ agents: string[];
+}
+
+export interface GetDirectivesResponse {
+ directives: Directive[];
+}
+
+export interface DeleteIncidentAgentDirectivePayload {
+ id: string;
+}
+
+export interface MCPServerData {
+ uid: string;
+ name: string;
+ config: string;
+ agents: string[];
+ editable: boolean;
+ selected_tools: string[];
+ all_tools: string[];
+ instructions_prompt: string;
+}
+
+export interface GetMCPServersResponse {
+ mcps: MCPServerData[];
+}
+
+export interface TestMCPServerPayload {
+ config_json: string;
+}
+
+export interface TestMCPServerResponse {
+ tools: string[];
+}
+
+export interface AddMCPServerPayload {
+ config_json: string;
+ selected_tools: string[];
+ agent: string;
+ instructions_prompt: string;
+}
+
+export interface UpdateMCPServerPayload {
+ id: string;
+ data: AddMCPServerPayload;
+}
+
+export interface DeleteMCPServerPayload {
+ id: string;
+}
diff --git a/src/redux/slices/incidentsSlice.ts b/src/redux/slices/incidentsSlice.ts
index 1980bffa7..2e83265b4 100644
--- a/src/redux/slices/incidentsSlice.ts
+++ b/src/redux/slices/incidentsSlice.ts
@@ -5,11 +5,13 @@ import type { BaseState } from "./types";
export interface IncidentsState extends BaseState {
isCreateIncidentChatOpen: boolean;
+ incidentToClose: string | null;
}
const initialState: IncidentsState = {
version: STATE_VERSION,
- isCreateIncidentChatOpen: false
+ isCreateIncidentChatOpen: false,
+ incidentToClose: null
};
export const incidentsSlice = createSlice({
@@ -19,6 +21,9 @@ export const incidentsSlice = createSlice({
setIsCreateIncidentChatOpen: (state, action: { payload: boolean }) => {
state.isCreateIncidentChatOpen = action.payload;
},
+ setIncidentToClose: (state, action: { payload: string | null }) => {
+ state.incidentToClose = action.payload;
+ },
clear: () => initialState
},
extraReducers: (builder) => {
@@ -26,6 +31,7 @@ export const incidentsSlice = createSlice({
}
});
-export const { setIsCreateIncidentChatOpen, clear } = incidentsSlice.actions;
+export const { setIsCreateIncidentChatOpen, setIncidentToClose, clear } =
+ incidentsSlice.actions;
export default incidentsSlice.reducer;
diff --git a/webpack.dev.ts b/webpack.dev.ts
index 55911a9ef..7a9a5092c 100644
--- a/webpack.dev.ts
+++ b/webpack.dev.ts
@@ -22,14 +22,14 @@ const DEFAULT_PORT = 3000;
dotenv.config();
-if (!process.env.USERNAME || !process.env.PASSWORD) {
- throw new Error("Username and password must be provided");
+if (!process.env.LOGIN || !process.env.PASSWORD) {
+ throw new Error("Login and password must be provided");
}
let session: Session | undefined;
const credentials: Credentials = {
- username: process.env.USERNAME,
+ username: process.env.LOGIN,
password: process.env.PASSWORD
};
@@ -43,16 +43,13 @@ const apiProxyClient = axios.create({
}
});
-const login = async ({ username, password }: Credentials) => {
+const login = async (credentials: Credentials) => {
const response = await apiProxyClient.post<{
accessToken: string;
refreshToken: string;
expiration: string;
userId: string;
- }>("/authentication/login", {
- username,
- password
- });
+ }>("/authentication/login", credentials);
return response.data;
};
@@ -147,17 +144,19 @@ const config: WebpackConfiguration = {
webApps.forEach((app) =>
devServer.app?.get(`/${app}/env.js`, (req, res) => {
const envVariables = {
- // Put app environment variables here
- isJaegerEnabled: true,
- // jaegerURL: "",
- jaegerApiPath: process.env.JAEGER_API_PATH,
platform: "Web",
- areInsightSuggestionsEnabled: true,
- // googleClientId: "",
- // postHogApiKey: "",
- // postHogHost: "",
- isSandboxModeEnabled: false
- // productFruitsWorkspaceCode: ""
+ isJaegerEnabled: process.env.IS_JAEGER_ENABLED === "true",
+ jaegerURL: process.env.JAEGER_URL,
+ jaegerApiPath: process.env.JAEGER_API_PATH,
+ isSandboxModeEnabled:
+ process.env.IS_SANDBOX_MODE_ENABLED === "true",
+ areInsightSuggestionsEnabled:
+ process.env.ARE_INSIGHT_SUGGESTIONS_ENABLED === "true",
+ googleClientId: process.env.GOOGLE_CLIENT_ID,
+ postHogApiKey: process.env.POSTHOG_API_KEY,
+ postHogHost: process.env.POSTHOG_URL,
+ productFruitsWorkspaceCode:
+ process.env.PRODUCT_FRUITS_WORKSPACE_CODE
};
let envFileContent = "";