Skip to content
Snippets Groups Projects
Commit deead5a8 authored by Valentin Rigal's avatar Valentin Rigal Committed by Erwan Rouchet
Browse files

Manual ML results deletion

parent 07da67ce
No related branches found
No related tags found
1 merge request!1563Manual ML results deletion
......@@ -21,7 +21,7 @@ export type DeleteWorkerResultsParameters = {
/**
* Delete results produced with a specific worker configuration, or exclude them.
*/
worker_configuration?: UUID | false
configuration_id?: UUID | false
/**
* Delete results from selected elements, not compatible with element_id filter.
......
......@@ -2,53 +2,154 @@
<Modal
:model-value="modelValue"
v-on:update:model-value="updateModelValue"
is-large
:is-large="!advancedMode"
>
<template v-slot:header>
<p class="modal-card-title">{{ truncateLong(title) }}</p>
<p class="modal-card-title mr-5">{{ truncateLong('Delete results ' + originDescription) }}</p>
<span class="ml-auto">
<input
id="advancedModeSwitch"
type="checkbox"
class="switch is-rtl is-rounded is-info"
v-model="advancedMode"
/>
<label class="is-pulled-right mr-4" for="advancedModeSwitch">Advanced mode</label>
</span>
</template>
<table class="table is-fullwidth is-hoverable" v-if="workerVersionsCache">
<thead>
<tr>
<th>Worker&nbsp;version</th>
<th>Model&nbsp;version</th>
<th>Configuration</th>
<th class="is-narrow">Actions</th>
</tr>
</thead>
<tbody>
<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" />
<template v-else></template>
</td>
<td>
<ConfigurationSummary v-if="worker_configuration" :configuration="worker_configuration" :worker-version="worker_version" />
<template v-else></template>
</td>
<td>
<button
class="button is-danger"
type="button"
v-on:click="select(cacheId)"
<div v-if="advancedMode" class="content">
<div class="field">
<label class="label">Worker version UUID</label>
<div class="control">
<input
type="text"
placeholder="00000000-0000-0000-0000-000000000000"
maxlength="36"
class="input uuid-input"
v-model="workerVersionId"
:disabled="loading || null"
/>
<template v-if="errors.worker_version_id">
<p
class="help is-danger"
v-for="err in errors.worker_version_id"
:key="err"
>
Delete
</button>
</td>
</tr>
</tbody>
</table>
<template v-slot:footer="{ close }">
{{ err }}
</p>
</template>
</div>
</div>
<div class="field">
<label class="label">Model version UUID</label>
<div class="control">
<input
type="text"
placeholder="00000000-0000-0000-0000-000000000000"
maxlength="36"
class="input uuid-input"
v-model="modelVersionId"
:disabled="loading || null"
/>
<template v-if="errors.model_version_id">
<p
class="help is-danger"
v-for="err in errors.model_version_id"
:key="err"
>
{{ err }}
</p>
</template>
</div>
</div>
<div class="field">
<label class="label">
Configuration
<span class="control">
<label
v-for="(value, label) in configurationRadio"
:key="value"
class="radio ml-4"
:class="{ 'active': configurationField === value }"
v-on:click.capture="setConfigurationField(value)"
>
<input
class="is-checkradio"
type="radio"
:checked="configurationField === value || null"
:disabled="loading || null"
/>
{{ label }}
</label>
</span>
</label>
<div class="control">
<input
type="text"
placeholder="00000000-0000-0000-0000-000000000000"
maxlength="36"
class="input uuid-input"
v-model="configurationId"
:disabled="configurationField !== true || loading || null"
/>
<template v-if="errors.configuration_id">
<p
class="help is-danger"
v-for="err in errors.configuration_id"
:key="err"
>
{{ err }}
</p>
</template>
</div>
</div>
<button
class="button is-danger is-light"
type="button"
v-on:click="confirmationModal = true"
v-on:click="checkManual"
>
Delete all results
Delete
</button>
</div>
<div v-else class="content">
<table class="table is-fullwidth is-hoverable" v-if="workerVersionsCache">
<thead>
<tr>
<th>Worker&nbsp;version</th>
<th>Model&nbsp;version</th>
<th>Configuration</th>
<th class="is-narrow">Actions</th>
</tr>
</thead>
<tbody>
<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" />
<template v-else></template>
</td>
<td>
<ConfigurationSummary v-if="worker_configuration" :configuration="worker_configuration" :worker-version="worker_version" />
<template v-else></template>
</td>
<td>
<button
class="button is-danger"
type="button"
v-on:click="select(cacheId)"
>
Delete
</button>
</td>
</tr>
</tbody>
</table>
</div>
<template v-slot:footer="{ close }">
<button class="button ml-auto" v-on:click="close">Cancel</button>
</template>
</Modal>
......@@ -58,58 +159,23 @@
title="Confirm deletion"
>
<div class="content">
<template v-if="toDelete === null">
You are about to delete <strong>all worker results</strong>
</template>
<template v-else>
You are about to delete the following results:
<ul>
<li>
worker version:
<router-link :to="{ name: 'worker-version', params: { versionId: versionCacheToDelete?.worker_version.id } }" target="_blank">
{{ truncateLong(versionCacheToDelete?.worker_version?.worker?.name) }}
</router-link>
</li>
<li>
worker configuration:
<template v-if="versionCacheToDelete?.worker_configuration ?? null">
{{ truncateLong(versionCacheToDelete?.worker_configuration?.name) }}
</template>
<template v-else></template>
</li>
<template v-if="versionCacheToDelete?.model_version ?? null">
<li>
model version:
<router-link :to="{ name: 'model-version', params: { versionId: versionCacheToDelete?.model_version?.id } }" target="_blank">
{{ truncateLong(versionCacheToDelete?.model_version?.model?.name) }}
</router-link>
</li>
</template>
</ul>
<template v-if="deletesAll">
You are about to delete <strong>all worker results</strong> {{ originDescription }}.
</template>
<template v-if="elementId">
on all elements under <strong>{{ typeName(element.type) }} {{ element.name }} (included)</strong>.
</template>
<template v-else-if="selection">
on selected elements from project <strong>{{ corpus?.name }}</strong>.
</template>
<template v-else>
on project <strong>{{ corpus?.name }}</strong>.
<p class="mb-0" v-for="(item, index) in deleteMessages" :key="index">
{{ item }}
</p>
</template>
<template v-if="elementId || selection">
<br />
Child elements of these results will also be deleted recursively.
</template>
<hr class="my-3" />
This action is irreversible.
<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="title">{{ truncateLong(title) }}</button>
<button class="button is-danger ml-auto" v-on:click="performDelete" :title="originDescription">{{ truncateLong('Delete results ' + originDescription) }}</button>
</template>
</Modal>
</template>
......@@ -124,6 +190,7 @@ import { useMLResultsStore } from '@/stores'
import { defineComponent, PropType } from 'vue'
import { UUID, WorkerVersionCache } from '@/types'
import { DeleteWorkerResultsParameters } from '@/api'
import { isAxiosError } from 'axios'
import WorkerVersionSummary from '@/components/Process/Workers/Versions/Summary.vue'
import ModelVersionSummary from '@/components/Model/Versions/Summary.vue'
import ConfigurationSummary from '@/components/Process/Workers/Configurations/Summary.vue'
......@@ -175,11 +242,26 @@ export default defineComponent({
},
data: () => ({
loading: false,
advancedMode: false,
/**
* Optional worker version cache to delete
*/
toDelete: null as UUID | null,
confirmationModal: false
cacheUUIDToDelete: null as UUID | null,
/**
* Custom fields for manual deletion
*/
workerVersionId: '',
modelVersionId: '',
configurationId: '',
configurationField: null as boolean | null,
configurationRadio: {
Any: null,
None: false,
UUID: true
},
confirmationModal: false,
// Mappings of [field, error_msg] for the manual deletion form
errors: {} as { [key: string]: Array<string> }
}),
computed: {
...mapVuexState('elements', ['elements']),
......@@ -189,49 +271,101 @@ export default defineComponent({
return store.corpusWorkerVersionsCache[this.corpusId] ?? null
}
}),
title (): string {
let title = 'Delete worker results'
originDescription (): string {
if (this.element?.name !== undefined) {
title += ` on "${this.element.name}" and its children`
return ` on "${this.element.name}" and its children`
} else if (this.selection && this.corpus?.name !== undefined) {
title += ` on selected elements from "${this.corpus.name}"`
} else if (this.corpus?.name) {
title += ` on project "${this.corpus.name}"`
return ` on selected elements from "${this.corpus.name}"`
} else {
return ` on project "${this.corpus.name}"`
}
return title
},
deletesAll (): boolean {
return (
this.advancedMode &&
this.workerVersionId === '' &&
this.modelVersionId === '' &&
this.configurationField === null
)
},
element () {
if (!this.elementId) return
return this.elements[this.elementId] || null
},
versionCacheToDelete (): WorkerVersionCache | null {
if (this.toDelete === null) return null
return this.workerVersionsCache[this.toDelete]
if (this.cacheUUIDToDelete === null) return null
return this.workerVersionsCache[this.cacheUUIDToDelete]
},
deleteMessages (): Array<string> {
const messages = []
// Elements to be deleted
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'
)
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}`)
}
// Worker configuration
const confName = this.advancedMode
? 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 }
if (this.elementId) payload.element_id = this.elementId
if (this.advancedMode) {
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 }
} 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
}
return payload
}
},
methods: {
...mapActions(useMLResultsStore, ['listWorkerVersionsCache', 'deleteWorkerResults']),
updateModelValue (value: boolean) { this.$emit('update:modelValue', value) },
select (cacheId: UUID) {
this.toDelete = cacheId
this.cacheUUIDToDelete = cacheId
this.confirmationModal = true
},
setConfigurationField (value: boolean | null) {
this.configurationField = value
},
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 }
},
async performDelete () {
this.errors = {}
// No need to check for access rights, as the component can only be accessed by corpus admins
this.loading = true
const payload: DeleteWorkerResultsParameters = { use_selection: this.selection }
if (this.elementId) payload.element_id = this.elementId
if (this.versionCacheToDelete !== null) {
payload.worker_version_id = this.versionCacheToDelete.worker_version.id
payload.worker_configuration = this.versionCacheToDelete?.worker_configuration?.id ?? false
if (this.versionCacheToDelete?.model_version?.id) payload.model_version_id = this.versionCacheToDelete.model_version.id
try {
await this.deleteWorkerResults(this.corpusId, this.payload)
this.$emit('update:modelValue', false)
} catch (err) {
if (isAxiosError(err)) this.errors = err?.response?.data || {}
} finally {
this.confirmationModal = false
this.advancedMode = false
this.loading = false
}
await this.deleteWorkerResults(this.corpusId, payload)
this.confirmationModal = false
this.$emit('update:modelValue', false)
this.loading = false
}
},
watch: {
......@@ -242,9 +376,18 @@ export default defineComponent({
},
confirmationModal (newValue: boolean) {
if (newValue === false) {
this.toDelete = null
this.cacheUUIDToDelete = null
}
},
configurationField (newValue: boolean | null) {
if ([false, null].includes(newValue)) this.configurationId = ''
}
}
})
</script>
<style scoped>
.uuid-input {
max-width: 40ch;
}
</style>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment