Skip to content

Commit c9d2f22

Browse files
Merge pull request #1163 from digma-ai/fix/ide-launcher
Handle errors in IDE Plugin launcher
2 parents b8c4ea6 + 25981e8 commit c9d2f22

File tree

7 files changed

+227
-88
lines changed

7 files changed

+227
-88
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "digma-ui",
3-
"version": "2.0.0",
3+
"version": "2.1.0",
44
"description": "Digma UI",
55
"main": "dist/index.js",
66
"scripts": {

src/components/IdeLauncher/index.tsx

Lines changed: 164 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1-
import axios from "axios";
2-
import { useEffect, useState } from "react";
1+
import { useCallback, useEffect, useState } from "react";
32
import { Helmet } from "react-helmet";
43
import { useTheme } from "styled-components";
54
import { isString } from "../../typeGuards/isString";
65
import { getThemeKind } from "../common/App/styles";
6+
import { NewButton } from "../common/v3/NewButton";
77
import { Select } from "../common/v3/Select";
88
import { SelectItem } from "../common/v3/Select/types";
9-
import { scanIDEs } from "./scanIDEs";
9+
import { scanRunningIdeProjects } from "./scanRunningIdeProjects";
10+
import { showIdeProject } from "./showIdeProject";
1011
import * as s from "./styles";
12+
import { ShowIdeProjectResult } from "./types";
1113

1214
const SELECT_VALUE_DELIMITER = ":";
1315

16+
const getSelectItemValue = (port: number, project: string) =>
17+
`${port}${SELECT_VALUE_DELIMITER}${project}`;
18+
19+
const parseSelectedItemValue = (value: string) => {
20+
const [port, project] = value.split(SELECT_VALUE_DELIMITER);
21+
return { port: Number(port), project };
22+
};
23+
1424
const getURLQueryParams = (url: string) => {
1525
const searchParams = new URLSearchParams(url);
1626
const params: Record<string, string> = {};
@@ -23,126 +33,198 @@ const getURLQueryParams = (url: string) => {
2333
export const IdeLauncher = () => {
2434
const theme = useTheme();
2535
const themeKind = getThemeKind(theme);
26-
const [isInitialLoading, setInitialLoading] = useState(true);
27-
// const [isOpening, setIsOpening] = useState(false);
28-
const [items, setItems] = useState<SelectItem[]>([]);
29-
const params = getURLQueryParams(window.location.search);
36+
const [selectItems, setSelectItems] = useState<SelectItem[]>();
3037
const isMobile = ["Android", "iPhone", "iPad"].some((x) =>
3138
window.navigator.userAgent.includes(x)
3239
);
40+
const [isIdeProjectScanningInProgress, setIsIdeProjectScanningInProgress] =
41+
useState(false);
42+
const [isShowIdeProjectInProgress, setIsShowIdeProjectInProgress] =
43+
useState(false);
44+
const [showIdeProjectResult, setShowIdeProjectResult] =
45+
useState<ShowIdeProjectResult>();
46+
47+
const tryToShowIdeProject = useCallback(
48+
async (port: number, project: string) => {
49+
setShowIdeProjectResult(undefined);
50+
setIsShowIdeProjectInProgress(true);
51+
const params = getURLQueryParams(window.location.search);
52+
const result = await showIdeProject(port, project, params);
53+
setShowIdeProjectResult(result);
54+
setIsShowIdeProjectInProgress(false);
55+
},
56+
[]
57+
);
58+
59+
const tryToScanRunningIdeProjects = useCallback(async () => {
60+
setSelectItems(undefined);
61+
setIsIdeProjectScanningInProgress(true);
62+
const result = await scanRunningIdeProjects();
63+
setIsIdeProjectScanningInProgress(false);
64+
65+
const projects = result
66+
.filter((x) => x.response.isCentralized)
67+
.flatMap((info) =>
68+
info.response.openProjects.map((project) => ({
69+
...info,
70+
project: project,
71+
port: info.port
72+
}))
73+
);
74+
75+
setSelectItems(
76+
projects.map((x, i) => ({
77+
label: `${x.response.name} (${x.project})`,
78+
description: `${x.response.name} (${x.project})`,
79+
value: getSelectItemValue(x.port, x.project),
80+
enabled: true,
81+
selected: projects.length === 1 && i === 0
82+
}))
83+
);
84+
85+
if (projects.length === 1) {
86+
await tryToShowIdeProject(projects[0].port, projects[0].project);
87+
}
88+
}, [tryToShowIdeProject]);
3389

3490
const handleSelectChange = (value: string | string[]) => {
3591
const selectedValue = isString(value) ? value : value[0];
36-
const [port, project] = selectedValue.split(SELECT_VALUE_DELIMITER);
3792

38-
const pluginParams = Object.entries(params).reduce((acc, [key, value]) => {
39-
const KEY_PREFIX = "plugin.";
40-
if (key.startsWith(KEY_PREFIX)) {
41-
const newKey = key.replace(KEY_PREFIX, "");
42-
acc[newKey] = value;
93+
setSelectItems((prev) => {
94+
if (!prev) {
95+
return prev;
4396
}
4497

45-
return acc;
46-
}, {} as Record<string, string>);
47-
48-
axios
49-
.get(`http://localhost:${port}/api/digma/show`, {
50-
params: { ...pluginParams, projectName: project }
51-
})
52-
.then(() => {
53-
// TODO: handle response
54-
})
55-
.catch(() => {
56-
// TODO: handle error
57-
});
58-
59-
// setIsOpening(true);
60-
61-
const itemToSelect = items.find((item) => item.value === selectedValue);
62-
setItems(
63-
items.map((item) => ({ ...item, selected: item === itemToSelect }))
64-
);
98+
return prev.map((item) => ({
99+
...item,
100+
selected: item.value === selectedValue
101+
}));
102+
});
103+
};
104+
105+
const handleRefreshButtonClick = () => {
106+
window.location.reload();
107+
};
108+
109+
const handleTryAgainButtonClick = async () => {
110+
const selectedItemValue = selectItems?.find((item) => item.selected)?.value;
111+
if (!selectedItemValue) {
112+
return;
113+
}
114+
115+
const { port, project } = parseSelectedItemValue(selectedItemValue);
116+
await tryToShowIdeProject(port, project);
65117
};
66118

67119
useEffect(() => {
68-
async function getIDEsInfo() {
69-
const ideInfo = await scanIDEs();
70-
const projects = ideInfo
71-
.filter((x) => x.response.isCentralized)
72-
.flatMap((info) =>
73-
info.response.openProjects.map((project) => ({
74-
...info,
75-
project: project,
76-
port: info.port
77-
}))
78-
);
79-
80-
setItems(
81-
projects.map((x) => ({
82-
label: `${x.response.name} (${x.project})`,
83-
description: `${x.response.name} (${x.project})`,
84-
value: [x.port, x.project].join(SELECT_VALUE_DELIMITER),
85-
enabled: true,
86-
selected: false
87-
}))
88-
);
89-
setInitialLoading(false);
120+
async function initialScan() {
121+
await tryToScanRunningIdeProjects();
90122
}
91123

92124
if (!isMobile) {
93-
void getIDEsInfo();
125+
void initialScan();
94126
}
95-
}, [isMobile]);
127+
}, [isMobile, tryToScanRunningIdeProjects]);
96128

97-
const selectedItem = items.find((item) => item.selected);
129+
const selectedItem = selectItems?.find((item) => item.selected);
98130

99131
const renderContent = () => {
100132
if (isMobile) {
101133
return (
102134
<s.TextContainer>
103-
<s.Title>Failed to open</s.Title>
135+
<s.Title>Unsupported platform</s.Title>
104136
<s.Description>
105-
This cannot be opened on your mobile device. Please switch to a
106-
desktop with an IDE installed.
137+
The IDE cannot be opened on this device.
107138
</s.Description>
108139
</s.TextContainer>
109140
);
110141
}
111142

112-
return isInitialLoading ? (
113-
<>Scanning running IDEs...</>
114-
) : (
115-
<>
143+
if (isIdeProjectScanningInProgress) {
144+
return (
116145
<s.TextContainer>
117-
<s.Title>Select an IDE to view the Digma issue</s.Title>
118-
<s.Description>
119-
{items.length > 0 ? (
120-
<>
121-
Please select the IDE project you&apos;d like to open all
122-
endpoints in the {params.environment ?? "selected"} environment.
123-
</>
124-
) : (
125-
<>No IDEs with enabled Digma plugin found</>
126-
)}
127-
</s.Description>
146+
<s.Title>Looking for running IDEs...</s.Title>
128147
</s.TextContainer>
129-
{items.length > 0 && (
148+
);
149+
}
150+
151+
if (isShowIdeProjectInProgress) {
152+
return (
153+
<s.TextContainer>
154+
<s.Title>
155+
Opening the {selectedItem?.label ?? "IDE project"}...
156+
</s.Title>
157+
</s.TextContainer>
158+
);
159+
}
160+
161+
if (showIdeProjectResult?.error) {
162+
return (
163+
<>
164+
<s.TextContainer>
165+
<s.Title>
166+
Failed to open the {selectedItem?.label ?? "IDE project"}
167+
</s.Title>
168+
<s.Description>
169+
Please check that IDE is running and click the{" "}
170+
<s.ButtonName>Try again</s.ButtonName> button below.
171+
</s.Description>
172+
</s.TextContainer>
173+
<NewButton
174+
label={"Try again"}
175+
onClick={() => {
176+
void handleTryAgainButtonClick();
177+
}}
178+
/>
179+
</>
180+
);
181+
}
182+
183+
if (!selectItems) {
184+
return null;
185+
}
186+
187+
if (selectItems.length === 0) {
188+
return (
189+
<>
190+
<s.TextContainer>
191+
<s.Title>Failed to find IDEs with Digma plugin running</s.Title>
192+
<s.Description>
193+
Please open the IDE with Digma plugin installed, check its
194+
settings and click the <s.ButtonName>Refresh</s.ButtonName> button
195+
below.
196+
</s.Description>
197+
</s.TextContainer>
198+
<NewButton label={"Refresh"} onClick={handleRefreshButtonClick} />
199+
</>
200+
);
201+
}
202+
203+
if (selectItems.length > 0) {
204+
return (
205+
<>
206+
<s.TextContainer>
207+
<s.Title>Select the IDE project to view issues in Digma</s.Title>
208+
<s.Description>
209+
Please select the IDE project you&apos;d like to open.
210+
</s.Description>
211+
</s.TextContainer>
130212
<s.SelectContainer>
131213
<Select
132214
placeholder={selectedItem?.label ?? "Select IDE Project"}
133-
items={items}
215+
items={selectItems}
134216
onChange={handleSelectChange}
135217
/>
136218
</s.SelectContainer>
137-
)}
138-
</>
139-
);
219+
</>
220+
);
221+
}
140222
};
141223

142224
return (
143225
<s.Container>
144226
<Helmet>
145-
<title>IDE Launcher</title>
227+
<title>Digma IDE Plugin Launcher</title>
146228
</Helmet>
147229
<s.Header>
148230
<a
@@ -153,7 +235,6 @@ export const IdeLauncher = () => {
153235
<s.Logo src={`/images/digmaLogo_${themeKind}.svg`} />
154236
</a>
155237
</s.Header>
156-
157238
<s.Content>{renderContent()}</s.Content>
158239
<s.Footer>
159240
<span>&copy; {new Date().getFullYear()}</span>

src/components/IdeLauncher/scanIDEs.ts renamed to src/components/IdeLauncher/scanRunningIdeProjects.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import axios from "axios";
22
import { isString } from "../../typeGuards/isString";
3-
import { PluginInfo } from "./types";
3+
import { IdeScanningResult, PluginInfo } from "./types";
44

55
const DEFAULT_PORT = 63342;
66
const PORT_RANGE = 20;
77
const ABOUT_PATH = "api/digma/about";
88

9-
export const scanIDEs = async () => {
9+
export const scanRunningIdeProjects = async (): Promise<IdeScanningResult> => {
1010
const instances = Array.from(
1111
{ length: PORT_RANGE },
1212
(_, i) => DEFAULT_PORT + i
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import axios, { isAxiosError } from "axios";
2+
import { ShowIdeProjectResult } from "./types";
3+
4+
export const showIdeProject = async (
5+
port: number,
6+
project: string,
7+
params: Record<string, string>
8+
): Promise<ShowIdeProjectResult> => {
9+
const pluginParams = Object.entries(params).reduce((acc, [key, value]) => {
10+
const KEY_PREFIX = "plugin.";
11+
if (key.startsWith(KEY_PREFIX)) {
12+
const newKey = key.replace(KEY_PREFIX, "");
13+
acc[newKey] = value;
14+
}
15+
16+
return acc;
17+
}, {} as Record<string, string>);
18+
19+
try {
20+
await axios.get(`http://localhost:${port}/api/digma/show`, {
21+
params: { ...pluginParams, projectName: project }
22+
});
23+
return { result: "success" };
24+
} catch (error) {
25+
return {
26+
result: "failure",
27+
error: {
28+
message: isAxiosError(error)
29+
? error.message
30+
: "Failed to open IDE project"
31+
}
32+
};
33+
}
34+
};

0 commit comments

Comments
 (0)