From f0e36f69ad474e3c327fc3e6863a90b6d3fd18e4 Mon Sep 17 00:00:00 2001
From: Remi  PLANEL <rplanel@pasteur.fr>
Date: Wed, 20 Mar 2024 09:36:31 +0100
Subject: [PATCH] Error msg when galaxy instance not running

---
 backend/analysis/api.py                      | 26 +++++++++-
 backend/analysis/models.py                   |  6 +--
 deploy/charts/nuxt/templates/deployment.yaml |  4 +-
 frontend/components/AnalysisList.vue         | 50 ++++++++++----------
 frontend/components/ErrorAlert.vue           | 36 ++++++++++++++
 frontend/components/Main.vue                 |  2 -
 frontend/components/SubmitAnalysis.vue       |  8 ++++
 frontend/components/UppyGenomeUpload.vue     |  8 ++--
 frontend/composables/isGalaxyOffline.ts      | 18 +++++++
 frontend/composables/useFetchAnalyses.ts     |  7 +--
 frontend/error.vue                           |  2 +
 frontend/middleware/maintenance.global.ts    |  6 ---
 frontend/pages/analyses/index.vue            | 14 +-----
 frontend/pages/index.vue                     | 13 +----
 frontend/plugins/api.ts                      |  1 +
 frontend/server/api/probe.get.ts             |  3 ++
 16 files changed, 132 insertions(+), 72 deletions(-)
 create mode 100644 frontend/components/ErrorAlert.vue
 create mode 100644 frontend/composables/isGalaxyOffline.ts
 create mode 100644 frontend/server/api/probe.get.ts

diff --git a/backend/analysis/api.py b/backend/analysis/api.py
index a481fde..984063d 100644
--- a/backend/analysis/api.py
+++ b/backend/analysis/api.py
@@ -4,6 +4,9 @@ from django.conf import settings
 
 from ninja import Router, File
 from ninja.files import UploadedFile
+
+from django_to_galaxy.models import GalaxyInstance
+
 from analysis.models import (
     Analysis,
     AnalysisWorkflow,
@@ -38,6 +41,14 @@ def csrf(request):
     return JsonResponse({"token": token})
 
 
+@router.get("is-galaxy-online")
+def is_galaxy_online(request):
+
+    gi = GalaxyInstance.objects.get(name=settings.GALAXY_INSTANCE_NAME)
+
+    return JsonResponse({"is_online": gi.is_online()})
+
+
 @router.get("login")
 def login(request):
     if not request.session.session_key:
@@ -49,7 +60,7 @@ def login(request):
     )
 
 
-@router.post("add", response=AnalysisOutSchema)
+@router.post("add", response={200: AnalysisOutSchema, 503: Error})
 def add(
     request,
     genomefile: UploadedFile = File(...),
@@ -71,6 +82,12 @@ def add(
     session = Session.objects.get(session_key=session_key)
 
     aw = AnalysisWorkflow.objects.get(galaxy_id=settings.GALAXY_WORKFLOW_ID)
+
+    # check if Galaxy online
+
+    is_galaxy_online = aw.analysis_owner.galaxy_instance.is_online()
+    if not is_galaxy_online:
+        return 503, {"message": "The Galaxy instance is offline"}
     input_files = [
         genome_path,
     ]
@@ -80,11 +97,16 @@ def add(
     return analysis
 
 
-@router.get("/", response=list[AnalysisOutSchema])
+@router.get("/", response={200: list[AnalysisOutSchema], 503: Error})
 def list(request):
     session_key = request.session.session_key
     if session_key:
         session = Session.objects.get(session_key=session_key)
+        gi = GalaxyInstance.objects.get(name=settings.GALAXY_INSTANCE_NAME)
+        # check if Galaxy online
+        is_galaxy_online = gi.is_online()
+        if not is_galaxy_online:
+            return 503, {"message": "The Galaxy instance is offline"}
         return Analysis.objects.filter(analysis_history__session=session)
     else:
         return []
diff --git a/backend/analysis/models.py b/backend/analysis/models.py
index 6418f16..3793948 100644
--- a/backend/analysis/models.py
+++ b/backend/analysis/models.py
@@ -2,16 +2,14 @@ import csv
 import tarfile
 from typing import Any
 from django.db import models
-from django_to_galaxy.utils import enabled_cache
 from django.contrib.sessions.models import Session
 from tempfile import mkstemp
 from django.conf import settings
 from Bio import SeqIO
 from pathlib import Path
 from rich.console import Console
-from typing import List, Dict
+from typing import Dict
 
-console = Console()
 
 # from galaxy.models import WorkflowInvocation
 
@@ -31,6 +29,8 @@ from .schemas import GeneEntry, HmmerEntry, ProteinEntry, SystemEntry
 
 # Create your models here.
 
+console = Console()
+
 
 class AnalysisGalaxyUser(GalaxyUser):
     def create_history(self, session, name=None):
diff --git a/deploy/charts/nuxt/templates/deployment.yaml b/deploy/charts/nuxt/templates/deployment.yaml
index ca6d250..38819ab 100644
--- a/deploy/charts/nuxt/templates/deployment.yaml
+++ b/deploy/charts/nuxt/templates/deployment.yaml
@@ -48,11 +48,11 @@ spec:
               value: {{ .Values.dfApiPrefix }}
           livenessProbe:
             httpGet:
-              path: /
+              path: /api/probe
               port: 3000
           readinessProbe:
             httpGet:
-              path: /
+              path: /api/probe
               port: 3000
           resources:
             {{- toYaml .Values.resources | nindent 12 }}
diff --git a/frontend/components/AnalysisList.vue b/frontend/components/AnalysisList.vue
index 8180e81..8ec5158 100644
--- a/frontend/components/AnalysisList.vue
+++ b/frontend/components/AnalysisList.vue
@@ -3,19 +3,13 @@ import { computed } from 'vue'
 import { usePolling } from "../composables/useFetchAnalyses";
 import type { Analysis } from '~/types';
 import { joinURL } from "ufo";
-// import { useIsGalaxyOffline } from '~/composables/isGalaxyOffline';
 const runtimeConfig = useRuntimeConfig()
 const sessionExpiryDate = useState('sessionExpiryDate')
 const toEdit: Ref<number | null> = ref(null)
 const newTitle: Ref<string | null> = ref(null)
-
-
-
 const sanitizedAnalyses = computed(() => {
   return data !== null ? data.value?.map(a => {
-
     const createTimeDate = new Date(a.create_time)
-
     return {
       ...a,
       create_time: createTimeDate.toLocaleString()
@@ -29,13 +23,17 @@ function getResultArchiveUrl(analysisId: number) {
 
 async function deleteAnalysis(analysisId: number) {
   // if (csrfToken.value) {
-  const { error } = await useAPI<null>(`/analysis/${analysisId}`,
-    {
-      method: "DELETE",
-    }
-  )
-  if (error.value) throw createError(`Cannot delete analysis ${analysisId}`)
-  refresh()
+  try {
+    await useNuxtApp().$api<null>(`/analysis/${analysisId}`,
+      {
+        method: "DELETE",
+      }
+    )
+    await refreshNuxtData()
+  } catch (error) {
+    throw createError(`Cannot delete analysis ${analysisId}`)
+  }
+
 }
 
 function editionMode(analysisId: number, name: string) {
@@ -44,14 +42,14 @@ function editionMode(analysisId: number, name: string) {
 }
 async function editAnalysis(analysisId: number) {
   // if (csrfToken.value) {
-  await useAPI<null>(`/analysis/${analysisId}`,
+  await useNuxtApp().$api<null>(`/analysis/${analysisId}`,
     {
       method: "PUT",
       body: {
         name: newTitle.value
       },
       onResponse() {
-        refresh().finally(() => {
+        refreshNuxtData().finally(() => {
           toEdit.value = null
           newTitle.value = null
         })
@@ -76,27 +74,31 @@ const options = computed(() => {
   }
 })
 
-
-
-
 const { data, refresh, pending, error } = await useAPI<Analysis[]>('/analysis/', {
   key: "analyses-list",
 });
 
-
 usePolling(options)
 
-
-
 if (error.value) {
+  console.log(error.value)
   const toValError = toValue(error)
-  if (toValError !== null) {
+
+  if (toValError?.data) {
     throw createError({
       statusCode: toValError.statusCode,
-      message: "Error while getting the list of analysis"
-
+      statusMessage: toValError.statusMessage,
+      message: toValError?.message,
+      data: toValError?.data ?? ""
     })
+  } else {
+    console.log(error.value)
+    throw createError("Unable to get the analysis")
+
   }
+
+
+  // }
 }
 
 
diff --git a/frontend/components/ErrorAlert.vue b/frontend/components/ErrorAlert.vue
new file mode 100644
index 0000000..582fd1d
--- /dev/null
+++ b/frontend/components/ErrorAlert.vue
@@ -0,0 +1,36 @@
+<script setup lang="ts">
+import { useIsGalaxyOffline } from '~/composables/isGalaxyOffline';
+import type { FetchError } from "ofetch"
+
+defineEmits(['clearError'])
+
+export interface Props {
+    error?: MaybeRef<Error | FetchError | null | undefined>
+}
+const props = withDefaults(defineProps<Props>(), {
+    error: undefined,
+});
+
+const { message, isGalaxyOffline } = useIsGalaxyOffline({ error: props.error })
+
+
+
+</script>
+
+<template>
+    <v-alert type="error" variant="tonal" prominent>
+        <v-card flat color="transparent">
+            <v-card-text v-if="isGalaxyOffline">
+                {{ message }}
+            </v-card-text>
+            <v-card-text v-else>
+                {{ error }}
+            </v-card-text>
+            <v-card-actions>
+                <v-btn color="currentColor" prepend-icon="md:home" variant="text" @click="$emit('clearError')">
+                    Back home
+                </v-btn>
+            </v-card-actions>
+        </v-card>
+    </v-alert>
+</template>
\ No newline at end of file
diff --git a/frontend/components/Main.vue b/frontend/components/Main.vue
index 47949bd..9d21eae 100644
--- a/frontend/components/Main.vue
+++ b/frontend/components/Main.vue
@@ -8,8 +8,6 @@ export interface Props {
 const props = withDefaults(defineProps<Props>(), {
   fluid: true
 });
-const sessionExpiryDate = useState('sessionExpiryDate', () => "")
-// const { csrfToken } =  useCsrfToken()
 const maxWidth = ref(1300)
 const minWidth = ref(900)
 const computedMinWidth = computed(() => {
diff --git a/frontend/components/SubmitAnalysis.vue b/frontend/components/SubmitAnalysis.vue
index f50afc0..b538d0f 100644
--- a/frontend/components/SubmitAnalysis.vue
+++ b/frontend/components/SubmitAnalysis.vue
@@ -14,6 +14,14 @@ const { meta, handleSubmit } = useForm({
 const files = useField("files");
 
 
+const { data, error } = await useAPI<{ is_online: boolean }>("/analysis/is-galaxy-online", {
+  onRequest({ request }) {
+  }
+})
+
+if (data.value !== null && !data.value.is_online) {
+  throw createError("The galaxy instance is offline")
+}
 
 </script>
 <template>
diff --git a/frontend/components/UppyGenomeUpload.vue b/frontend/components/UppyGenomeUpload.vue
index 76d380c..848b5b9 100644
--- a/frontend/components/UppyGenomeUpload.vue
+++ b/frontend/components/UppyGenomeUpload.vue
@@ -53,10 +53,10 @@ const uppyCovar = new Uppy({
         const emitObject = (files.length === 0) ? undefined : files
         emit('update:modelValue', emitObject)
     })
-    .on("error", (error) => {
-        console.log(error)
-        throw createError("uppy error")
-    });
+    // .on("error", (error) => {
+    //     console.log(error)
+    //     throw createError("uppy error")
+    // });
 
 
 const themeUppy = computed(() => {
diff --git a/frontend/composables/isGalaxyOffline.ts b/frontend/composables/isGalaxyOffline.ts
new file mode 100644
index 0000000..6dbb86a
--- /dev/null
+++ b/frontend/composables/isGalaxyOffline.ts
@@ -0,0 +1,18 @@
+import type { FetchError } from "ofetch"
+
+export const useIsGalaxyOffline = ({ error }: {
+  error: MaybeRef<Error | FetchError | null | undefined>
+}) => {
+  const message = ref<string>("The Galaxy instance used to run the analyses is currently offline. It is probably a maintenance ")
+  const isGalaxyOffline = ref<boolean>(false)
+  const toValError = toValue(error)
+  console.dir(toValError)
+  if (toValError && "data" in toValError) {
+    if (toValError?.statusCode === 503 && toValError.data?.message === "The Galaxy instance is offline") {
+      isGalaxyOffline.value = true
+    }
+
+  }
+
+  return { message, isGalaxyOffline }
+}
diff --git a/frontend/composables/useFetchAnalyses.ts b/frontend/composables/useFetchAnalyses.ts
index ee4ee5c..bfac4b2 100644
--- a/frontend/composables/useFetchAnalyses.ts
+++ b/frontend/composables/useFetchAnalyses.ts
@@ -1,18 +1,13 @@
 import { useIntervalFn } from "@vueuse/core";
 import { NON_TERMINAL_STATE, type JobState, type AnalysesPollingOptions, type Analysis } from "~/types";
 
-
-
-
-
-export function usePolling( options: MaybeRef<AnalysesPollingOptions> = { interval: 5000, refresh: null, data: ref(null), pending: ref(false) }) {
+export function usePolling(options: MaybeRef<AnalysesPollingOptions> = { interval: 5000, refresh: null, data: ref(null), pending: ref(false) }) {
     const nonTerminalState = computed(() => {
         return new Set<JobState>(NON_TERMINAL_STATE)
     })
 
     const { interval, refresh, pending, data } = toValue(options)
     const { pause, resume, isActive } = useIntervalFn(() => {
-        console.log("je suis dans le polling")
         if (!pending.value) {
             console.log(
                 `pending ${pending.value
diff --git a/frontend/error.vue b/frontend/error.vue
index a42e825..645b210 100644
--- a/frontend/error.vue
+++ b/frontend/error.vue
@@ -1,4 +1,5 @@
 <script setup lang="ts">
+
 const error = useError()
 function handleError() {
     clearError({ redirect: '/' })
@@ -7,6 +8,7 @@ function handleError() {
 <template>
     <div>
         <NuxtLayout>
+            <!-- <div v-if="isGalaxyOffline">galaxy offfline</div> -->
             <v-alert type="error" variant="tonal" prominent>
                 <v-card flat color="transparent">
                     <v-card-title v-if="error && error?.statusCode">
diff --git a/frontend/middleware/maintenance.global.ts b/frontend/middleware/maintenance.global.ts
index 4e10edf..e1469c6 100644
--- a/frontend/middleware/maintenance.global.ts
+++ b/frontend/middleware/maintenance.global.ts
@@ -7,10 +7,4 @@ export default defineNuxtRouteMiddleware((to, from) => {
     if (isMaintenance && to.name !== "maintenance") {
         return navigateTo('/maintenance')
     }
-    // else { return }
-
-
-
-
-
 })
diff --git a/frontend/pages/analyses/index.vue b/frontend/pages/analyses/index.vue
index beda58b..dcf62b3 100644
--- a/frontend/pages/analyses/index.vue
+++ b/frontend/pages/analyses/index.vue
@@ -27,19 +27,9 @@ function resetError(error) {
     <v-card flat color="transparent">
         <v-breadcrumbs :items="breadcrumbItems"></v-breadcrumbs>
         <NuxtErrorBoundary>
-            <analysis-list> </analysis-list>
+            <AnalysisList></AnalysisList>
             <template #error="{ error }">
-                <v-alert type="error" variant="tonal" prominent>
-                    <v-card flat color="transparent">
-                        <v-card-text>
-                            {{ error }}
-                        </v-card-text>
-                        <v-card-actions>
-                            <v-btn color="currentColor" prepend-icon="md:home" variant="outlined"
-                                @click="resetError(error)">Back home</v-btn>
-                        </v-card-actions>
-                    </v-card>
-                </v-alert>
+                <ErrorAlert :error="error" @clear-error="resetError(error)"></ErrorAlert>
             </template>
         </NuxtErrorBoundary>
     </v-card>
diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue
index b9ccb96..82f4e13 100644
--- a/frontend/pages/index.vue
+++ b/frontend/pages/index.vue
@@ -82,17 +82,8 @@ function resetError(error) {
         <NuxtErrorBoundary>
           <SubmitAnalysis></SubmitAnalysis>
           <template #error="{ error }">
-            <v-alert type="error" variant="tonal" prominent>
-              <v-card flat color="transparent">
-                <v-card-text>
-                  {{ error }}
-                </v-card-text>
-                <v-card-actions>
-                  <v-btn color="currentColor" prepend-icon="md:home" variant="outlined" @click="resetError(error)">Back
-                    home</v-btn>
-                </v-card-actions>
-              </v-card>
-            </v-alert></template>
+            <ErrorAlert :error="error"></ErrorAlert>
+          </template>
         </NuxtErrorBoundary>
       </v-card>
     </v-col>
diff --git a/frontend/plugins/api.ts b/frontend/plugins/api.ts
index 8d66b65..57f7964 100644
--- a/frontend/plugins/api.ts
+++ b/frontend/plugins/api.ts
@@ -5,6 +5,7 @@ export default defineNuxtPlugin(() => {
     const $api = $fetch.create({
         baseURL: "/dfapi",
         async onRequest({ request, options, error }) {
+            console.log("dans le on request du create")
             const { data } = await useFetch("/api/auth", { headers })
             const toValToken = toValue(csrfToken)
             if (toValToken !== undefined) {
diff --git a/frontend/server/api/probe.get.ts b/frontend/server/api/probe.get.ts
new file mode 100644
index 0000000..a5cd75e
--- /dev/null
+++ b/frontend/server/api/probe.get.ts
@@ -0,0 +1,3 @@
+export default defineEventHandler((event) => {
+  return 'ok'
+})
-- 
GitLab