Skip to content
Snippets Groups Projects
Commit 96fec790 authored by ml bonhomme's avatar ml bonhomme :bee: Committed by Erwan Rouchet
Browse files

Add archive model modal

parent 747eb464
No related branches found
No related tags found
1 merge request!1613Add archive model modal
......@@ -4,7 +4,7 @@ import { Model, ModelVersion } from '@/types/model'
import { PageNumberPaginationParameters, unique } from '.'
export type CreateModelPayload = Pick<Model, 'name'> & Partial<Pick<Model, 'description'>>
export type UpdateModelPayload = Partial<Pick<Model, 'name' | 'description'>>
export type UpdateModelPayload = Partial<Pick<Model, 'name' | 'description' | 'archived'>>
export const createModel = unique(async (params: CreateModelPayload): Promise<Model> => (await axios.post('/models/', params)).data)
......
<template>
<Modal :model-value="modelValue" v-on:update:model-value="updateModelValue" :title="'Archive model ' + truncateShort(model.name)">
<p>
Are you sure you want to archive model
<strong>{{ truncateLong(model.name) }}</strong>
?
</p>
<template v-slot:footer="{ close }">
<button class="button" v-on:click.prevent="close">Cancel</button>
<button
class="button is-danger"
:class="{ 'is-loading': loading }"
:disabled="loading || !canAdmin || undefined"
v-on:click.prevent="performArchive"
>
Archive
</button>
</template>
</Modal>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue'
import Modal from '@/components/Modal.vue'
import { mapState, mapActions } from 'pinia'
import { useModelStore, useNotificationStore } from '@/stores'
import { Model } from '@/types/model'
import { truncateMixin } from '@/mixins'
import { isAxiosError } from 'axios'
import { errorParser } from '@/helpers'
export default defineComponent({
components: {
Modal
},
mixins: [
truncateMixin
],
props: {
// The model to archive.
model: {
type: Object as PropType<Model>,
required: true
},
modelValue: {
type: Boolean,
default: false
}
},
emits: {
'update:modelValue': (value: boolean) => typeof value === 'boolean',
archived: (value: boolean) => typeof value === 'boolean'
},
data: () => ({
opened: false,
loading: false
}),
computed: {
...mapState(useModelStore, ['models']),
canAdmin () {
return this.models[this.model.id].rights.includes('admin')
}
},
methods: {
...mapActions(useModelStore, ['updateModel']),
...mapActions(useNotificationStore, ['notify']),
updateModelValue (value: boolean) { this.$emit('update:modelValue', value) },
async performArchive () {
if (!this.canAdmin || this.loading) return
this.loading = true
try {
await this.updateModel(this.model.id, { archived: true })
this.$emit('archived', true)
this.updateModelValue(false)
} catch (err) {
if (isAxiosError(err) && err.response?.status === 400 && err.response.data && 'archived' in err.response.data) {
err.response.data.archived.forEach((error: string) =>
this.notify({ type: 'error', text: error })
)
} else {
this.notify({ type: 'error', text: errorParser(err) })
}
} finally {
this.loading = false
}
}
}
})
</script>
<template>
<router-link
v-if="$route.name !== 'model'"
class="button is-pulled-right"
:to="{ name: 'model', params: { modelId } }"
target="_blank"
title="Open model version details in a new tab"
>
Model details
</router-link>
<div v-if="$route.name !== 'model'" class="fields is-grouped is-pulled-right">
<router-link
class="button mr-2"
:to="{ name: 'model', params: { modelId } }"
target="_blank"
title="Open model details in a new tab"
>
Model details
</router-link>
<button
v-if="!models[modelId].archived"
class="button is-danger"
v-on:click="archiveModal = true"
:disabled="!canAdmin || undefined"
:title="archiveButtonTitle"
>
Archive model
</button>
</div>
<div class="title is-4">Versions</div>
<div class="control">
<VersionList
......@@ -28,6 +38,12 @@
v-model:page-number="membersPageNumber"
/>
</template>
<ArchivalModal
v-if="canAdmin"
:model="models[modelId]"
v-model="archiveModal"
v-on:archived="$emit('reloadModels', true)"
/>
</template>
<script lang="ts">
......@@ -36,12 +52,19 @@ import { UUID_REGEX } from '@/config'
import ListMembers from '@/components/Memberships/ListMembers.vue'
import VersionList from './Versions/List.vue'
import ModelWorkers from '@/components/Model/Workers/List.vue'
import ArchivalModal from '@/components/Model/ArchivalModal.vue'
import { mapState } from 'pinia'
import { useModelStore } from '@/stores'
export default defineComponent({
components: {
ListMembers,
VersionList,
ModelWorkers
ModelWorkers,
ArchivalModal
},
emits: {
reloadModels: (value: boolean) => typeof value === 'boolean'
},
props: {
modelId: {
......@@ -61,8 +84,19 @@ export default defineComponent({
}
},
data: () => ({
membersPageNumber: 1
membersPageNumber: 1,
archiveModal: false
}),
computed: {
...mapState(useModelStore, ['models']),
canAdmin () {
return this.models[this.modelId].rights.includes('admin')
},
archiveButtonTitle () {
if (this.canAdmin) return 'Archive model'
return 'You need administrator rights on a model to archive it'
}
},
watch: {
modelId () {
this.membersPageNumber = 1
......
......@@ -22,6 +22,7 @@ export interface Model {
updated: string
description: string
rights: Right[]
archived: boolean
}
export interface ModelVersion {
......
......@@ -2,13 +2,15 @@
<main class="container is-fluid">
<div class="columns">
<div class="column is-one-third">
<router-link
class="button is-primary is-pulled-right mb-2"
:to="{ name: 'model-create' }"
v-if="trainingMode"
>
Create a model
</router-link>
<div class="has-text-right">
<router-link
class="button mb-2"
:to="{ name: 'model-create' }"
v-if="trainingMode"
>
Create a model
</router-link>
</div>
<div v-if="compatibleWorkerId" class="field has-text-right">
<div class="control">
......@@ -32,6 +34,13 @@
<p class="help all-models-help is-pulled-right mb-2">By default, only models compatible with the selected worker are shown.</p>
</div>
<div class="tabs is-centered mb-2">
<ul>
<li :class="{ 'is-active': !archivedModels }" v-on:click="archivedModels = false"><a>Available models</a></li>
<li :class="{ 'is-active': archivedModels }" v-on:click="archivedModels = true"><a>Archived models</a></li>
</ul>
</div>
<!-- Selection of a worker is required to list versions -->
<form
v-if="modelsPage"
......@@ -90,6 +99,7 @@
:model-id="selectedModel"
:process-id="processId"
:worker-run-id="workerRunId"
v-on:reload-models="updateModelsPage"
/>
</div>
</div>
......@@ -160,6 +170,7 @@ export default defineComponent({
page: 1,
nameFilter: '',
allModels: false,
archivedModels: false,
uid: uniqueId()
}),
methods: {
......@@ -169,7 +180,7 @@ export default defineComponent({
this.loading = true
try {
this.selectedModel = null
const payload: ModelListParameters = { page: this.page, archived: false }
const payload: ModelListParameters = { page: this.page, archived: this.archivedModels }
if (this.nameFilter) payload.name = this.nameFilter
if (this.compatibleWorkerId && !this.allModels) payload.compatible_worker = this.compatibleWorkerId
this.modelsPage = await this.listModels(payload)
......@@ -193,6 +204,10 @@ export default defineComponent({
allModels: {
immediate: true,
handler: 'updateModelsPage'
},
archivedModels: {
immediate: true,
handler: 'updateModelsPage'
}
}
})
......@@ -211,4 +226,11 @@ export default defineComponent({
.all-models-help {
margin-top: -0.5em;
}
.tabs {
width: 100%;
}
.tabs a {
padding-top: 0;
}
</style>
......@@ -6,10 +6,21 @@
Models
</router-link>
<div class="title">
{{ model.name }}
<span v-if="model.archived" class="tag">Archived</span> {{ model.name }}
<EditForm class="ml-2 is-primary" :model="model" />
</div>
<p class="subtitle is-5">Model <ItemId :item-id="model.id" /></p>
<div class="subtitle is-5">
<p>Model <ItemId :item-id="model.id" /></p>
<button
v-if="!model.archived"
class="button is-danger mt-2"
v-on:click="archiveModal = canAdmin"
:disabled="!canAdmin || undefined"
:title="archiveButtonTitle"
>
Archive model
</button>
</div>
<div class="columns">
<div class="column">
......@@ -44,6 +55,11 @@
</template>
<div v-else-if="error" class="notification is-danger">{{ error }}</div>
<div v-else class="loader is-size-2 mx-auto"></div>
<ArchivalModal
v-if="canAdmin"
:model="model"
v-model="archiveModal"
/>
</main>
</template>
......@@ -57,6 +73,7 @@ import Model from '@/components/Model'
import EditForm from '@/components/Model/EditForm.vue'
import { useModelStore, useNotificationStore } from '@/stores'
import ItemId from '@/components/ItemId.vue'
import ArchivalModal from '@/components/Model/ArchivalModal.vue'
export default defineComponent({
props: {
......@@ -69,11 +86,13 @@ export default defineComponent({
components: {
Model,
EditForm,
ItemId
ItemId,
ArchivalModal
},
data: () => ({
loading: false,
error: null as string | null
error: null as string | null,
archiveModal: false
}),
computed: {
...mapState(useModelStore, ['models']),
......@@ -87,6 +106,13 @@ export default defineComponent({
updateDate (): string | null {
if (!this.model) return null
return ago(new Date(this.model.updated))
},
canAdmin () {
return this.model?.rights.includes('admin')
},
archiveButtonTitle () {
if (this.canAdmin) return 'Archive model'
return 'You need administrator rights on a model to archive it'
}
},
methods: {
......
......@@ -54,8 +54,8 @@ describe('model', () => {
describe('updateModel', () => {
it('updates a model', async () => {
store.models = { modelid: { id: 'modelid' } }
const attrs = { name: 'test', description: 'A' }
store.models = { modelid: { id: 'modelid', description: 'hello', archived: false } }
const attrs = { name: 'test', description: 'A', archived: true }
mock.onPatch('/model/modelid/').reply(201, { id: 'modelid', ...attrs })
await store.updateModel('modelid', attrs)
......@@ -68,7 +68,7 @@ describe('model', () => {
}
])
assert.deepStrictEqual(store.models, {
modelid: { id: 'modelid', name: 'test', description: 'A' }
modelid: { id: 'modelid', name: 'test', description: 'A', archived: true }
})
})
})
......
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