-
Notifications
You must be signed in to change notification settings - Fork 699
fix(api): resolve infinite loading on trace/service fetch errors #3079 #3590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3be9f40
14480b9
8dbc5b3
b279ad7
58a2a6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,20 +14,37 @@ import { ServicesResponseSchema, OperationsResponseSchema } from './schemas'; | |
| export class JaegerClient { | ||
| private apiRoot = prefixUrl('/api/v3'); | ||
|
|
||
| /** | ||
| * Helper to handle Jaeger v3 specific error arrays in 200 OK responses. | ||
| * Throws an Error when the response body contains a non-empty `errors` array, | ||
| * surfacing the raw concatenated error messages so public methods can wrap them with context. | ||
| */ | ||
| private throwIfBodyHasErrors(data: any): void { | ||
| if (data?.errors && Array.isArray(data.errors) && data.errors.length > 0) { | ||
| const messages = data.errors.map((e: { msg: string }) => e.msg).join(', '); | ||
| throw new Error(messages); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Fetch the list of services from the Jaeger API. | ||
| * @returns Promise<string[]> - Array of service names | ||
| */ | ||
| async fetchServices(): Promise<string[]> { | ||
| const response = await this.fetchWithTimeout(`${this.apiRoot}/services`); | ||
| if (!response.ok) { | ||
| throw new Error(`Failed to fetch services: ${response.status} ${response.statusText}`); | ||
| } | ||
| const data = await response.json(); | ||
| const context = 'Failed to fetch services'; | ||
| try { | ||
| const response = await this.fetchWithTimeout(`${this.apiRoot}/services`); | ||
| const data = await response.json(); | ||
|
|
||
| this.throwIfBodyHasErrors(data); | ||
|
|
||
| // Runtime validation with Zod | ||
| const validated = ServicesResponseSchema.parse(data); | ||
| return validated.services; | ||
| const validated = ServicesResponseSchema.parse(data); | ||
|
Comment on lines
+37
to
+41
|
||
| return validated.services; | ||
| } catch (error) { | ||
| // Copilot Fix: Preserve original error as cause and avoid undefined messages | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| throw new Error(`${context}: ${message}`); | ||
| } | ||
|
Comment on lines
+43
to
+47
|
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -36,19 +53,21 @@ export class JaegerClient { | |
| * @returns Promise<{ name: string; spanKind: string }[]> - Array of span name objects | ||
| */ | ||
| async fetchSpanNames(service: string): Promise<{ name: string; spanKind: string }[]> { | ||
| const response = await this.fetchWithTimeout( | ||
| `${this.apiRoot}/operations?service=${encodeURIComponent(service)}` | ||
| ); | ||
| if (!response.ok) { | ||
| throw new Error( | ||
| `Failed to fetch span names for service "${service}": ${response.status} ${response.statusText}` | ||
| const context = `Failed to fetch span names for service "${service}"`; | ||
| try { | ||
| const response = await this.fetchWithTimeout( | ||
| `${this.apiRoot}/operations?service=${encodeURIComponent(service)}` | ||
| ); | ||
| } | ||
| const data = await response.json(); | ||
| const data = await response.json(); | ||
|
|
||
| this.throwIfBodyHasErrors(data); | ||
|
|
||
|
Comment on lines
17
to
64
|
||
| // Runtime validation with Zod | ||
| const validated = OperationsResponseSchema.parse(data); | ||
| return validated.operations; | ||
| const validated = OperationsResponseSchema.parse(data); | ||
| return validated.operations; | ||
| } catch (error) { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| throw new Error(`${context}: ${message}`); | ||
| } | ||
|
Comment on lines
+67
to
+70
|
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -64,6 +83,22 @@ export class JaegerClient { | |
|
|
||
| try { | ||
| const response = await fetch(url, { signal: controller.signal }); | ||
|
|
||
| if (!response.ok) { | ||
| let errorDetail = response.statusText; | ||
| try { | ||
| const errorBody: any = await response.json(); | ||
| if (Array.isArray(errorBody?.errors) && errorBody.errors.length > 0) { | ||
| errorDetail = errorBody.errors.map((e: { msg: string }) => e.msg).join(', '); | ||
| } | ||
| } catch { | ||
| /* Fallback to statusText if body isn't JSON */ | ||
| } | ||
|
|
||
| // Copilot Fix: Richer error including URL as requested | ||
| throw new Error(`${response.status} (${url}) ${errorDetail}`); | ||
| } | ||
|
Comment on lines
+87
to
+100
|
||
|
|
||
| return response; | ||
| } catch (error) { | ||
| if (error instanceof Error && error.name === 'AbortError') { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fetchWithTimeout()now throws on non-2xx responses, butfetchServices()no longer wraps those errors. As a result, callers/UI will likely show a generic message like "404 Not Found" instead of a contextual "Failed to fetch services: ...". Consider catching errors fromfetchWithTimeout()here and rethrowing with endpoint context (preserving the original error message as the cause).