Skip to content

Commit 4db953f

Browse files
committed
Add incident manual creation chat
1 parent b2026ef commit 4db953f

File tree

25 files changed

+490
-112
lines changed

25 files changed

+490
-112
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useParams } from "react-router";
2+
import { useStableSearchParams } from "../../../../hooks/useStableSearchParams";
3+
import { useSendMessageToIncidentAgentChatMutation } from "../../../../redux/services/digma";
4+
import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent";
5+
import { AgentChat } from "../../common/AgentChat";
6+
import { trackingEvents } from "../../tracking";
7+
8+
export const IncidentAgentChat = () => {
9+
const params = useParams();
10+
const incidentId = params.id;
11+
const [searchParams] = useStableSearchParams();
12+
const agentId = searchParams.get("agent");
13+
14+
const [sendMessage, { isLoading: isMessageSending }] =
15+
useSendMessageToIncidentAgentChatMutation();
16+
17+
const handleMessageSend = (text: string) => {
18+
sendUserActionTrackingEvent(
19+
trackingEvents.INCIDENT_AGENT_MESSAGE_SUBMITTED,
20+
{
21+
agentName: agentId ?? ""
22+
}
23+
);
24+
25+
void sendMessage({
26+
incidentId: incidentId ?? "",
27+
agentId: agentId ?? "",
28+
data: { text }
29+
});
30+
};
31+
32+
return (
33+
<AgentChat
34+
incidentId={incidentId}
35+
agentId={agentId ?? ""}
36+
onMessageSend={handleMessageSend}
37+
isMessageSending={isMessageSending}
38+
/>
39+
);
40+
};

src/components/Agentic/IncidentDetails/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { trackingEvents } from "../tracking";
1818
import { AdditionalInfo } from "./AdditionalInfo";
1919
import { AgentEvents } from "./AgentEvents";
2020
import { AgentFlowChart } from "./AgentFlowChart";
21-
import { Chat } from "./Chat";
21+
import { IncidentAgentChat } from "./IncidentAgentChat";
2222
import { IncidentMetaData } from "./IncidentMetaData";
2323
import * as s from "./styles";
2424
import type { AgentViewMode } from "./types";
@@ -185,7 +185,7 @@ export const IncidentDetails = () => {
185185
</s.SummaryContainerToolbar>
186186
{agentId ? (
187187
agentViewMode === "chat" ? (
188-
<Chat key={agentId} />
188+
<IncidentAgentChat key={agentId} />
189189
) : (
190190
<AgentEvents key={agentId} />
191191
)

src/components/Agentic/IncidentTemplate/AddMCPServerDialog/index.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { useState } from "react";
22
import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent";
3-
import { CrossIcon } from "../../../common/icons/12px/CrossIcon";
3+
import { Dialog } from "../../common/Dialog";
44
import { trackingEvents } from "../../tracking";
55
import { ServerStep } from "./ServerStep";
6-
import * as s from "./styles";
76
import { ToolsStep } from "./ToolsStep";
87
import type { AddMCPServerDialogProps } from "./types";
98

@@ -45,24 +44,16 @@ export const AddMCPServerDialog = ({
4544
/>
4645
];
4746

48-
const handleCloseButtonClick = () => {
47+
const handleDialogClose = () => {
4948
sendUserActionTrackingEvent(
50-
trackingEvents.INCIDENT_TEMPLATE_ADD_MCP_DIALOG_CLOSE_BUTTON_CLICKED
49+
trackingEvents.INCIDENT_TEMPLATE_ADD_MCP_DIALOG_CLOSED
5150
);
5251
onClose();
5352
};
5453

5554
return (
56-
<s.Container>
57-
<s.Header>
58-
<s.Header>
59-
Wizard
60-
<s.CloseButton onClick={handleCloseButtonClick}>
61-
<CrossIcon color={"currentColor"} />
62-
</s.CloseButton>
63-
</s.Header>
64-
</s.Header>
55+
<Dialog title={"Wizard"} onClose={handleDialogClose}>
6556
{steps[currentStep]}
66-
</s.Container>
57+
</Dialog>
6758
);
6859
};
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { fetchEventSource } from "@microsoft/fetch-event-source";
2+
import { useEffect, useRef, useState } from "react";
3+
import { useAgenticDispatch } from "../../../../containers/Agentic/hooks";
4+
import { useSendMessageToIncidentCreationChatMutation } from "../../../../redux/services/digma";
5+
import { setIsCreateIncidentChatOpen } from "../../../../redux/slices/incidentsSlice";
6+
import { isString } from "../../../../typeGuards/isString";
7+
import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent";
8+
import { CancelConfirmation } from "../../../common/CancelConfirmation";
9+
import { Dialog } from "../../common/Dialog";
10+
import { trackingEvents } from "../../tracking";
11+
import * as s from "./styles";
12+
13+
const AGENT_ID = "incident_entry";
14+
const PROMPT_FONT_SIZE = 14; // in pixels
15+
16+
export const CreateIncidentChatOverlay = () => {
17+
const [incidentId, setIncidentId] = useState<string>();
18+
const [
19+
isCloseConfirmationDialogVisible,
20+
setIsCloseConfirmationDialogVisible
21+
] = useState(false);
22+
const [isStartMessageSending, setIsStartMessageSending] = useState(false);
23+
const abortControllerRef = useRef<AbortController | null>(null);
24+
25+
const dispatch = useAgenticDispatch();
26+
27+
const [sendMessage, { isLoading: isMessageSending }] =
28+
useSendMessageToIncidentCreationChatMutation();
29+
30+
const handleCreateIncidentChatMessageSend = (text: string) => {
31+
// Send first message to start the incident creation chat
32+
if (!incidentId) {
33+
// Stop any existing connection
34+
if (abortControllerRef.current) {
35+
abortControllerRef.current.abort();
36+
}
37+
38+
abortControllerRef.current = new AbortController();
39+
40+
setIsStartMessageSending(true);
41+
void fetchEventSource(
42+
`${
43+
isString(window.digmaApiProxyPrefix)
44+
? window.digmaApiProxyPrefix
45+
: "/api/"
46+
}Agentic/incident-entry`,
47+
{
48+
method: "POST",
49+
headers: {
50+
"Content-Type": "application/json"
51+
},
52+
body: JSON.stringify({
53+
text
54+
}),
55+
onopen: (response: Response) => {
56+
if (response.ok) {
57+
setIncidentId(
58+
response.headers.get("agentic-conversation-id") ?? ""
59+
);
60+
setIsStartMessageSending(false);
61+
return Promise.resolve();
62+
} else {
63+
setIsStartMessageSending(false);
64+
return Promise.reject(
65+
new Error(`HTTP ${response.status}: ${response.statusText}`)
66+
);
67+
}
68+
},
69+
onerror: (err: unknown) => {
70+
abortControllerRef.current = null;
71+
setIsStartMessageSending(false);
72+
if (err instanceof Error) {
73+
// eslint-disable-next-line no-console
74+
console.error("Error starting incident creation chat:", err);
75+
} else {
76+
// eslint-disable-next-line no-console
77+
console.error("Unknown error starting incident creation chat");
78+
}
79+
}
80+
}
81+
);
82+
}
83+
84+
// Send subsequent messages to the incident creation chat
85+
if (incidentId) {
86+
void sendMessage({
87+
incidentId,
88+
data: { text }
89+
});
90+
}
91+
};
92+
93+
const handleCreateIncidentChatDialogClose = () => {
94+
setIsCloseConfirmationDialogVisible(true);
95+
};
96+
97+
const handleCloseConfirmationDialogClose = () => {
98+
setIsCloseConfirmationDialogVisible(false);
99+
};
100+
101+
const handleCloseConfirmationDialogConfirm = () => {
102+
sendUserActionTrackingEvent(
103+
trackingEvents.INCIDENT_CREATION_CHAT_DIALOG_CLOSED
104+
);
105+
setIsCloseConfirmationDialogVisible(false);
106+
dispatch(setIsCreateIncidentChatOpen(false));
107+
};
108+
109+
useEffect(() => {
110+
return () => {
111+
if (abortControllerRef.current) {
112+
abortControllerRef.current.abort();
113+
}
114+
};
115+
}, []);
116+
117+
return (
118+
<>
119+
<s.StyledOverlay>
120+
<Dialog
121+
onClose={handleCreateIncidentChatDialogClose}
122+
title={"Add new incident"}
123+
>
124+
<s.StyledAgentChat
125+
incidentId={incidentId}
126+
agentId={AGENT_ID}
127+
onMessageSend={handleCreateIncidentChatMessageSend}
128+
isMessageSending={isMessageSending || isStartMessageSending}
129+
promptFontSize={PROMPT_FONT_SIZE}
130+
/>
131+
</Dialog>
132+
</s.StyledOverlay>
133+
{isCloseConfirmationDialogVisible && (
134+
<s.StyledOverlay>
135+
<CancelConfirmation
136+
header={"Close incident creation chat?"}
137+
description={
138+
"Are you sure that you want to stop creating the new incident?"
139+
}
140+
onClose={handleCloseConfirmationDialogClose}
141+
onConfirm={handleCloseConfirmationDialogConfirm}
142+
/>
143+
</s.StyledOverlay>
144+
)}
145+
</>
146+
);
147+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import styled from "styled-components";
2+
import { Overlay } from "../../../common/Overlay";
3+
import { AgentChat } from "../../common/AgentChat";
4+
5+
export const StyledOverlay = styled(Overlay)`
6+
align-items: center;
7+
`;
8+
9+
export const StyledAgentChat = styled(AgentChat)`
10+
${/* TODO: change to color from the theme */ ""}
11+
background: #000;
12+
border-radius: 8px;
13+
padding: 24px;
14+
gap: 12px;
15+
`;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export interface OverlayProps {
2+
$transitionDuration: number;
3+
$transitionClassName: string;
4+
$isVisible: boolean;
5+
}
6+
7+
export interface PopupContainerProps {
8+
$transitionDuration: number;
9+
$transitionClassName: string;
10+
}
Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import { Outlet } from "react-router";
2+
import { useAgenticSelector } from "../../../containers/Agentic/hooks";
3+
import { CreateIncidentChatOverlay } from "./CreateIncidentChatOverlay";
24
import * as s from "./styles";
35

4-
export const IncidentsContainer = () => (
5-
<s.Container>
6-
<Outlet />
7-
</s.Container>
8-
);
6+
export const IncidentsContainer = () => {
7+
const isCreateIncidentChatOpen = useAgenticSelector(
8+
(state) => state.incidents.isCreateIncidentChatOpen
9+
);
10+
11+
return (
12+
<s.Container>
13+
<Outlet />
14+
{isCreateIncidentChatOpen && <CreateIncidentChatOverlay />}
15+
</s.Container>
16+
);
17+
};

src/components/Agentic/Sidebar/index.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@ import { usePostHog } from "posthog-js/react";
22
import { useEffect, useMemo, useState } from "react";
33
import { useLocation, useNavigate, useParams } from "react-router";
44
import { useTheme } from "styled-components";
5-
import { useAgenticSelector } from "../../../containers/Agentic/hooks";
5+
import {
6+
useAgenticDispatch,
7+
useAgenticSelector
8+
} from "../../../containers/Agentic/hooks";
69
import { useLogoutMutation } from "../../../redux/services/auth";
710
import { useGetIncidentsQuery } from "../../../redux/services/digma";
811
import type { IncidentResponseItem } from "../../../redux/services/types";
12+
import { setIsCreateIncidentChatOpen } from "../../../redux/slices/incidentsSlice";
913
import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
1014
import { getThemeKind } from "../../common/App/styles";
1115
import { LogoutIcon } from "../../common/icons/16px/LogoutIcon";
1216
import { NewPopover } from "../../common/NewPopover";
17+
import { NewButton } from "../../common/v3/NewButton";
1318
import { Tooltip } from "../../common/v3/Tooltip";
1419
import { MenuList } from "../../Navigation/common/MenuList";
1520
import { Popup } from "../../Navigation/common/Popup";
@@ -31,6 +36,7 @@ export const Sidebar = () => {
3136
const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
3237
const posthog = usePostHog();
3338
const location = useLocation();
39+
const dispatch = useAgenticDispatch();
3440

3541
const { data } = useGetIncidentsQuery(undefined, {
3642
pollingInterval: REFRESH_INTERVAL
@@ -49,6 +55,11 @@ export const Sidebar = () => {
4955
void navigate(`/incidents/${id}`);
5056
};
5157

58+
const handleCreateButtonClick = () => {
59+
sendUserActionTrackingEvent(trackingEvents.SIDEBAR_CREATE_BUTTON_CLICKED);
60+
dispatch(setIsCreateIncidentChatOpen(true));
61+
};
62+
5263
const handleTemplateButtonClick = () => {
5364
sendUserActionTrackingEvent(trackingEvents.SIDEBAR_TEMPLATE_BUTTON_CLICKED);
5465
void navigate("/incidents/template");
@@ -111,7 +122,10 @@ export const Sidebar = () => {
111122
/>
112123
</s.LogoLink>
113124
<s.IncidentsListContainer>
114-
<s.IncidentsListTitle>Incidents</s.IncidentsListTitle>
125+
<s.IncidentsListHeader>
126+
<s.IncidentsListTitle>Incidents</s.IncidentsListTitle>
127+
<NewButton label={"Create"} onClick={handleCreateButtonClick} />
128+
</s.IncidentsListHeader>
115129
<s.IncidentsList>
116130
{sortedIncidents.map((incident) => (
117131
<s.IncidentItem

src/components/Agentic/Sidebar/styles.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ export const IncidentsListContainer = styled.div`
5656
flex-grow: 1;
5757
`;
5858

59+
export const IncidentsListHeader = styled.div`
60+
display: flex;
61+
justify-content: space-between;
62+
align-items: center;
63+
`;
64+
5965
export const IncidentsListTitle = styled.div`
6066
${subheading2RegularTypography};
6167
color: ${({ theme }) => theme.colors.v3.text.primary};

0 commit comments

Comments
 (0)