Merge 'main' into 20382-process-action-menu-cancel
[arvados-workbench2.git] / src / store / process-logs-panel / process-logs-panel-actions.ts
index 9da7d1b9110d0f7a24dfc6700229fbf277d4ed22..87a2fa12aaddc1d6d980908583dfda366c0f1ab4 100644 (file)
@@ -13,6 +13,7 @@ import { Process, getProcess } from 'store/processes/process';
 import { navigateTo } from 'store/navigation/navigation-action';
 import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
 import { CollectionFile, CollectionFileType } from "models/collection-file";
+import { ContainerRequestResource } from "models/container-request";
 
 const SNIPLINE = `================ ✀ ================ ✀ ========= Some log(s) were skipped ========= ✀ ================ ✀ ================`;
 const LOG_TIMESTAMP_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{9}Z/;
@@ -44,7 +45,7 @@ export const initProcessLogsPanel = (processUuid: string) =>
             const process = getProcess(processUuid)(getState().resources);
             if (process?.containerRequest?.uuid) {
                 // Get log file size info
-                const logFiles = await loadContainerLogFileList(process.containerRequest.uuid, logService);
+                const logFiles = await loadContainerLogFileList(process.containerRequest, logService);
 
                 // Populate lastbyte 0 for each file
                 const filesWithProgress = logFiles.map((file) => ({file, lastByte: 0}));
@@ -60,7 +61,10 @@ export const initProcessLogsPanel = (processUuid: string) =>
             // On error, populate empty state to allow polling to start
             const initialState = createInitialLogPanelState([], []);
             dispatch(processLogsPanelActions.INIT_PROCESS_LOGS_PANEL(initialState));
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not load process logs', hideDuration: 2000, kind: SnackbarKind.ERROR }));
+            // Only show toast on errors other than 404 since 404 is expected when logs do not exist yet
+            if (e.status !== 404) {
+                dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not load process logs', hideDuration: 2000, kind: SnackbarKind.ERROR }));
+            }
         }
     };
 
@@ -73,7 +77,7 @@ export const pollProcessLogs = (processUuid: string) =>
 
             // Check if container request is present and initial logs state loaded
             if (process?.containerRequest?.uuid && Object.keys(currentState.logs).length > 0) {
-                const logFiles = await loadContainerLogFileList(process.containerRequest.uuid, logService);
+                const logFiles = await loadContainerLogFileList(process.containerRequest, logService);
 
                 // Determine byte to fetch from while filtering unchanged files
                 const filesToUpdateWithProgress = logFiles.reduce((acc, updatedFile) => {
@@ -102,13 +106,13 @@ export const pollProcessLogs = (processUuid: string) =>
             return Promise.resolve();
         } catch (e) {
             // Remove log when polling error is handled in some way instead of being ignored
-            console.log("Polling process logs failed");
+            console.error("Error occurred in pollProcessLogs:", e);
             return Promise.reject();
         }
     };
 
-const loadContainerLogFileList = async (containerUuid: string, logService: LogService) => {
-    const logCollectionContents = await logService.listLogFiles(containerUuid);
+const loadContainerLogFileList = async (containerRequest: ContainerRequestResource, logService: LogService) => {
+    const logCollectionContents = await logService.listLogFiles(containerRequest);
 
     // Filter only root directory files matching log event types which have bytes
     return logCollectionContents.filter((file): file is CollectionFile => (
@@ -134,25 +138,28 @@ const loadContainerLogFileContents = async (logFilesWithProgress: FileWithProgre
             const chunkSize = Math.floor(maxLogFetchSize / 2);
             const firstChunkEnd = lastByte+chunkSize-1;
             return Promise.all([
-                logService.getLogFileContents(process.containerRequest.uuid, file, lastByte, firstChunkEnd),
-                logService.getLogFileContents(process.containerRequest.uuid, file, file.size-chunkSize, file.size-1)
+                logService.getLogFileContents(process.containerRequest, file, lastByte, firstChunkEnd),
+                logService.getLogFileContents(process.containerRequest, file, file.size-chunkSize, file.size-1)
             ] as Promise<(LogFragment)>[]);
         } else {
-            return Promise.all([logService.getLogFileContents(process.containerRequest.uuid, file, lastByte, file.size-1)]);
+            return Promise.all([logService.getLogFileContents(process.containerRequest, file, lastByte, file.size-1)]);
         }
     })).then((res) => {
-        if (res.length && res.every(promise => (promise.status === 'rejected'))) {
+        if (res.length && res.every(promiseResult => (promiseResult.status === 'rejected'))) {
             // Since allSettled does not pass promise rejection we throw an
             //   error if every request failed
-            return Promise.reject("Failed to load logs");
+            const error = res.find(
+                (promiseResult): promiseResult is PromiseRejectedResult => promiseResult.status === 'rejected'
+              )?.reason;
+            return Promise.reject(error);
         }
-        return res.filter((one): one is PromiseFulfilledResult<LogFragment[]> => (
+        return res.filter((promiseResult): promiseResult is PromiseFulfilledResult<LogFragment[]> => (
             // Filter out log files with rejected promises
             //   (Promise.all rejects on any failure)
-            one.status === 'fulfilled' &&
+            promiseResult.status === 'fulfilled' &&
             // Filter out files where any fragment is empty
             //   (prevent incorrect snipline generation or an un-resumable situation)
-            !!one.value.every(logFragment => logFragment.contents.length)
+            !!promiseResult.value.every(logFragment => logFragment.contents.length)
         )).map(one => one.value)
     })).map((logResponseSet)=> {
         // For any multi fragment response set, modify the last line of non-final chunks to include a line break and snip line