From 68520c303f086dfe4ca935a3ada18eb216c73a10 Mon Sep 17 00:00:00 2001
From: Theo Lesage <tlesage@teklia.com>
Date: Mon, 25 Mar 2024 15:26:30 +0000
Subject: [PATCH] Add a field for deleting worker results by filtering worker
 runs

---
 src/api/mlresults.ts                          |   5 +
 .../Process/Workers/DeleteResultsModal.vue    | 183 +++++++++++++-----
 2 files changed, 141 insertions(+), 47 deletions(-)

diff --git a/src/api/mlresults.ts b/src/api/mlresults.ts
index 497acb32f..e8ac9116e 100644
--- a/src/api/mlresults.ts
+++ b/src/api/mlresults.ts
@@ -8,6 +8,11 @@ export type DeleteWorkerResultsParameters = {
    */
   element_id?: UUID
 
+  /**
+   * Delete results produced by a specific worker run
+   */
+  worker_run_id?: UUID
+
   /**
    * Delete results produced with a specific worker version.
    */
diff --git a/src/components/Process/Workers/DeleteResultsModal.vue b/src/components/Process/Workers/DeleteResultsModal.vue
index 3c702b0fa..a41907c56 100644
--- a/src/components/Process/Workers/DeleteResultsModal.vue
+++ b/src/components/Process/Workers/DeleteResultsModal.vue
@@ -5,7 +5,9 @@
     :is-large="!advancedMode"
   >
     <template v-slot:header>
-      <p class="modal-card-title mr-5">{{ truncateLong('Delete results ' + originDescription) }}</p>
+      <p class="modal-card-title mr-5">
+        {{ truncateLong('Delete results ' + originDescription) }}
+      </p>
       <span class="ml-auto">
         <input
           id="advancedModeSwitch"
@@ -13,11 +15,36 @@
           class="switch is-rtl is-rounded is-info"
           v-model="advancedMode"
         />
-        <label class="is-pulled-right mr-4" for="advancedModeSwitch">Advanced mode</label>
+        <label
+          class="is-pulled-right mr-4"
+          for="advancedModeSwitch"
+        >Advanced mode</label>
       </span>
     </template>
 
     <div v-if="advancedMode" class="content">
+      <div class="field">
+        <label class="label">Worker run UUID</label>
+        <div class="control">
+          <input
+            type="text"
+            placeholder="00000000-0000-0000-0000-000000000000"
+            maxlength="36"
+            class="input uuid-input"
+            v-model="workerRunId"
+            :disabled="loading || undefined"
+          />
+          <template v-if="errors.worker_run_id">
+            <p
+              class="help is-danger"
+              v-for="err in errors.worker_run_id"
+              :key="err"
+            >
+              {{ err }}
+            </p>
+          </template>
+        </div>
+      </div>
       <div class="field">
         <label class="label">Worker version UUID</label>
         <div class="control">
@@ -27,7 +54,7 @@
             maxlength="36"
             class="input uuid-input"
             v-model="workerVersionId"
-            :disabled="loading || null"
+            :disabled="inWorkerRunMode || loading || undefined"
           />
           <template v-if="errors.worker_version_id">
             <p
@@ -49,7 +76,7 @@
             maxlength="36"
             class="input uuid-input"
             v-model="modelVersionId"
-            :disabled="loading || null"
+            :disabled="inWorkerRunMode || loading || undefined"
           />
           <template v-if="errors.model_version_id">
             <p
@@ -68,16 +95,19 @@
           <span class="control">
             <label
               v-for="(value, label) in configurationRadio"
-              :key="value"
+              :key="label"
               class="radio ml-4"
-              :class="{ 'active': configurationField === value }"
+              :class="{ active: configurationField === value }"
               v-on:click.capture="setConfigurationField(value)"
             >
               <input
                 class="is-checkradio"
                 type="radio"
-                :checked="configurationField === value || null"
-                :disabled="loading || null"
+                :checked="
+                  (configurationField === value && !inWorkerRunMode) ||
+                    undefined
+                "
+                :disabled="inWorkerRunMode || loading || undefined"
               />
               {{ label }}
             </label>
@@ -91,7 +121,12 @@
             maxlength="36"
             class="input uuid-input"
             v-model="configurationId"
-            :disabled="configurationField !== true || loading || null"
+            :disabled="
+              inWorkerRunMode ||
+                configurationField !== true ||
+                loading ||
+                undefined
+            "
           />
           <template v-if="errors.configuration_id">
             <p
@@ -104,11 +139,7 @@
           </template>
         </div>
       </div>
-      <button
-        class="button is-danger is-light"
-        type="button"
-        v-on:click="checkManual"
-      >
+      <button class="button is-danger" type="button" v-on:click="checkManual">
         Delete
       </button>
     </div>
@@ -124,16 +155,28 @@
           </tr>
         </thead>
         <tbody>
-          <tr v-for="({ worker_version, worker_configuration, model_version }, cacheId) in workerVersionsCache" :key="cacheId">
+          <tr
+            v-for="(
+              { worker_version, worker_configuration, model_version }, cacheId
+            ) in workerVersionsCache"
+            :key="cacheId"
+          >
             <td>
               <WorkerVersionSummary :worker-version="worker_version" />
             </td>
             <td>
-              <ModelVersionSummary v-if="model_version" :model-version="model_version" />
+              <ModelVersionSummary
+                v-if="model_version"
+                :model-version="model_version"
+              />
               <template v-else>—</template>
             </td>
             <td>
-              <ConfigurationSummary v-if="worker_configuration" :configuration="worker_configuration" :worker-version="worker_version" />
+              <ConfigurationSummary
+                v-if="worker_configuration"
+                :configuration="worker_configuration"
+                :worker-version="worker_version"
+              />
               <template v-else>—</template>
             </td>
             <td>
@@ -154,13 +197,16 @@
     </template>
   </Modal>
 
-  <Modal
-    v-model="confirmationModal"
-    title="Confirm deletion"
-  >
+  <Modal v-model="confirmationModal" title="Confirm deletion">
     <div class="content">
-      <template v-if="deletesAll">
-        You are about to delete <strong>all worker results</strong> {{ originDescription }}.
+      <template v-if="inWorkerRunMode">
+        You are about to delete all results from worker run <strong>{{ workerRunId }}</strong>
+        {{ originDescription }}.
+      </template>
+
+      <template v-else-if="deletesAll">
+        You are about to delete <strong>all worker results</strong>
+        {{ originDescription }}.
       </template>
 
       <template v-else>
@@ -169,13 +215,21 @@
         </p>
       </template>
       <hr class="my-3" />
-      <p class="mb-0">Child elements of these results will also be deleted recursively.</p>
+      <p class="mb-0">
+        Child elements of these results will also be deleted recursively.
+      </p>
       <p>This action is irreversible.</p>
     </div>
 
     <template v-slot:footer="{ close }">
       <button class="button" v-on:click="close">Cancel</button>
-      <button class="button is-danger ml-auto" v-on:click="performDelete" :title="originDescription">{{ truncateLong('Delete results ' + originDescription) }}</button>
+      <button
+        class="button is-danger ml-auto"
+        v-on:click="performDelete"
+        :title="originDescription"
+      >
+        {{ truncateLong('Delete results ' + originDescription) }}
+      </button>
     </template>
   </Modal>
 </template>
@@ -196,10 +250,7 @@ import ModelVersionSummary from '@/components/Model/Versions/Summary.vue'
 import ConfigurationSummary from '@/components/Process/Workers/Configurations/Summary.vue'
 
 export default defineComponent({
-  mixins: [
-    corporaMixin,
-    truncateMixin
-  ],
+  mixins: [corporaMixin, truncateMixin],
   components: {
     Modal,
     WorkerVersionSummary,
@@ -219,7 +270,7 @@ export default defineComponent({
      */
     corpusId: {
       type: String as PropType<UUID>,
-      validator: value => typeof value === 'string' && UUID_REGEX.test(value),
+      validator: (value) => typeof value === 'string' && UUID_REGEX.test(value),
       required: true
     },
     /**
@@ -228,7 +279,7 @@ export default defineComponent({
      */
     elementId: {
       type: String as PropType<UUID>,
-      validator: value => typeof value === 'string' && UUID_REGEX.test(value),
+      validator: (value) => typeof value === 'string' && UUID_REGEX.test(value),
       default: null
     },
     /**
@@ -250,6 +301,7 @@ export default defineComponent({
     /**
      * Custom fields for manual deletion
      */
+    workerRunId: '',
     workerVersionId: '',
     modelVersionId: '',
     configurationId: '',
@@ -302,40 +354,64 @@ export default defineComponent({
       messages.push(`You are about to delete results ${this.originDescription}`)
       // Worker version
       const versionName = this.truncateLong(
-        this.versionCacheToDelete?.worker_version?.worker?.name || this.workerVersionId || 'Any'
+        this.versionCacheToDelete?.worker_version?.worker?.name ||
+          this.workerVersionId ||
+          'Any'
       )
       messages.push(`• Worker version: ${versionName}`)
       // Model version
       if (this.advancedMode) {
         messages.push(`• Model version: ${this.modelVersionId || 'Any'}`)
       } else if (this.versionCacheToDelete?.model_version) {
-        messages.push(`• Model version: ${this.versionCacheToDelete.model_version.model.name}`)
+        messages.push(
+          `• Model version: ${this.versionCacheToDelete.model_version.model.name}`
+        )
       }
       // Worker configuration
       const confName = this.advancedMode
-        ? this.configurationId || (this.configurationField === null && 'Any') || 'None'
-        : this.truncateLong(this.versionCacheToDelete?.worker_configuration?.name || 'None')
+        ? this.configurationId ||
+          (this.configurationField === null && 'Any') ||
+          'None'
+        : this.truncateLong(
+          this.versionCacheToDelete?.worker_configuration?.name || 'None'
+        )
       messages.push(`• Configuration: ${confName}`)
       return messages
     },
     payload () {
-      const payload: DeleteWorkerResultsParameters = { use_selection: this.selection }
+      const payload: DeleteWorkerResultsParameters = {
+        use_selection: this.selection
+      }
       if (this.elementId) payload.element_id = this.elementId
       if (this.advancedMode) {
-        if (this.workerVersionId) payload.worker_version_id = this.workerVersionId
+        if (this.workerRunId) payload.worker_run_id = this.workerRunId
+        if (this.workerVersionId) { payload.worker_version_id = this.workerVersionId }
         if (this.modelVersionId) payload.model_version_id = this.modelVersionId
-        if (this.configurationField !== null) { payload.configuration_id = this.configurationField ? this.configurationId : false }
+        if (this.configurationField !== null) {
+          payload.configuration_id = this.configurationField
+            ? this.configurationId
+            : false
+        }
       } else if (this.versionCacheToDelete !== null) {
         payload.worker_version_id = this.versionCacheToDelete.worker_version.id
-        payload.configuration_id = this.versionCacheToDelete?.worker_configuration?.id ?? false
-        if (this.versionCacheToDelete?.model_version?.id) payload.model_version_id = this.versionCacheToDelete.model_version.id
+        payload.configuration_id =
+          this.versionCacheToDelete?.worker_configuration?.id ?? false
+        if (this.versionCacheToDelete?.model_version?.id) { payload.model_version_id = this.versionCacheToDelete.model_version.id }
       }
       return payload
+    },
+    inWorkerRunMode (): boolean {
+      return this.workerRunId !== ''
     }
   },
   methods: {
-    ...mapActions(useMLResultsStore, ['listWorkerVersionsCache', 'deleteWorkerResults']),
-    updateModelValue (value: boolean) { this.$emit('update:modelValue', value) },
+    ...mapActions(useMLResultsStore, [
+      'listWorkerVersionsCache',
+      'deleteWorkerResults'
+    ]),
+    updateModelValue (value: boolean) {
+      this.$emit('update:modelValue', value)
+    },
     select (cacheId: UUID) {
       this.cacheUUIDToDelete = cacheId
       this.confirmationModal = true
@@ -345,11 +421,24 @@ export default defineComponent({
     },
     checkManual () {
       this.errors = {}
-      if (this.workerVersionId && !UUID_REGEX.test(this.workerVersionId)) { this.errors.worker_version_id = ['This value must be a valid UUID.'] }
-      if (this.modelVersionId && !UUID_REGEX.test(this.modelVersionId)) { this.errors.model_version_id = ['This value must be a valid UUID.'] }
-      if (this.configurationId && !UUID_REGEX.test(this.configurationId)) { this.errors.configuration_id = ['This value must be a valid UUID.'] }
-      if (this.configurationField === true && this.configurationId === '') { this.errors.configuration_id = ['This value must be set.'] }
-      if (Object.keys(this.errors).length === 0) { this.confirmationModal = true }
+      if (this.workerRunId && !UUID_REGEX.test(this.workerRunId)) {
+        this.errors.worker_run_id = ['This value must be a valid UUID.']
+      }
+      if (this.workerVersionId && !UUID_REGEX.test(this.workerVersionId)) {
+        this.errors.worker_version_id = ['This value must be a valid UUID.']
+      }
+      if (this.modelVersionId && !UUID_REGEX.test(this.modelVersionId)) {
+        this.errors.model_version_id = ['This value must be a valid UUID.']
+      }
+      if (this.configurationId && !UUID_REGEX.test(this.configurationId)) {
+        this.errors.configuration_id = ['This value must be a valid UUID.']
+      }
+      if (this.configurationField === true && this.configurationId === '') {
+        this.errors.configuration_id = ['This value must be set.']
+      }
+      if (Object.keys(this.errors).length === 0) {
+        this.confirmationModal = true
+      }
     },
     async performDelete () {
       this.errors = {}
-- 
GitLab