Browse Source

Allow member to create label types

pull/2207/head
Hironsan 2 years ago
parent
commit
85c5aa8887
21 changed files with 159 additions and 63 deletions
  1. 35
      frontend/components/label/ActionMenu.vue
  2. 17
      frontend/components/label/LabelList.vue
  3. 8
      frontend/components/layout/TheSideBar.vue
  4. 30
      frontend/components/project/FormUpdate.vue
  5. 37
      frontend/domain/models/project/project.ts
  6. 3
      frontend/layouts/project.vue
  7. 4
      frontend/middleware/isProjectAdmin.ts
  8. 3
      frontend/pages/projects/_id/comments/index.vue
  9. 2
      frontend/pages/projects/_id/dataset/_example_id/edit.vue
  10. 2
      frontend/pages/projects/_id/dataset/export.vue
  11. 2
      frontend/pages/projects/_id/dataset/import.vue
  12. 2
      frontend/pages/projects/_id/guideline/index.vue
  13. 2
      frontend/pages/projects/_id/labels/_label_id/edit.vue
  14. 2
      frontend/pages/projects/_id/labels/import.vue
  15. 35
      frontend/pages/projects/_id/labels/index.vue
  16. 3
      frontend/pages/projects/_id/members/index.vue
  17. 2
      frontend/pages/projects/_id/metrics/index.vue
  18. 3
      frontend/pages/projects/_id/settings/index.vue
  19. 19
      frontend/pages/projects/create.vue
  20. 2
      frontend/repositories/project/apiProjectRepository.ts
  21. 9
      frontend/store/projects.js

35
frontend/components/label/ActionMenu.vue

@ -18,25 +18,38 @@ export default Vue.extend({
ActionMenu
},
props: {
addOnly: {
type: Boolean,
default: false
}
},
computed: {
items() {
return [
const items = [
{
title: this.$t('labels.createLabel'),
icon: mdiPencil,
event: 'create'
},
{
title: this.$t('labels.importLabels'),
icon: mdiUpload,
event: 'upload'
},
{
title: this.$t('labels.exportLabels'),
icon: mdiDownload,
event: 'download'
}
]
if (this.addOnly) {
return items
} else {
return items.concat([
{
title: this.$t('labels.importLabels'),
icon: mdiUpload,
event: 'upload'
},
{
title: this.$t('labels.exportLabels'),
icon: mdiDownload,
event: 'download'
}
])
}
}
}
})

17
frontend/components/label/LabelList.vue

@ -64,6 +64,10 @@ export default Vue.extend({
type: Array as PropType<LabelDTO[]>,
default: () => [],
required: true
},
disableEdit: {
type: Boolean,
default: false
}
},
@ -77,12 +81,15 @@ export default Vue.extend({
computed: {
headers() {
return [
{ text: this.$t('generic.name'), value: 'text' },
{ text: this.$t('labels.shortkey'), value: 'suffixKey' },
{ text: this.$t('labels.color'), value: 'backgroundColor' },
{ text: 'Actions', value: 'actions', sortable: false }
const headers = [
{ text: this.$t('generic.name'), value: 'text', sortable: true },
{ text: this.$t('labels.shortkey'), value: 'suffixKey', sortable: true },
{ text: this.$t('labels.color'), value: 'backgroundColor', sortable: true }
]
if (!this.disableEdit) {
headers.push({ text: 'Actions', value: 'actions', sortable: false })
}
return headers
}
}
})

8
frontend/components/layout/TheSideBar.vue

@ -81,13 +81,17 @@ export default {
icon: mdiLabel,
text: this.$t('labels.labels'),
link: 'labels',
isVisible: this.isProjectAdmin && this.project.canDefineLabel
isVisible:
(this.isProjectAdmin || this.project.allowMemberToCreateLabelType) &&
this.project.canDefineLabel
},
{
icon: mdiLabel,
text: 'Relations',
link: 'links',
isVisible: this.isProjectAdmin && this.project.canDefineRelation
isVisible:
(this.isProjectAdmin || this.project.allowMemberToCreateLabelType) &&
this.project.canDefineRelation
},
{
icon: mdiAccount,

30
frontend/components/project/FormUpdate.vue

@ -9,33 +9,31 @@
<tag-list v-model="tags" />
<random-order-field v-model="project.enableRandomOrder" />
<sharing-mode-field v-model="project.enableSharingMode" />
<v-checkbox
v-if="project.canDefineLabel"
v-model="project.allowMemberToCreateLabelType"
label="Allow project members to create label types"
/>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-card-actions class="ps-4 pt-0">
<v-btn
v-if="!isEditing"
color="primary"
class="text-capitalize"
@click="isEditing = true"
v-text="`Edit`"
/>
<v-btn v-if="!isEditing" color="primary" class="text-capitalize" @click="isEditing = true">
Edit
</v-btn>
<v-btn
v-show="isEditing"
color="primary"
:disabled="!valid || isUpdating"
class="mr-4 text-capitalize"
@click="save"
v-text="$t('generic.save')"
/>
<v-btn
v-show="isEditing"
:disabled="isUpdating"
class="text-capitalize"
@click="cancel"
v-text="$t('generic.cancel')"
/>
>
{{ $t('generic.save') }}
</v-btn>
<v-btn v-show="isEditing" :disabled="isUpdating" class="text-capitalize" @click="cancel">
{{ $t('generic.cancel') }}
</v-btn>
</v-card-actions>
</v-card>
</template>

37
frontend/domain/models/project/project.ts

@ -33,6 +33,24 @@ export const validateNameMaxLength = (name: string): boolean => {
return name.trim().length <= MAX_PROJECT_NAME_LENGTH
}
export const canDefineCategory = (projectType: ProjectType): boolean => {
return [
DocumentClassification,
IntentDetectionAndSlotFilling,
ImageClassification,
BoundingBox,
Segmentation
].includes(projectType)
}
export const canDefineSpan = (projectType: ProjectType): boolean => {
return [SequenceLabeling, IntentDetectionAndSlotFilling].includes(projectType)
}
export const canDefineLabel = (projectType: ProjectType): boolean => {
return canDefineCategory(projectType) || canDefineSpan(projectType)
}
export class Project {
name: string
description: string
@ -50,6 +68,7 @@ export class Project {
readonly enableGraphemeMode: boolean,
readonly useRelation: boolean,
readonly tags: TagItem[],
readonly allowMemberToCreateLabelType: boolean = false,
readonly users: number[] = [],
readonly createdAt: string = '',
readonly updatedAt: string = '',
@ -85,7 +104,8 @@ export class Project {
allowOverlappingSpans: boolean,
enableGraphemeMode: boolean,
useRelation: boolean,
tags: TagItem[]
tags: TagItem[],
allowMemberToCreateLabelType: boolean
) {
return new Project(
id,
@ -99,26 +119,21 @@ export class Project {
allowOverlappingSpans,
enableGraphemeMode,
useRelation,
tags
tags,
allowMemberToCreateLabelType
)
}
get canDefineLabel(): boolean {
return this.canDefineCategory || this.canDefineSpan
return canDefineLabel(this.projectType)
}
get canDefineCategory(): boolean {
return [
DocumentClassification,
IntentDetectionAndSlotFilling,
ImageClassification,
BoundingBox,
Segmentation
].includes(this.projectType)
return canDefineCategory(this.projectType)
}
get canDefineSpan(): boolean {
return [SequenceLabeling, IntentDetectionAndSlotFilling].includes(this.projectType)
return canDefineSpan(this.projectType)
}
get canDefineRelation(): boolean {

3
frontend/layouts/project.vue

@ -32,7 +32,8 @@ export default {
TheSideBar,
TheHeader
},
middleware: ['check-auth', 'auth', 'check-admin'],
middleware: ['check-auth', 'auth', 'setCurrentProject'],
data() {
return {

4
frontend/middleware/isProjectAdmin.ts

@ -3,10 +3,8 @@ import _ from 'lodash'
export default _.debounce(async ({ app, route, redirect }: NuxtAppOptions) => {
const member = await app.$repositories.member.fetchMyRole(route.params.id)
const projectRoot = app.localePath('/projects/' + route.params.id)
const path = route.fullPath.replace(/\/$/g, '')
if (!member.isProjectAdmin && path !== projectRoot) {
if (!member.isProjectAdmin) {
return redirect(app.localePath('/projects/' + route.params.id))
}
}, 1000)

3
frontend/pages/projects/_id/comments/index.vue

@ -39,8 +39,11 @@ export default Vue.extend({
CommentList,
FormDelete
},
layout: 'project',
middleware: ['isProjectAdmin'],
validate({ params }) {
return /^\d+$/.test(params.id)
},

2
frontend/pages/projects/_id/dataset/_example_id/edit.vue

@ -27,6 +27,8 @@ import { ExampleDTO } from '~/services/application/example/exampleData'
export default Vue.extend({
layout: 'project',
middleware: ['isProjectAdmin'],
validate({ params, app }) {
if (/^\d+$/.test(params.id) && /^\d+$/.test(params.example_id)) {
return app.$services.project.findById(params.id).then((res: Project) => {

2
frontend/pages/projects/_id/dataset/export.vue

@ -44,6 +44,8 @@ import { Format } from '~/domain/models/download/format'
export default Vue.extend({
layout: 'project',
middleware: ['isProjectAdmin'],
validate({ params }) {
return /^\d+$/.test(params.id)
},

2
frontend/pages/projects/_id/dataset/import.vue

@ -105,6 +105,8 @@ export default {
layout: 'project',
middleware: ['isProjectAdmin'],
validate({ params }) {
return /^\d+$/.test(params.id)
},

2
frontend/pages/projects/_id/guideline/index.vue

@ -24,6 +24,8 @@ export default {
layout: 'project',
middleware: ['isProjectAdmin'],
validate({ params }) {
return /^\d+$/.test(params.id)
},

2
frontend/pages/projects/_id/labels/_label_id/edit.vue

@ -19,6 +19,8 @@ export default Vue.extend({
layout: 'project',
middleware: ['isProjectAdmin'],
validate({ params, query, app }) {
if (!['category', 'span', 'relation'].includes(query.type as string)) {
return false

2
frontend/pages/projects/_id/labels/import.vue

@ -14,6 +14,8 @@ export default Vue.extend({
layout: 'project',
middleware: ['isProjectAdmin'],
validate({ params, query, app }) {
if (!['category', 'span', 'relation'].includes(query.type as string)) {
return false

35
frontend/pages/projects/_id/labels/index.vue

@ -12,11 +12,13 @@
</v-tabs>
<v-card-title>
<action-menu
:add-only="canOnlyAdd"
@create="$router.push('labels/add?type=' + labelType)"
@upload="$router.push('labels/import?type=' + labelType)"
@download="download"
/>
<v-btn
v-if="!canOnlyAdd"
class="text-capitalize ms-2"
:disabled="!canDelete"
outlined
@ -28,7 +30,13 @@
<form-delete :selected="selected" @cancel="dialogDelete = false" @remove="remove" />
</v-dialog>
</v-card-title>
<label-list v-model="selected" :items="items" :is-loading="isLoading" @edit="editItem" />
<label-list
v-model="selected"
:items="items"
:is-loading="isLoading"
:disable-edit="canOnlyAdd"
@edit="editItem"
/>
</v-card>
</template>
@ -39,6 +47,7 @@ import FormDelete from '@/components/label/FormDelete.vue'
import LabelList from '@/components/label/LabelList.vue'
import { Project } from '~/domain/models/project/project'
import { LabelDTO } from '~/services/application/label/labelData'
import { MemberItem } from '~/domain/models/member/member'
export default Vue.extend({
components: {
@ -46,12 +55,21 @@ export default Vue.extend({
FormDelete,
LabelList
},
layout: 'project',
validate({ params, app }) {
if (/^\d+$/.test(params.id)) {
return app.$services.project.findById(params.id).then((res: Project) => {
return res.canDefineLabel
return app.$services.project.findById(params.id).then((project: Project) => {
if (!project.canDefineLabel) {
return false
}
return app.$repositories.member.fetchMyRole(params.id).then((member: MemberItem) => {
if (member.isProjectAdmin) {
return true
}
return project.allowMemberToCreateLabelType
})
})
}
return false
@ -64,11 +82,19 @@ export default Vue.extend({
selected: [] as LabelDTO[],
isLoading: false,
tab: 0,
project: {} as Project
project: {} as Project,
member: {} as MemberItem
}
},
computed: {
canOnlyAdd(): boolean {
if (this.member.isProjectAdmin) {
return false
}
return this.project.allowMemberToCreateLabelType
},
canDelete(): boolean {
return this.selected.length > 0
},
@ -129,6 +155,7 @@ export default Vue.extend({
async created() {
this.project = await this.$services.project.findById(this.projectId)
this.member = await this.$repositories.member.fetchMyRole(this.projectId)
await this.list()
},

3
frontend/pages/projects/_id/members/index.vue

@ -41,8 +41,11 @@ export default Vue.extend({
FormCreate,
FormDelete
},
layout: 'project',
middleware: ['isProjectAdmin'],
validate({ params }) {
return /^\d+$/.test(params.id)
},

2
frontend/pages/projects/_id/metrics/index.vue

@ -39,6 +39,8 @@ export default {
layout: 'project',
middleware: ['isProjectAdmin'],
validate({ params }) {
return /^\d+$/.test(params.id)
},

3
frontend/pages/projects/_id/settings/index.vue

@ -28,8 +28,11 @@ export default Vue.extend({
ConfigList,
FormUpdate
},
layout: 'project',
middleware: ['isProjectAdmin'],
validate({ params }) {
return /^\d+$/.test(params.id)
},

19
frontend/pages/projects/create.vue

@ -12,6 +12,11 @@
v-model="editedItem.exclusiveCategories"
:label="$t('overview.allowSingleLabel')"
/>
<v-checkbox
v-if="_canDefineLabel"
v-model="editedItem.allowMemberToCreateLabelType"
label="Allow project members to create label types"
/>
<template v-if="isSequenceLabelingProject">
<v-checkbox v-model="editedItem.allowOverlappingSpans" label="Allow overlapping spans" />
<v-img
@ -54,8 +59,9 @@
style="text-transform: none"
outlined
@click="create"
v-text="$t('generic.create')"
/>
>
{{ $t('generic.create') }}
</v-btn>
</v-card-actions>
</v-card>
</template>
@ -71,7 +77,8 @@ import TagList from '~/components/project/TagList.vue'
import {
DocumentClassification,
ImageClassification,
SequenceLabeling
SequenceLabeling,
canDefineLabel
} from '~/domain/models/project/project'
const initializeProject = () => {
@ -86,7 +93,8 @@ const initializeProject = () => {
enableGraphemeMode: false,
useRelation: false,
tags: [] as string[],
guideline: ''
guideline: '',
allowMemberToCreateLabelType: false
}
}
@ -117,6 +125,9 @@ export default Vue.extend({
},
isSequenceLabelingProject(): boolean {
return this.editedItem.projectType === SequenceLabeling
},
_canDefineLabel(): boolean {
return canDefineLabel(this.editedItem.projectType as any)
}
},

2
frontend/repositories/project/apiProjectRepository.ts

@ -38,6 +38,7 @@ function toModel(item: { [key: string]: any }): Project {
item.grapheme_mode,
item.use_relation,
item.tags.map((tag: { [key: string]: any }) => new TagItem(tag.id, tag.text, tag.project)),
item.allow_member_to_create_label_type,
item.users,
item.created_at,
item.updated_at,
@ -60,6 +61,7 @@ function toPayload(item: Project): { [key: string]: any } {
grapheme_mode: item.enableGraphemeMode,
use_relation: item.useRelation,
tags: item.tags,
allow_member_to_create_label_type: item.allowMemberToCreateLabelType,
resourcetype: item.resourceType
}
}

9
frontend/store/projects.js

@ -6,12 +6,9 @@ export const getters = {
currentProject(state) {
return state.current
},
getCurrentUserRole(state) {
return state.current.current_users_role || {}
},
canViewApproveButton(state) {
const role = state.current.current_users_role
return role && !role.is_annotator
project(state) {
return state.current
}
}

Loading…
Cancel
Save