Browse Source

Merge pull request #1115 from prabd/feature/bulk_delete_and_clear_annotations

Clear annotations and bulk delete functionality. (Fix #523 and #631)
pull/1120/head
Hiroki Nakayama 3 years ago
committed by GitHub
parent
commit
1e40005f35
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 180 additions and 19 deletions
  1. 11
      app/api/views.py
  2. 2
      frontend/components/containers/annotation/ApproveButton.vue
  3. 58
      frontend/components/containers/annotation/ClearAnnotationsButton.vue
  4. 20
      frontend/components/containers/annotation/EntityItemBox.vue
  5. 2
      frontend/components/containers/annotation/FilterButton.vue
  6. 2
      frontend/components/containers/annotation/GuidelineButton.vue
  7. 50
      frontend/components/containers/documents/DocumentBulkDeletionButton.vue
  8. 4
      frontend/components/organisms/utils/ConfirmForm.vue
  9. 1
      frontend/i18n/en/generic.js
  10. 2
      frontend/i18n/en/projects/dataset.js
  11. 5
      frontend/layouts/annotation.vue
  12. 2
      frontend/nuxt.config.js
  13. 6
      frontend/pages/projects/_id/dataset/index.vue
  14. 4
      frontend/services/annotation.service.js
  15. 4
      frontend/services/document.service.js
  16. 26
      frontend/store/documents.js

11
app/api/views.py

@ -184,6 +184,12 @@ class DocumentList(generics.ListCreateAPIView):
project = get_object_or_404(Project, pk=self.kwargs['project_id'])
serializer.save(project=project)
def delete(self, request, *args, **kwargs):
project = get_object_or_404(Project, pk=self.kwargs['project_id'])
queryset = project.documents
queryset.all().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class DocumentDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Document.objects.all()
@ -221,6 +227,11 @@ class AnnotationList(generics.ListCreateAPIView):
def perform_create(self, serializer):
serializer.save(document_id=self.kwargs['doc_id'], user=self.request.user)
def delete(self, request, *args, **kwargs):
queryset = self.get_queryset()
queryset.all().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@staticmethod
def check_single_class_classification(project_id, doc_id, user):
project = get_object_or_404(Project, pk=project_id)

2
frontend/components/containers/annotation/ApproveButton.vue

@ -6,7 +6,7 @@
:disabled="disabled"
class="text-capitalize ps-1 pe-1"
min-width="36"
outlined
icon
v-on="on"
@shortkey="approveNextPage"
@click="approveDocument"

58
frontend/components/containers/annotation/ClearAnnotationsButton.vue

@ -0,0 +1,58 @@
<template>
<v-tooltip bottom>
<template v-slot:activator="{ on }">
<v-btn
class="text-capitalize ps-1 pe-1"
color="error"
min-width="36"
icon
v-on="on"
@click="dialog=true"
>
<v-icon>
mdi-delete-outline
</v-icon>
</v-btn>
</template>
<span>Clear Annotations</span>
<v-dialog
v-model="dialog"
width="800"
>
<confirm-form
title="Clear annotations"
message="Are you sure you want to delete all annotations?"
:button-true-text="$t('generic.yes')"
:button-false-text="$t('generic.cancel')"
@ok="handleClear();dialog=false"
@cancel="dialog=false"
/>
</v-dialog>
</v-tooltip>
</template>
<script>
import { mapActions } from 'vuex'
import ConfirmForm from '@/components/organisms/utils/ConfirmForm'
export default {
components: {
ConfirmForm
},
data() {
return {
dialog: false
}
},
methods: {
...mapActions('documents', ['clearAnnotations']),
handleClear() {
const projectId = this.$route.params.id
this.clearAnnotations(projectId)
}
}
}
</script>

20
frontend/components/containers/annotation/EntityItemBox.vue

@ -1,13 +1,15 @@
<template>
<entity-item-box
v-if="isReady"
:labels="items"
:text="currentDoc.text"
:entities="currentDoc.annotations"
:delete-annotation="removeEntity"
:update-entity="updateEntity"
:add-entity="addEntity"
/>
<div>
<entity-item-box
v-if="isReady"
:labels="items"
:text="currentDoc.text"
:entities="currentDoc.annotations"
:delete-annotation="removeEntity"
:update-entity="updateEntity"
:add-entity="addEntity"
/>
</div>
</template>
<script>

2
frontend/components/containers/annotation/FilterButton.vue

@ -6,7 +6,7 @@
<v-btn
class="text-capitalize ps-1 pe-1"
min-width="36"
outlined
icon
v-on="{ ...tooltip, ...menu }"
>
<v-icon>

2
frontend/components/containers/annotation/GuidelineButton.vue

@ -5,7 +5,7 @@
<v-btn
class="text-capitalize ps-1 pe-1"
min-width="36"
outlined
icon
v-on="on"
@click="dialog=true"
>

50
frontend/components/containers/documents/DocumentBulkDeletionButton.vue

@ -0,0 +1,50 @@
<template>
<div>
<v-btn
:disabled="!total"
class="text-capitalize"
color="error"
@click="dialog=true"
>
{{ $t('generic.deleteAll') }}
</v-btn>
<v-dialog
v-model="dialog"
width="800"
>
<confirm-form
:title="$t('dataset.deleteBulkDocumentsTitle')"
:message="$t('dataset.deleteBulkDocumentsMessage')"
:button-true-text="$t('generic.yes')"
:button-false-text="$t('generic.cancel')"
@ok="deleteAllDocuments($route.params.id);dialog=false"
@cancel="dialog=false"
/>
</v-dialog>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
import ConfirmForm from '@/components/organisms/utils/ConfirmForm'
export default {
components: {
ConfirmForm
},
data() {
return {
dialog: false
}
},
computed: {
...mapState('documents', ['total'])
},
methods: {
...mapActions('documents', ['deleteAllDocuments'])
}
}
</script>

4
frontend/components/organisms/utils/ConfirmForm.vue

@ -41,12 +41,12 @@ export default {
items: {
type: Array,
default: () => [],
required: true
required: false
},
itemKey: {
type: String,
default: '',
required: true
required: false
},
buttonTrueText: {
type: String,

1
frontend/i18n/en/generic.js

@ -10,6 +10,7 @@ export default {
upload: 'Upload',
add: 'Add',
delete: 'Delete',
deleteAll: 'Delete All',
search: 'Search',
name: 'Name',
import: 'Import',

2
frontend/i18n/en/projects/dataset.js

@ -16,5 +16,7 @@ export default {
exportDataMessage2: 'Select a file name',
deleteDocumentsTitle: 'Delete Document',
deleteDocumentsMessage: 'Are you sure you want to delete these documents from this project?',
deleteBulkDocumentsTitle: 'Delete All Documents',
deleteBulkDocumentsMessage: 'Are you sure you want to delete all documents from this project?',
pageText: '{0}-{1} of {2}'
}

5
frontend/layouts/annotation.vue

@ -39,6 +39,7 @@
v-model="filterOption"
/>
<guideline-button />
<clear-annotations-button />
</v-col>
<v-spacer />
<v-col>
@ -73,6 +74,7 @@
<script>
import { mapActions, mapGetters, mapState, mapMutations } from 'vuex'
import BottomNavigator from '@/components/containers/annotation/BottomNavigator'
import ClearAnnotationsButton from '@/components/containers/annotation/ClearAnnotationsButton.vue'
import GuidelineButton from '@/components/containers/annotation/GuidelineButton'
import MetadataBox from '@/components/organisms/annotation/MetadataBox'
import FilterButton from '@/components/containers/annotation/FilterButton'
@ -92,7 +94,8 @@ export default {
GuidelineButton,
FilterButton,
ApproveButton,
MetadataBox
MetadataBox,
ClearAnnotationsButton
},
fetch() {

2
frontend/nuxt.config.js

@ -79,7 +79,7 @@ export default {
proxy: {
// Use a fake value for use at build-time
'/v1/': {
target: process.env.API_URL || 'http://127.0.0.1:12345'
target: process.env.API_URL || 'http://127.0.0.1:8000'
}
},
/*

6
frontend/pages/projects/_id/dataset/index.vue

@ -3,6 +3,8 @@
<v-card-title class="mb-2">
<document-action-menu />
<document-deletion-button class="ms-2" />
<v-spacer />
<document-bulk-deletion-button />
</v-card-title>
<document-list />
</v-card>
@ -12,6 +14,7 @@
import DocumentList from '@/components/containers/documents/DocumentList'
import DocumentActionMenu from '@/components/containers/documents/DocumentActionMenu'
import DocumentDeletionButton from '@/components/containers/documents/DocumentDeletionButton'
import DocumentBulkDeletionButton from '@/components/containers/documents/DocumentBulkDeletionButton'
export default {
layout: 'project',
@ -19,7 +22,8 @@ export default {
components: {
DocumentList,
DocumentActionMenu,
DocumentDeletionButton
DocumentDeletionButton,
DocumentBulkDeletionButton
},
validate({ params, query }) {

4
frontend/services/annotation.service.js

@ -17,6 +17,10 @@ class AnnotationService {
return this.request.delete(`/projects/${projectId}/docs/${docId}/annotations/${annotationId}`)
}
clearAnnotations(projectId, docid) {
return this.request.delete(`/projects/${projectId}/docs/${docid}/annotations`)
}
updateAnnotation(projectId, docId, annotationId, payload) {
return this.request.patch(`/projects/${projectId}/docs/${docId}/annotations/${annotationId}`, payload)
}

4
frontend/services/document.service.js

@ -13,6 +13,10 @@ class DocumentService {
return this.request.post(`/projects/${projectId}/docs`, payload)
}
deleteAllDocuments(projectId) {
return this.request.delete(`/projects/${projectId}/docs`)
}
deleteDocument(projectId, docId) {
return this.request.delete(`/projects/${projectId}/docs/${docId}`)
}

26
frontend/store/documents.js

@ -31,7 +31,6 @@ export const getters = {
return state.items[state.current]
}
}
export const mutations = {
setCurrent(state, payload) {
state.current = payload
@ -67,6 +66,9 @@ export const mutations = {
deleteAnnotation(state, annotationId) {
state.items[state.current].annotations = state.items[state.current].annotations.filter(item => item.id !== annotationId)
},
clearAnnotations(state) {
state.items[state.current].annotations = []
},
updateAnnotation(state, payload) {
const item = state.items[state.current].annotations.find(item => item.id === payload.id)
Object.assign(item, payload)
@ -88,7 +90,6 @@ export const mutations = {
export const actions = {
getDocumentList({ commit, state }, payload) {
commit('setLoading', true)
// payload = Object.assign(payload, state.searchOptions)
return DocumentService.getDocumentList(payload)
.then((response) => {
commit('setDocumentList', response.data.results)
@ -146,6 +147,17 @@ export const actions = {
alert(error)
})
},
deleteAllDocuments({ commit, state }, projectId) {
DocumentService.deleteAllDocuments(projectId)
.then((response) => {
commit('setDocumentList', [])
commit('setTotalItems', 0)
commit('resetSelected')
})
.catch((error) => {
alert(error)
})
},
deleteDocument({ commit, state }, projectId) {
for (const document of state.selected) {
DocumentService.deleteDocument(projectId, document.id)
@ -188,6 +200,16 @@ export const actions = {
alert(error)
})
},
clearAnnotations({ commit, state }, projectId) {
const documentId = state.items[state.current].id
AnnotationService.clearAnnotations(projectId, documentId)
.then((response) => {
commit('clearAnnotations')
})
.catch((error) => {
alert(error)
})
},
approve({ commit, getters }, payload) {
const documentId = getters.currentDoc.id
const data = {

Loading…
Cancel
Save