Skip to content

Commit d35524c

Browse files
committed
Add Incident details page to IDE launcher
1 parent d66e91d commit d35524c

File tree

22 files changed

+824
-312
lines changed

22 files changed

+824
-312
lines changed

src/components/Admin/common/RepositorySidebarOverlay/RepositorySidebar/Header/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export const Header = ({
100100
onGoHome={handleGoHome}
101101
/>
102102
<ScopeBar
103-
isExpanded={false}
103+
isExpanded={isSpanInfoVisible}
104104
isSpanInfoEnabled={isSpanInfoEnabled}
105105
linkedEndpoints={linkedEndpoints}
106106
scope={scope}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import axios, { isAxiosError } from "axios";
2+
import type { AddChatContextFileResult } from "../types";
3+
4+
export const addChatContextFile = async (
5+
port: number,
6+
file: {
7+
name: string;
8+
content: string;
9+
}
10+
): Promise<AddChatContextFileResult> => {
11+
try {
12+
await axios.post(
13+
`http://localhost:${port}/api/digma/chat/context/file`,
14+
file
15+
);
16+
return { result: "success" };
17+
} catch (error) {
18+
return {
19+
result: "failure",
20+
error: {
21+
message: isAxiosError(error)
22+
? error.message
23+
: "Failed to add file to the chat context"
24+
}
25+
};
26+
}
27+
};
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
import { useCallback, useEffect, useState } from "react";
2+
import { useParams } from "react-router";
3+
import { useGetIncidentQuery } from "../../../redux/services/digma";
4+
import type { GetIncidentResponse } from "../../../redux/services/types";
5+
import { isString } from "../../../typeGuards/isString";
6+
import { sendTrackingEvent } from "../../../utils/actions/sendTrackingEvent";
7+
import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
8+
import {
9+
Subtitle,
10+
TextContainer,
11+
Title
12+
} from "../../common/GenericPageLayout/styles";
13+
import { NewButton } from "../../common/v3/NewButton";
14+
import type { SelectItem } from "../../common/v3/Select/types";
15+
import { IdeProjectSelect } from "../common/IdeProjectSelect";
16+
import { getSelectItemValue } from "../common/IdeProjectSelect/utils/getSelectedItemValue";
17+
import { parseSelectedItemValue } from "../common/IdeProjectSelect/utils/parseSelectedItemValue";
18+
import { scanRunningVSCodeIdeProjects } from "../scanRunningVSCodeIdeProjects";
19+
import { ButtonsContainer, EmphasizedText } from "../styles";
20+
import { trackingEvents } from "../tracking";
21+
import type { AddChatContextFileResult } from "../types";
22+
import { addChatContextFile } from "./addChatContextFile";
23+
24+
const REFRESH_INTERVAL = 10 * 1000; // in milliseconds
25+
26+
export const IncidentDetails = () => {
27+
const params = useParams();
28+
const incidentId = params.id;
29+
const [isIncidentNotFound, setIsIncidentNotFound] = useState(false);
30+
const [selectItems, setSelectItems] = useState<SelectItem[]>();
31+
const [isIdeProjectScanningInProgress, setIsIdeProjectScanningInProgress] =
32+
useState(false);
33+
const [
34+
isAddingChatContextFileInProgress,
35+
setAddingChatContextFileInProgress
36+
] = useState(false);
37+
const [addChatContextFileResult, setAddChatContextFileResult] =
38+
useState<AddChatContextFileResult>();
39+
40+
const {
41+
data: incidentData,
42+
isLoading,
43+
error
44+
} = useGetIncidentQuery(
45+
{ id: incidentId ?? "" },
46+
{
47+
pollingInterval: isIncidentNotFound ? 0 : REFRESH_INTERVAL,
48+
skip: !incidentId
49+
}
50+
);
51+
52+
const tryToAddChatContextFile = useCallback(
53+
async (
54+
port: number,
55+
incidentId: string,
56+
incidentData: GetIncidentResponse
57+
) => {
58+
setAddChatContextFileResult(undefined);
59+
setAddingChatContextFileInProgress(true);
60+
const result = await addChatContextFile(port, {
61+
name: `incident-${incidentId}.json`,
62+
content: JSON.stringify(incidentData, null, 2)
63+
});
64+
sendTrackingEvent(trackingEvents.IDE_CHAT_CONTEXT_FILE_RESULT_RECEIVED, {
65+
result
66+
});
67+
setAddChatContextFileResult(result);
68+
setAddingChatContextFileInProgress(false);
69+
},
70+
[]
71+
);
72+
73+
const tryToScanRunningIdeProjects = useCallback(async () => {
74+
setSelectItems(undefined);
75+
setIsIdeProjectScanningInProgress(true);
76+
const result = await scanRunningVSCodeIdeProjects();
77+
setIsIdeProjectScanningInProgress(false);
78+
79+
setSelectItems(
80+
result.map((x, i) => ({
81+
label: `${x.response.ideName} (${x.response.workspace})`,
82+
description: `${x.response.ideName} (${x.response.workspace})`,
83+
value: getSelectItemValue(x.port, x.response.workspace),
84+
enabled: true,
85+
selected: result.length === 1 && i === 0
86+
}))
87+
);
88+
}, []);
89+
90+
useEffect(() => {
91+
if (selectItems && selectItems.length === 1 && incidentId && incidentData) {
92+
void tryToAddChatContextFile(
93+
parseSelectedItemValue(selectItems[0].value).port,
94+
incidentId,
95+
incidentData
96+
);
97+
}
98+
}, [incidentId, incidentData, tryToAddChatContextFile, selectItems]);
99+
100+
const handleSelectChange = async (value: string | string[]) => {
101+
sendUserActionTrackingEvent(trackingEvents.IDE_PROJECT_SELECTED);
102+
const selectedValue = isString(value) ? value : value[0];
103+
const { port } = parseSelectedItemValue(selectedValue);
104+
105+
if (!selectItems) {
106+
return;
107+
}
108+
109+
setSelectItems(
110+
selectItems.map((item) => ({
111+
...item,
112+
selected: item.value === selectedValue
113+
}))
114+
);
115+
116+
if (!incidentId || !incidentData) {
117+
return;
118+
}
119+
120+
await tryToAddChatContextFile(port, incidentId, incidentData);
121+
};
122+
123+
const handleTryScanningAgainButtonClick = () => {
124+
sendUserActionTrackingEvent(
125+
trackingEvents.TRY_SCANNING_AGAIN_BUTTON_CLICKED
126+
);
127+
window.location.reload();
128+
};
129+
130+
const handleTryShowIdeProjectAgainButtonClick = async () => {
131+
sendUserActionTrackingEvent(trackingEvents.TRY_AGAIN_BUTTON_CLICKED);
132+
const selectedItemValue = selectItems?.find((item) => item.selected)?.value;
133+
if (!selectedItemValue) {
134+
return;
135+
}
136+
137+
if (!incidentId || !incidentData) {
138+
return;
139+
}
140+
141+
const { port } = parseSelectedItemValue(selectedItemValue);
142+
await tryToAddChatContextFile(port, incidentId, incidentData);
143+
};
144+
145+
// const handleGetDigmaButtonClick = () => {
146+
// sendUserActionTrackingEvent(trackingEvents.GET_DIGMA_BUTTON_CLICKED);
147+
// window.open(
148+
// JETBRAINS_MARKETPLACE_PLUGIN_URL,
149+
// "_blank",
150+
// "noopener noreferrer"
151+
// );
152+
// };
153+
154+
useEffect(() => {
155+
async function initialScan() {
156+
await tryToScanRunningIdeProjects();
157+
}
158+
159+
void initialScan();
160+
}, [tryToScanRunningIdeProjects]);
161+
162+
useEffect(() => {
163+
setIsIncidentNotFound(false);
164+
}, [incidentId]);
165+
166+
useEffect(() => {
167+
if (error && "status" in error && error.status === 404) {
168+
setIsIncidentNotFound(true);
169+
}
170+
}, [error]);
171+
172+
const renderContent = () => {
173+
if (!incidentId) {
174+
return (
175+
<TextContainer>
176+
<Title>Incident ID is not provided</Title>
177+
</TextContainer>
178+
);
179+
}
180+
181+
if (!incidentData && isLoading) {
182+
return (
183+
<TextContainer>
184+
<Title>Getting incident details</Title>
185+
</TextContainer>
186+
);
187+
}
188+
189+
if (!incidentData && error) {
190+
return (
191+
<TextContainer>
192+
<Title>Failed to get incident details</Title>
193+
</TextContainer>
194+
);
195+
}
196+
197+
if (isIdeProjectScanningInProgress) {
198+
return (
199+
<TextContainer>
200+
<Title>Searching for a running IDE</Title>
201+
<Subtitle>
202+
You&apos;ll need an IDE installed with Digma configured to open the
203+
link
204+
</Subtitle>
205+
</TextContainer>
206+
);
207+
}
208+
209+
if (isAddingChatContextFileInProgress) {
210+
return (
211+
<TextContainer>
212+
<Title>Adding the incident details to the IDE chat context</Title>
213+
</TextContainer>
214+
);
215+
}
216+
217+
if (addChatContextFileResult?.result === "failure") {
218+
return (
219+
<>
220+
<TextContainer>
221+
<Title>
222+
There was an issue adding the incident details to the IDE chat
223+
context
224+
</Title>
225+
<Subtitle>
226+
Please check that IDE is running and click the{" "}
227+
<EmphasizedText>Try again</EmphasizedText> button below.
228+
</Subtitle>
229+
</TextContainer>
230+
<NewButton
231+
label={"Try again"}
232+
onClick={() => {
233+
void handleTryShowIdeProjectAgainButtonClick();
234+
}}
235+
/>
236+
</>
237+
);
238+
}
239+
240+
if (addChatContextFileResult?.result === "success") {
241+
return (
242+
<TextContainer>
243+
<Title>
244+
Incident details have been added to the IDE chat context
245+
</Title>
246+
<Subtitle>You can close this tab.</Subtitle>
247+
</TextContainer>
248+
);
249+
}
250+
251+
if (!selectItems) {
252+
return null;
253+
}
254+
255+
if (selectItems.length === 0) {
256+
return (
257+
<>
258+
<TextContainer>
259+
<Title>Unable to open the Digma link</Title>
260+
<Subtitle>
261+
Opening this link requires a running IDE with Digma installed and
262+
configured. Launch your IDE and install Digma as needed, then
263+
click the <EmphasizedText>Try again</EmphasizedText> button.
264+
</Subtitle>
265+
</TextContainer>
266+
<ButtonsContainer>
267+
<NewButton
268+
label={"Try again"}
269+
onClick={handleTryScanningAgainButtonClick}
270+
buttonType={"secondary"}
271+
/>
272+
{/* <NewButton
273+
label={"Get Digma"}
274+
onClick={handleGetDigmaButtonClick}
275+
/> */}
276+
</ButtonsContainer>
277+
</>
278+
);
279+
}
280+
281+
// TODO: remove equal check when we have only one IDE project
282+
if (selectItems.length >= 1) {
283+
return (
284+
<>
285+
<TextContainer>
286+
<Title>
287+
Select the IDE project to add the incident details to the chat
288+
context
289+
</Title>
290+
</TextContainer>
291+
<IdeProjectSelect
292+
items={selectItems}
293+
onChange={(value) => void handleSelectChange(value)}
294+
/>
295+
</>
296+
);
297+
}
298+
};
299+
300+
return <>{renderContent()}</>;
301+
};

0 commit comments

Comments
 (0)