diff --git a/app/components/analysis/AnalysisControlButtons.vue b/app/components/analysis/AnalysisControlButtons.vue index 01eb9bf..3d8540a 100644 --- a/app/components/analysis/AnalysisControlButtons.vue +++ b/app/components/analysis/AnalysisControlButtons.vue @@ -6,7 +6,7 @@ import { ProcessStatus } from "~/types/analysis"; import { type PodProgressResponse, PodStatus, - type StatusOnlyResponse, + type StatusOnlyResponse } from "~/services/Api"; type ToastSeverity = "success" | "info" | "warn" | "error" | undefined; @@ -22,41 +22,73 @@ const props = defineProps({ analysisBuildStatus: [String, null], analysisExecutionStatus: { type: [String, null], - required: true, + required: true }, analysisDistributionStatus: { type: [String, null], - required: true, + required: true }, analysisNodeId: { type: String, - required: true, + required: true }, analysisId: { type: String, - required: true, + required: true }, projectId: { type: String, - required: true, + required: true }, nodeId: { type: String, - required: true, + required: true }, datastore: { type: Boolean, - required: true, + required: true }, requireDatastore: { type: Boolean, - required: true, - }, + required: true + } }); const emit = defineEmits(["updateAnalysisRow", "missingDataStore"]); const toast = useToast(); const loading = ref(false); +// Needed in order to distinguish in-flight request from stored +const loadingRestoredFromStorage = ref(false); + +const loadingStorageKey = computed( + () => `analysis-start-loading-${props.analysisId}` +); + +onMounted(() => { + if ( + localStorage.getItem(loadingStorageKey.value) === "true" && + playButtonActiveStates.includes(props.analysisExecutionStatus) + ) { + loading.value = true; + loadingRestoredFromStorage.value = true; + } else { + localStorage.removeItem(loadingStorageKey.value); + } +}); + +watch( + () => props.analysisExecutionStatus, + (newStatus) => { + if ( + loadingRestoredFromStorage.value && + !playButtonActiveStates.includes(newStatus) + ) { + localStorage.removeItem(loadingStorageKey.value); + loading.value = false; + loadingRestoredFromStorage.value = false; + } + } +); // API Constants const NOT_FOUND_STATUS = 404; @@ -68,14 +100,14 @@ const rerunButtonActiveStates: Array = [ PodStatus.Executed, PodStatus.Finished, // Deprecated PodStatus.Stopped, - PodStatus.Stopping, + PodStatus.Stopping ]; const stopButtonActiveStates: Array = [ PodStatus.Executing, PodStatus.Running, // Deprecated PodStatus.Starting, PodStatus.Started, - PodStatus.Stopping, + PodStatus.Stopping ]; const deleteButtonActiveStates: Array = [ PodStatus.Failed, @@ -84,7 +116,7 @@ const deleteButtonActiveStates: Array = [ PodStatus.Executing, PodStatus.Running, // Deprecated PodStatus.Starting, - PodStatus.Started, + PodStatus.Started ]; function getButtonStatuses(podStatus: string | null | undefined) { @@ -92,21 +124,21 @@ function getButtonStatuses(podStatus: string | null | undefined) { playActive: playButtonActiveStates.includes(podStatus), rerunActive: rerunButtonActiveStates.includes(podStatus), stopActive: stopButtonActiveStates.includes(podStatus), - deleteActive: deleteButtonActiveStates.includes(podStatus), + deleteActive: deleteButtonActiveStates.includes(podStatus) }; } const buttonStatuses = computed( - () => getButtonStatuses(props.analysisExecutionStatus), // Changes buttons when new status update comes in + () => getButtonStatuses(props.analysisExecutionStatus) // Changes buttons when new status update comes in ); function updatePodStatus( podStatus: string | null | undefined, - progressUpdate?: number | null | undefined, + progressUpdate?: number | null | undefined ) { const analysisUpdate = { status: podStatus, - progress: progressUpdate, + progress: progressUpdate }; emit("updateAnalysisRow", props.analysisId, analysisUpdate); } @@ -116,7 +148,7 @@ const showToast = (severity: ToastSeverity, summary: string, msg: string) => { severity: severity, summary: summary, detail: msg, - life: 5000, + life: 5000 }); }; @@ -124,14 +156,14 @@ const showStatusUnknownToast = () => { showToast( "warn", "Status unknown", - "Pod was not found, but the stop command was still issued", + "Pod was not found, but the stop command was still issued" ); }; async function checkPodStatus(): Promise { const podStatus: PodProgressResponse = (await useNuxtApp() .$hubApi(`/po/status/${props.analysisId}`, { - method: "GET", + method: "GET" }) .catch(() => null)) as PodProgressResponse; // Set the response to undefined if an error occurs @@ -144,7 +176,7 @@ async function checkPodStatus(): Promise { showToast( "warn", "Analysis already running", - "The analysis is already running on this node, the controls have been updated", + "The analysis is already running on this node, the controls have been updated" ); updatePodStatus(currentPodStatus, currentPodProgress); // Grab the first status update return true; @@ -160,6 +192,8 @@ async function onRerunAnalysis() { async function onStartAnalysis() { loading.value = true; + loadingRestoredFromStorage.value = false; + localStorage.setItem(loadingStorageKey.value, "true"); const originalExecutionStatus = props.analysisExecutionStatus; updatePodStatus(PodStatus.Starting); let analysisProps = new FormData(); @@ -170,7 +204,7 @@ async function onStartAnalysis() { let startPodResp: StatusOnlyResponse = (await useNuxtApp() .$hubApi("/analysis/initialize", { method: "POST", - body: analysisProps, + body: analysisProps }) .catch((e) => { updatePodStatus(null); @@ -181,7 +215,7 @@ async function onStartAnalysis() { "info", "Analysis submitted", "The PodOrc did not respond in time, but the request was sent to start the analysis. " + - "The timeout was likely due to a large image being pulled.", + "The timeout was likely due to a large image being pulled." ); } else if (e.status === NOT_FOUND_STATUS) { emit("missingDataStore"); @@ -206,6 +240,7 @@ async function onStartAnalysis() { updatePodStatus(originalExecutionStatus); } + localStorage.removeItem(loadingStorageKey.value); loading.value = false; } @@ -216,13 +251,13 @@ async function onStopAnalysis() { const stopResp: StatusOnlyResponse = (await useNuxtApp() .$hubApi(`/po/stop/${props.analysisId}`, { - method: "PUT", + method: "PUT" }) .catch(() => { showToast( "error", "Stop failure", - "Failed to stop the analysis container", + "Failed to stop the analysis container" ); })) as StatusOnlyResponse; @@ -232,7 +267,7 @@ async function onStopAnalysis() { showToast( "success", "Stop success", - "Successfully stopped the container", + "Successfully stopped the container" ); } else { // No pod statuses returned from PO for analysis @@ -252,13 +287,13 @@ async function onDeleteAnalysis() { const deleteResp: StatusOnlyResponse = (await useNuxtApp() .$hubApi(`/analysis/terminate/${props.analysisId}`, { - method: "DELETE", + method: "DELETE" }) .catch(() => { showToast( "error", "Terminate request failure", - "Failed to terminate the analysis", + "Failed to terminate the analysis" ); })) as StatusOnlyResponse; @@ -268,7 +303,7 @@ async function onDeleteAnalysis() { showToast( "success", "Delete success", - "Successfully removed the container", + "Successfully removed the container" ); } else { showStatusUnknownToast(); diff --git a/package.json b/package.json index b4627bd..e0762ae 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "eslint": "~9.26.0", "eslint-config-prettier": "^9.1.2", "eslint-plugin-vue": "^9.33.0", - "happy-dom": "^20.0.8", + "happy-dom": "^20.8.9", "msw": "^2.11.6", "typescript-eslint": "^8.46.2", "vite-tsconfig-paths": "^5.1.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e762a08..e0f7b07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,7 +103,7 @@ importers: version: 9.39.2 '@nuxt/test-utils': specifier: ^4.0.0 - version: 4.0.0(@vue/test-utils@2.4.6)(happy-dom@20.6.3)(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.6.3)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + version: 4.0.0(@vue/test-utils@2.4.6)(happy-dom@20.8.9)(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.8.9)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) '@types/eslint-config-prettier': specifier: ^6.11.3 version: 6.11.3 @@ -118,7 +118,7 @@ importers: version: 6.0.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3)) '@vitest/coverage-v8': specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.6.3)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + version: 3.2.4(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.8.9)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) '@vue/test-utils': specifier: ^2.4.6 version: 2.4.6 @@ -132,8 +132,8 @@ importers: specifier: ^9.33.0 version: 9.33.0(eslint@9.26.0(jiti@2.6.1)) happy-dom: - specifier: ^20.0.8 - version: 20.6.3 + specifier: ^20.8.9 + version: 20.8.9 msw: specifier: ^2.11.6 version: 2.12.10(@types/node@25.3.0)(typescript@5.9.3) @@ -145,7 +145,7 @@ importers: version: 5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@25.3.0)(happy-dom@20.6.3)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) + version: 3.2.4(@types/node@25.3.0)(happy-dom@20.8.9)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) packages: @@ -3134,8 +3134,8 @@ packages: crossws: optional: true - happy-dom@20.6.3: - resolution: {integrity: sha512-QAMY7d228dHs8gb9NG4SJ3OxQo4r+NGN8pOXGZ3SGfQf/XYuuYubrtZ25QVY2WoUQdskhRXSXb4R4mcRk+hV1w==} + happy-dom@20.8.9: + resolution: {integrity: sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA==} engines: {node: '>=20.0.0'} has-flag@4.0.0: @@ -6306,7 +6306,7 @@ snapshots: rc9: 3.0.0 std-env: 3.10.0 - '@nuxt/test-utils@4.0.0(@vue/test-utils@2.4.6)(happy-dom@20.6.3)(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.6.3)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))': + '@nuxt/test-utils@4.0.0(@vue/test-utils@2.4.6)(happy-dom@20.8.9)(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.8.9)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))': dependencies: '@clack/prompts': 1.0.0 '@nuxt/devtools-kit': 2.7.0(magicast@0.5.2)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) @@ -6335,12 +6335,12 @@ snapshots: tinyexec: 1.0.2 ufo: 1.6.3 unplugin: 3.0.0 - vitest-environment-nuxt: 1.0.1(@vue/test-utils@2.4.6)(happy-dom@20.6.3)(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.6.3)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + vitest-environment-nuxt: 1.0.1(@vue/test-utils@2.4.6)(happy-dom@20.8.9)(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.8.9)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) vue: 3.5.28(typescript@5.9.3) optionalDependencies: '@vue/test-utils': 2.4.6 - happy-dom: 20.6.3 - vitest: 3.2.4(@types/node@25.3.0)(happy-dom@20.6.3)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) + happy-dom: 20.8.9 + vitest: 3.2.4(@types/node@25.3.0)(happy-dom@20.8.9)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) transitivePeerDependencies: - crossws - magicast @@ -7213,7 +7213,7 @@ snapshots: vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) vue: 3.5.28(typescript@5.9.3) - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.6.3)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.8.9)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -7228,7 +7228,7 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@25.3.0)(happy-dom@20.6.3)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) + vitest: 3.2.4(@types/node@25.3.0)(happy-dom@20.8.9)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -8636,7 +8636,7 @@ snapshots: rou3: 0.7.12 srvx: 0.10.1 - happy-dom@20.6.3: + happy-dom@20.8.9: dependencies: '@types/node': 25.3.0 '@types/whatwg-mimetype': 3.0.2 @@ -10989,9 +10989,9 @@ snapshots: terser: 5.46.0 yaml: 2.8.2 - vitest-environment-nuxt@1.0.1(@vue/test-utils@2.4.6)(happy-dom@20.6.3)(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.6.3)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)): + vitest-environment-nuxt@1.0.1(@vue/test-utils@2.4.6)(happy-dom@20.8.9)(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.8.9)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)): dependencies: - '@nuxt/test-utils': 4.0.0(@vue/test-utils@2.4.6)(happy-dom@20.6.3)(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.6.3)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + '@nuxt/test-utils': 4.0.0(@vue/test-utils@2.4.6)(happy-dom@20.8.9)(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.8.9)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) transitivePeerDependencies: - '@cucumber/cucumber' - '@jest/globals' @@ -11008,7 +11008,7 @@ snapshots: - vite - vitest - vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.6.3)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2): + vitest@3.2.4(@types/node@25.3.0)(happy-dom@20.8.9)(jiti@2.6.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.3.0)(typescript@5.9.3))(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 @@ -11035,7 +11035,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.3.0 - happy-dom: 20.6.3 + happy-dom: 20.8.9 transitivePeerDependencies: - jiti - less