Skip to content

Commit 11d37bb

Browse files
authored
More Detailed Error Message on Failed Cancel Runs (#1180)
## Description <!-- Please provide a brief description of the changes made in this pull request. Include any relevant context or reasoning for the changes. --> If the backend provides a useful error message when cancelling runs the frontend will display it. Otherwise will display "failed to fetch". Also updates the current error handling on fetch or actually show the error message if one is provided. Merging the [backend PR](TangleML/tangle#29) will provide a more useful error message via the API. We can follow up and improve when we have a better error handling system. ## Related Issue and Pull requests <!-- Link to any related issues using the format #<issue-number> --> Will help address user confusion that relates to Shopify/oasis-frontend#274 ## Type of Change <!-- Please delete options that are not relevant --> - [x] Improvement ## Checklist <!-- Please ensure the following are completed before submitting the PR --> - [ ] I have tested this does not break current pipelines / runs functionality - [ ] I have tested the changes on staging ## Screenshots (if applicable) <!-- Include any screenshots that might help explain the changes or provide visual context --> ![image.png](https://app.graphite.dev/user-attachments/assets/0556186f-17d5-4f17-9e13-64c5bac7d38f.png) (note: I cut off some of the fluff from this message after making the screenshot but it's a lengthy process to set up an environment just to get a new screenshot) ## Test Instructions <!-- Detail steps and prerequisites for testing the changes in this PR --> The best way to test this is with two users on staging: - user 1 starts a run - user 2 tries to cancel it Alternatively, this can be done in local dev by emulating two different users by changing the `DEV_USER` const in `app.py` as needed. Note the current backend will only throw an internal exception; resulting in a `failed to fetch` on the front end. For a detailed error message [this backend PR](TangleML/tangle#29) needs to be shipped first. ## Additional Comments <!-- Add any additional context or information that reviewers might need to know regarding this PR -->
1 parent 4b66bce commit 11d37bb

File tree

2 files changed

+32
-16
lines changed

2 files changed

+32
-16
lines changed

src/services/pipelineRunService.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
PIPELINE_RUNS_STORE_NAME,
1515
USER_PIPELINES_LIST_NAME,
1616
} from "@/utils/constants";
17+
import { fetchWithErrorHandling } from "@/utils/fetchWithErrorHandling";
1718

1819
export const createPipelineRun = async (
1920
payload: BodyCreateApiPipelineRunsPost,
@@ -187,16 +188,10 @@ export const fetchPipelineRunById = async (runId: string) => {
187188
};
188189

189190
export const cancelPipelineRun = async (runId: string, backendUrl: string) => {
190-
const response = await fetch(
191+
await fetchWithErrorHandling(
191192
`${backendUrl}/api/pipeline_runs/${runId}/cancel`,
192193
{
193194
method: "POST",
194195
},
195196
);
196-
197-
if (!response.ok) {
198-
throw new Error("Failed to cancel pipeline run");
199-
}
200-
201-
// endpoint returns nothing
202197
};

src/utils/fetchWithErrorHandling.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,44 @@ export const fetchWithErrorHandling = async (
1313
}
1414

1515
if (!response.ok) {
16-
let errorBody = "";
16+
let message = "No error details";
1717
try {
18-
errorBody = await response.text();
18+
let errorBody: any;
19+
if (response.headers.get("content-type")?.includes("application/json")) {
20+
errorBody = await response.json();
21+
} else {
22+
errorBody = await response.text();
23+
}
24+
message = errorBody.message;
1925
} catch {
2026
// Ignore if we can't read the error body
2127
}
2228

29+
if (response.status === 403) {
30+
throw new Error(message || "Access denied (403)");
31+
}
32+
2333
throw new Error(
24-
`HTTP ${response.status} ${response.statusText}: ${errorBody || "No error details"} (URL: ${url})`,
34+
`HTTP ${response.status} ${response.statusText}: ${message} (URL: ${url})`,
2535
);
2636
}
2737

28-
try {
29-
return await response.json();
30-
} catch (parseError) {
31-
const message =
32-
parseError instanceof Error ? parseError.message : String(parseError);
33-
throw new Error(`Invalid JSON response: ${message} (URL: ${url})`);
38+
const contentType = response.headers.get("content-type");
39+
const contentLength = response.headers.get("content-length");
40+
41+
if (!contentType || contentLength === "0") {
42+
return response;
3443
}
44+
45+
if (contentType?.includes("application/json")) {
46+
try {
47+
return await response.json();
48+
} catch (parseError) {
49+
const message =
50+
parseError instanceof Error ? parseError.message : String(parseError);
51+
throw new Error(`Invalid JSON response: ${message} (URL: ${url})`);
52+
}
53+
}
54+
55+
return response;
3556
};

0 commit comments

Comments
 (0)