{
+export const fetchTaskData = async (taskId, rootUrl) => {
let testGroups = [];
let taskQueueId = null;
if (!taskId || !rootUrl) {
- return { testGroups, taskQueueId };
+ return { testGroups, taskQueueId, taskExpired: false };
}
const queue = new Queue({ rootUrl });
- const taskDefinition = await queue.task(taskId);
+ let taskDefinition;
+ try {
+ taskDefinition = await queue.task(taskId);
+ } catch (error) {
+ // Task definition may be unavailable (e.g. expired in Taskcluster).
+ // Fall back to defaults so the rest of the details panel can still render,
+ // and flag the task as expired so the UI can communicate the degraded state.
+ // eslint-disable-next-line no-console
+ console.error('Error fetching Taskcluster task definition:', error);
+ return { testGroups, taskQueueId, taskExpired: true };
+ }
if (taskDefinition) {
taskQueueId = taskDefinition.taskQueueId;
if (taskDefinition.payload.env?.MOZHARNESS_TEST_PATHS) {
@@ -45,7 +55,7 @@ const fetchTaskData = async (taskId, rootUrl) => {
}
}
- return { testGroups, taskQueueId };
+ return { testGroups, taskQueueId, taskExpired: false };
};
const fetchClassifications = async (jobId, signal) => {
@@ -132,6 +142,7 @@ function useJobDetails(selectedJob, currentRepo, pushList, frameworks) {
const [classifications, setClassifications] = useState([]);
const [testGroups, setTestGroups] = useState([]);
const [bugs, setBugs] = useState([]);
+ const [taskExpired, setTaskExpired] = useState(false);
// Refs for cleanup
const abortControllerRef = useRef(null);
@@ -180,6 +191,7 @@ function useJobDetails(selectedJob, currentRepo, pushList, frameworks) {
// If no job is selected, clear the state
if (!selectedJob) {
setSelectedJobFull(null);
+ setTaskExpired(false);
previousJobIdRef.current = null;
isFirstLoadRef.current = true;
return;
@@ -304,6 +316,7 @@ function useJobDetails(selectedJob, currentRepo, pushList, frameworks) {
setLogViewerFullUrl(fullLogUrl);
setJobRevision(push ? push.revision : null);
setTestGroups(taskData.testGroups);
+ setTaskExpired(taskData.taskExpired);
setClassifications(classificationsResult.classifications);
setBugs(classificationsResult.bugs);
@@ -440,6 +453,7 @@ function useJobDetails(selectedJob, currentRepo, pushList, frameworks) {
classifications,
testGroups,
bugs,
+ taskExpired,
};
}
diff --git a/ui/logviewer/ClassicLogViewer.jsx b/ui/logviewer/ClassicLogViewer.jsx
index f3369ae11a7..753e85f316f 100644
--- a/ui/logviewer/ClassicLogViewer.jsx
+++ b/ui/logviewer/ClassicLogViewer.jsx
@@ -25,6 +25,7 @@ const ClassicLogViewer = ({
lineCount,
isLoading,
error,
+ errorStatus,
searchTerm,
setSearchTerm,
matchLineNumbers,
@@ -198,6 +199,13 @@ const ClassicLogViewer = ({
);
if (error) {
+ if (errorStatus === 404) {
+ return (
+
+ This log has expired and is no longer available.
+
+ );
+ }
return Error loading log: {error}
;
}
diff --git a/ui/logviewer/useLogViewer.js b/ui/logviewer/useLogViewer.js
index ae0d310d467..31b114a0ff2 100644
--- a/ui/logviewer/useLogViewer.js
+++ b/ui/logviewer/useLogViewer.js
@@ -20,6 +20,7 @@ export function useLogViewer({
const [lines, setLines] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
+ const [errorStatus, setErrorStatus] = useState(null);
const [searchTerm, setSearchTermState] = useState('');
const [matchLineNumbers, setMatchLineNumbers] = useState([]);
@@ -45,19 +46,23 @@ export function useLogViewer({
setLines([]);
setIsLoading(false);
setError(null);
+ setErrorStatus(null);
return;
}
let cancelled = false;
setIsLoading(true);
setError(null);
+ setErrorStatus(null);
fetch(url)
.then((response) => {
if (!response.ok) {
- throw new Error(
+ const err = new Error(
`Failed to fetch log: ${response.status} ${response.statusText}`,
);
+ err.status = response.status;
+ throw err;
}
return response.text();
})
@@ -70,6 +75,7 @@ export function useLogViewer({
.catch((err) => {
if (cancelled) return;
setError(err.message);
+ setErrorStatus(err.status ?? null);
setLines([]);
setIsLoading(false);
});
@@ -271,6 +277,7 @@ export function useLogViewer({
lineCount,
isLoading,
error,
+ errorStatus,
// Search
searchTerm,
setSearchTerm,
@@ -301,6 +308,7 @@ export function useLogViewer({
lineCount,
isLoading,
error,
+ errorStatus,
searchTerm,
setSearchTerm,
matchLineNumbers,