From 38f6f0c20ec1774fa9bb4728b1564a95cda645d7 Mon Sep 17 00:00:00 2001
From: mlbonhomme <bonhomme@teklia.com>
Date: Thu, 23 Jan 2025 12:23:46 +0100
Subject: [PATCH 1/4] Show worker costs

---
 src/components/Process/Workers/Costs.vue      | 46 +++++++++++++++++++
 .../Process/Workers/WorkerRuns/WorkerTag.vue  | 14 ++++--
 src/types/worker.ts                           |  3 ++
 src/views/Process/Workers/List.vue            |  8 +++-
 4 files changed, 66 insertions(+), 5 deletions(-)
 create mode 100644 src/components/Process/Workers/Costs.vue

diff --git a/src/components/Process/Workers/Costs.vue b/src/components/Process/Workers/Costs.vue
new file mode 100644
index 000000000..3320d92d7
--- /dev/null
+++ b/src/components/Process/Workers/Costs.vue
@@ -0,0 +1,46 @@
+<template>
+  <div class="field is-grouped is-grouped-multiline mt-3">
+    <div class="control">
+      <div class="tags has-addons">
+        <span class="tag is-medium is-light">CPU</span>
+        <span class="tag is-medium" :class="priceTagColor(worker.cost_cpu_hour)">{{ worker.cost_cpu_hour }}</span>
+        <span class="tag is-medium is-light">€ / h</span>
+      </div>
+    </div>
+
+    <div class="control">
+      <div class="tags has-addons">
+        <span class="tag is-medium is-light">GPU</span>
+        <span class="tag is-medium" :class="priceTagColor(worker.cost_gpu_hour)">{{ worker.cost_gpu_hour }}</span>
+        <span class="tag is-medium is-light">€ / h</span>
+      </div>
+    </div>
+
+    <div class="control">
+      <div class="tags has-addons">
+        <span class="tag is-medium" :class="priceTagColor(worker.cost_1k_elements)">{{ worker.cost_1k_elements }}</span>
+        <span class="tag is-medium is-light">€ / 1000 elements</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, PropType } from 'vue'
+import { Worker } from '@/types/worker'
+
+export default defineComponent({
+  props: {
+    worker: {
+      type: Object as PropType<Worker>,
+      required: true
+    }
+  },
+  methods: {
+    priceTagColor (value: number) {
+      if (value > 0) return 'is-warning'
+      return 'is-info is-light'
+    }
+  }
+})
+</script>
diff --git a/src/components/Process/Workers/WorkerRuns/WorkerTag.vue b/src/components/Process/Workers/WorkerRuns/WorkerTag.vue
index 79efa8cdc..a6a609d51 100644
--- a/src/components/Process/Workers/WorkerRuns/WorkerTag.vue
+++ b/src/components/Process/Workers/WorkerRuns/WorkerTag.vue
@@ -9,16 +9,19 @@
   <span class="mx-1" :title="worker.name">
     {{ worker.name }}
   </span>
+  <span v-if="hasCost" class="tag is-warning">€</span>
 </template>
 
-<script>
+<script lang="ts">
+import { defineComponent, PropType } from 'vue'
 import { WORKER_TYPE_COLORS } from '@/config'
+import { Worker } from '@/types/worker'
 
-export default {
+export default defineComponent({
   props: {
     // A worker instance to build base tag elements
     worker: {
-      type: Object,
+      type: Object as PropType<Worker>,
       required: true
     }
   },
@@ -28,7 +31,10 @@ export default {
     },
     workerClass () {
       return WORKER_TYPE_COLORS[this.workerType]?.cssClass ?? WORKER_TYPE_COLORS.default.cssClass
+    },
+    hasCost () {
+      return this.worker.cost_gpu_hour | this.worker.cost_cpu_hour | this.worker.cost_1k_elements
     }
   }
-}
+})
 </script>
diff --git a/src/types/worker.ts b/src/types/worker.ts
index aefe81d9d..7724c0baa 100644
--- a/src/types/worker.ts
+++ b/src/types/worker.ts
@@ -16,6 +16,9 @@ export interface Worker {
   description: string
   repository_url: string | null
   archived: boolean
+  cost_cpu_hour: number
+  cost_gpu_hour: number
+  cost_1k_elements: number
 }
 
 export type UserConfiguration = Record<string, UserConfigurationField>
diff --git a/src/views/Process/Workers/List.vue b/src/views/Process/Workers/List.vue
index 0a7346164..201c526ca 100644
--- a/src/views/Process/Workers/List.vue
+++ b/src/views/Process/Workers/List.vue
@@ -108,6 +108,7 @@
                 <WorkerTag :worker="selectedWorker" />
               </div>
             </h2>
+            <WorkerCosts v-if="hasCosts" :worker="selectedWorker" />
             <ItemId label="Worker ID:" :item-id="selectedWorker.id" />
             <div class="field mt-2">
               <label class="label">Description</label>
@@ -173,6 +174,7 @@ import WorkerTag from '@/components/Process/Workers/WorkerRuns/WorkerTag'
 import CreateForm from '@/components/Process/Workers/CreateForm.vue'
 import ItemId from '@/components/ItemId.vue'
 import ArchivalModal from '@/components/ArchivalModal.vue'
+import WorkerCosts from '@/components/Process/Workers/Costs.vue'
 
 export default {
   components: {
@@ -182,7 +184,8 @@ export default {
     WorkerTag,
     CreateForm,
     ItemId,
-    ArchivalModal
+    ArchivalModal,
+    WorkerCosts
   },
   mixins: [
     truncateMixin
@@ -247,6 +250,9 @@ export default {
     },
     canArchive () {
       return (this.user.is_admin || this.user.can_manage_workers)
+    },
+    hasCosts () {
+      return this.selectedWorker.cost_gpu_hour | this.selectedWorker.cost_cpu_hour | this.selectedWorker.cost_1k_elements
     }
   },
   methods: {
-- 
GitLab


From e2a31561798ad19f3866fbf101251803751b8a50 Mon Sep 17 00:00:00 2001
From: mlbonhomme <bonhomme@teklia.com>
Date: Thu, 23 Jan 2025 16:12:22 +0100
Subject: [PATCH 2/4] costs are string

---
 src/components/Process/Workers/Costs.vue                | 4 ++--
 src/components/Process/Workers/WorkerRuns/WorkerTag.vue | 2 +-
 src/types/worker.ts                                     | 6 +++---
 src/views/Process/Workers/List.vue                      | 2 +-
 4 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/components/Process/Workers/Costs.vue b/src/components/Process/Workers/Costs.vue
index 3320d92d7..a39a33fa0 100644
--- a/src/components/Process/Workers/Costs.vue
+++ b/src/components/Process/Workers/Costs.vue
@@ -37,8 +37,8 @@ export default defineComponent({
     }
   },
   methods: {
-    priceTagColor (value: number) {
-      if (value > 0) return 'is-warning'
+    priceTagColor (value: string) {
+      if (parseFloat(value) > 0) return 'is-warning'
       return 'is-info is-light'
     }
   }
diff --git a/src/components/Process/Workers/WorkerRuns/WorkerTag.vue b/src/components/Process/Workers/WorkerRuns/WorkerTag.vue
index a6a609d51..a48788b0b 100644
--- a/src/components/Process/Workers/WorkerRuns/WorkerTag.vue
+++ b/src/components/Process/Workers/WorkerRuns/WorkerTag.vue
@@ -33,7 +33,7 @@ export default defineComponent({
       return WORKER_TYPE_COLORS[this.workerType]?.cssClass ?? WORKER_TYPE_COLORS.default.cssClass
     },
     hasCost () {
-      return this.worker.cost_gpu_hour | this.worker.cost_cpu_hour | this.worker.cost_1k_elements
+      return parseFloat(this.worker.cost_gpu_hour) > 0 || parseFloat(this.worker.cost_cpu_hour) > 0 || parseFloat(this.worker.cost_1k_elements) > 0
     }
   }
 })
diff --git a/src/types/worker.ts b/src/types/worker.ts
index 7724c0baa..603c6c12c 100644
--- a/src/types/worker.ts
+++ b/src/types/worker.ts
@@ -16,9 +16,9 @@ export interface Worker {
   description: string
   repository_url: string | null
   archived: boolean
-  cost_cpu_hour: number
-  cost_gpu_hour: number
-  cost_1k_elements: number
+  cost_cpu_hour: string
+  cost_gpu_hour: string
+  cost_1k_elements: string
 }
 
 export type UserConfiguration = Record<string, UserConfigurationField>
diff --git a/src/views/Process/Workers/List.vue b/src/views/Process/Workers/List.vue
index 201c526ca..d0ec03148 100644
--- a/src/views/Process/Workers/List.vue
+++ b/src/views/Process/Workers/List.vue
@@ -252,7 +252,7 @@ export default {
       return (this.user.is_admin || this.user.can_manage_workers)
     },
     hasCosts () {
-      return this.selectedWorker.cost_gpu_hour | this.selectedWorker.cost_cpu_hour | this.selectedWorker.cost_1k_elements
+      return parseFloat(this.selectedWorker.cost_gpu_hour) > 0 || parseFloat(this.selectedWorker.cost_cpu_hour) > 0 || parseFloat(this.selectedWorker.cost_1k_elements) > 0
     }
   },
   methods: {
-- 
GitLab


From f0a688937f4fa37b1f6f89f0eda6b5da2bbfef08 Mon Sep 17 00:00:00 2001
From: mlbonhomme <bonhomme@teklia.com>
Date: Thu, 23 Jan 2025 18:13:04 +0100
Subject: [PATCH 3/4] add hasCosts helper + add costs to worker details view

---
 .../Process/Workers/WorkerRuns/WorkerTag.vue           |  9 +++++----
 src/helpers/worker.ts                                  |  5 +++++
 src/views/Process/Workers/List.vue                     |  9 ++++-----
 src/views/Process/Workers/Manage.vue                   | 10 ++++++++--
 4 files changed, 22 insertions(+), 11 deletions(-)
 create mode 100644 src/helpers/worker.ts

diff --git a/src/components/Process/Workers/WorkerRuns/WorkerTag.vue b/src/components/Process/Workers/WorkerRuns/WorkerTag.vue
index a48788b0b..51d05fa99 100644
--- a/src/components/Process/Workers/WorkerRuns/WorkerTag.vue
+++ b/src/components/Process/Workers/WorkerRuns/WorkerTag.vue
@@ -9,13 +9,14 @@
   <span class="mx-1" :title="worker.name">
     {{ worker.name }}
   </span>
-  <span v-if="hasCost" class="tag is-warning">€</span>
+  <span v-if="hasCosts(worker)" class="tag is-warning">€</span>
 </template>
 
 <script lang="ts">
 import { defineComponent, PropType } from 'vue'
 import { WORKER_TYPE_COLORS } from '@/config'
 import { Worker } from '@/types/worker'
+import { hasCosts } from '@/helpers/worker'
 
 export default defineComponent({
   props: {
@@ -25,15 +26,15 @@ export default defineComponent({
       required: true
     }
   },
+  data: () => ({
+    hasCosts
+  }),
   computed: {
     workerType () {
       return this.worker?.type
     },
     workerClass () {
       return WORKER_TYPE_COLORS[this.workerType]?.cssClass ?? WORKER_TYPE_COLORS.default.cssClass
-    },
-    hasCost () {
-      return parseFloat(this.worker.cost_gpu_hour) > 0 || parseFloat(this.worker.cost_cpu_hour) > 0 || parseFloat(this.worker.cost_1k_elements) > 0
     }
   }
 })
diff --git a/src/helpers/worker.ts b/src/helpers/worker.ts
new file mode 100644
index 000000000..0ded2ceff
--- /dev/null
+++ b/src/helpers/worker.ts
@@ -0,0 +1,5 @@
+import { Worker } from "@/types/worker"
+
+export const hasCosts = (worker: Worker) => {
+    return parseFloat(worker.cost_gpu_hour) > 0 || parseFloat(worker.cost_cpu_hour) > 0 || parseFloat(worker.cost_1k_elements) > 0
+}
\ No newline at end of file
diff --git a/src/views/Process/Workers/List.vue b/src/views/Process/Workers/List.vue
index d0ec03148..a137d6652 100644
--- a/src/views/Process/Workers/List.vue
+++ b/src/views/Process/Workers/List.vue
@@ -108,7 +108,7 @@
                 <WorkerTag :worker="selectedWorker" />
               </div>
             </h2>
-            <WorkerCosts v-if="hasCosts" :worker="selectedWorker" />
+            <WorkerCosts v-if="hasCosts(selectedWorker)" :worker="selectedWorker" />
             <ItemId label="Worker ID:" :item-id="selectedWorker.id" />
             <div class="field mt-2">
               <label class="label">Description</label>
@@ -175,6 +175,7 @@ import CreateForm from '@/components/Process/Workers/CreateForm.vue'
 import ItemId from '@/components/ItemId.vue'
 import ArchivalModal from '@/components/ArchivalModal.vue'
 import WorkerCosts from '@/components/Process/Workers/Costs.vue'
+import { hasCosts } from '@/helpers/worker'
 
 export default {
   components: {
@@ -238,7 +239,8 @@ export default {
     archiveModal: false,
     archivedWorkers: false,
     md: new MarkdownIt({ breaks: true }),
-    expandDescription: false
+    expandDescription: false,
+    hasCosts
   }),
   computed: {
     ...mapState(useWorkerStore, ['workerTypes']),
@@ -250,9 +252,6 @@ export default {
     },
     canArchive () {
       return (this.user.is_admin || this.user.can_manage_workers)
-    },
-    hasCosts () {
-      return parseFloat(this.selectedWorker.cost_gpu_hour) > 0 || parseFloat(this.selectedWorker.cost_cpu_hour) > 0 || parseFloat(this.selectedWorker.cost_1k_elements) > 0
     }
   },
   methods: {
diff --git a/src/views/Process/Workers/Manage.vue b/src/views/Process/Workers/Manage.vue
index 4696fc4e9..08c0462c5 100644
--- a/src/views/Process/Workers/Manage.vue
+++ b/src/views/Process/Workers/Manage.vue
@@ -23,6 +23,8 @@
         </button>
       </div>
 
+      <WorkerCosts v-if="hasCosts(worker)" :worker="worker" />
+
       <div class="field">
         <label class="label">Description</label>
         <div class="control">
@@ -74,6 +76,7 @@ import { mapState, mapActions } from 'pinia'
 import { defineComponent } from 'vue'
 import { mapGetters as mapVuexGetters, mapState as mapVuexState } from 'vuex'
 import { errorParser } from '@/helpers'
+import { hasCosts } from '@/helpers/worker'
 import VersionList from '@/components/Process/Workers/Versions/List.vue'
 import ListMembers from '@/components/Memberships/ListMembers.vue'
 import { useNotificationStore, useWorkerStore } from '@/stores'
@@ -81,6 +84,7 @@ import ItemId from '@/components/ItemId.vue'
 import ArchivalModal from '@/components/ArchivalModal.vue'
 import EditForm from '@/components/Process/Workers/EditForm.vue'
 import WorkerTag from '@/components/Process/Workers/WorkerRuns/WorkerTag.vue'
+import WorkerCosts from '@/components/Process/Workers/Costs.vue'
 
 export default defineComponent({
   props: {
@@ -95,13 +99,15 @@ export default defineComponent({
     ItemId,
     ArchivalModal,
     EditForm,
-    WorkerTag
+    WorkerTag,
+    WorkerCosts
   },
   data: () => ({
     loading: false,
     error: null as string | null,
     archiveModal: false,
-    md: new MarkdownIt({ breaks: true })
+    md: new MarkdownIt({ breaks: true }),
+    hasCosts
   }),
   async created () {
     if (!this.worker) await this.retrieveWorker()
-- 
GitLab


From 00cac2280fa0082cd5c2ee0c35f4cdfc23c6de3f Mon Sep 17 00:00:00 2001
From: Erwan Rouchet <rouchet@teklia.com>
Date: Mon, 27 Jan 2025 11:55:48 +0100
Subject: [PATCH 4/4] Explicit typing and VSCode linting errors

---
 src/helpers/worker.ts | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/helpers/worker.ts b/src/helpers/worker.ts
index 0ded2ceff..3fbc85417 100644
--- a/src/helpers/worker.ts
+++ b/src/helpers/worker.ts
@@ -1,5 +1,5 @@
-import { Worker } from "@/types/worker"
+import { Worker } from '@/types/worker'
 
-export const hasCosts = (worker: Worker) => {
-    return parseFloat(worker.cost_gpu_hour) > 0 || parseFloat(worker.cost_cpu_hour) > 0 || parseFloat(worker.cost_1k_elements) > 0
-}
\ No newline at end of file
+export const hasCosts = (worker: Worker): boolean => {
+  return parseFloat(worker.cost_gpu_hour) > 0 || parseFloat(worker.cost_cpu_hour) > 0 || parseFloat(worker.cost_1k_elements) > 0
+}
-- 
GitLab