diff --git a/backend/api/tests/utils.py b/backend/api/tests/utils.py index 1073ef0e..a52fd0e2 100644 --- a/backend/api/tests/utils.py +++ b/backend/api/tests/utils.py @@ -29,8 +29,11 @@ class CRUDMixin(APITestCase): self.assertEqual(response.status_code, expected) return response - def assert_delete(self, user=None, expected=status.HTTP_403_FORBIDDEN): + def assert_delete(self, user=None, expected=status.HTTP_403_FORBIDDEN, data=None): if user: self.client.force_login(user) - response = self.client.delete(self.url) + + if data is None: + data = {} + response = self.client.delete(self.url, data=data) self.assertEqual(response.status_code, expected) diff --git a/backend/label_types/tests/test_views.py b/backend/label_types/tests/test_views.py index 00198e9c..45a2c22c 100644 --- a/backend/label_types/tests/test_views.py +++ b/backend/label_types/tests/test_views.py @@ -15,16 +15,18 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), "data") class TestLabelList(CRUDMixin): - @classmethod - def setUpTestData(cls): - cls.non_member = make_user() - cls.project_a = prepare_project(ProjectType.DOCUMENT_CLASSIFICATION) - cls.label = make_label(cls.project_a.item) - cls.url = reverse(viewname="category_types", args=[cls.project_a.item.id]) + def setUp(self): + self.non_member = make_user() + self.project_a = prepare_project(ProjectType.DOCUMENT_CLASSIFICATION) + self.label = make_label(self.project_a.item) + self.url = reverse(viewname="category_types", args=[self.project_a.item.id]) # Ensure that the API does not return the labels of the other project. - cls.project_b = make_project(task="Any", users=["admin"], roles=[settings.ROLE_PROJECT_ADMIN]) - make_label(cls.project_b.item) + self.project_b = make_project(task="Any", users=["admin"], roles=[settings.ROLE_PROJECT_ADMIN]) + make_label(self.project_b.item) + + # for label creation + self.data = {"text": "example"} def test_returns_labels_to_project_member(self): for member in self.project_a.members: @@ -38,32 +40,11 @@ class TestLabelList(CRUDMixin): def test_does_not_return_labels_to_unauthenticated_user(self): self.assert_fetch(expected=status.HTTP_403_FORBIDDEN) - -class TestLabelSearch(CRUDMixin): - def setUp(self): - self.project = prepare_project(ProjectType.DOCUMENT_CLASSIFICATION) - make_label(self.project.item) - self.url = reverse(viewname="category_types", args=[self.project.item.id]) - - def test_search(self): - for member in self.project.members: - response = self.assert_fetch(member, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - - -class TestLabelCreate(CRUDMixin): - @classmethod - def setUpTestData(cls): - cls.non_member = make_user(ProjectType.DOCUMENT_CLASSIFICATION) - cls.project = prepare_project() - cls.url = reverse(viewname="category_types", args=[cls.project.item.id]) - cls.data = {"text": "example"} - def test_allows_admin_to_create_label(self): - self.assert_create(self.project.admin, status.HTTP_201_CREATED) + self.assert_create(self.project_a.admin, status.HTTP_201_CREATED) def test_denies_project_staff_to_create_label(self): - for member in self.project.staffs: + for member in self.project_a.staffs: self.assert_create(member, status.HTTP_403_FORBIDDEN) def test_denies_non_project_member_to_create_label(self): @@ -72,6 +53,31 @@ class TestLabelCreate(CRUDMixin): def test_denies_unauthenticated_user_to_create_label(self): self.assert_create(expected=status.HTTP_403_FORBIDDEN) + def test_allows_admin_to_bulk_delete_label(self): + self.assert_delete(self.project_a.admin, status.HTTP_204_NO_CONTENT, data={"ids": [self.label.id]}) + + def test_denies_project_staff_to_bulk_delete_label(self): + member = self.project_a.staffs[0] + self.assert_delete(member, status.HTTP_403_FORBIDDEN, data={"ids": [self.label.id]}) + + +class TestAllowMemberToCreateLabelType(CRUDMixin): + @classmethod + def setUpTestData(cls): + cls.project = prepare_project(ProjectType.DOCUMENT_CLASSIFICATION, allow_member_to_create_label_type=True) + cls.label = make_label(cls.project.item) + cls.url = reverse(viewname="category_types", args=[cls.project.item.id]) + cls.data = {"text": "example"} + + def test_allows_member_to_create_label_type(self): + for member in self.project.members: + self.data["text"] = member.username + self.assert_create(member, status.HTTP_201_CREATED) + + def test_denies_project_staff_to_bulk_delete_label(self): + member = self.project.staffs[0] + self.assert_delete(member, status.HTTP_403_FORBIDDEN, data={"ids": [self.label.id]}) + class TestLabelDetailAPI(CRUDMixin): @classmethod diff --git a/backend/label_types/views.py b/backend/label_types/views.py index 875cbc89..a783d788 100644 --- a/backend/label_types/views.py +++ b/backend/label_types/views.py @@ -2,6 +2,7 @@ import json import re from django.db import IntegrityError, transaction +from django.shortcuts import get_object_or_404 from django_filters.rest_framework import DjangoFilterBackend from rest_framework import generics, status from rest_framework.exceptions import ParseError @@ -18,7 +19,12 @@ from .serializers import ( RelationTypeSerializer, SpanTypeSerializer, ) -from projects.permissions import IsProjectAdmin, IsProjectStaffAndReadOnly +from projects.models import Project +from projects.permissions import ( + IsProjectAdmin, + IsProjectMember, + IsProjectStaffAndReadOnly, +) def camel_to_snake(name): @@ -35,7 +41,14 @@ class LabelList(generics.ListCreateAPIView): filter_backends = [DjangoFilterBackend] serializer_class = LabelSerializer pagination_class = None - permission_classes = [IsAuthenticated & (IsProjectAdmin | IsProjectStaffAndReadOnly)] + + def get_permissions(self): + project = get_object_or_404(Project, pk=self.kwargs["project_id"]) + if project.allow_member_to_create_label_type and self.request.method == "POST": + self.permission_classes = [IsAuthenticated & IsProjectMember] + else: + self.permission_classes = [IsAuthenticated & (IsProjectAdmin | IsProjectStaffAndReadOnly)] + return super().get_permissions() def get_queryset(self): return self.model.objects.filter(project=self.kwargs["project_id"]) diff --git a/backend/projects/migrations/0008_project_allow_member_to_create_label_type_and_more.py b/backend/projects/migrations/0008_project_allow_member_to_create_label_type_and_more.py new file mode 100644 index 00000000..af41b307 --- /dev/null +++ b/backend/projects/migrations/0008_project_allow_member_to_create_label_type_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 4.1.7 on 2023-06-07 04:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("projects", "0007_imagecaptioningproject_alter_project_project_type"), + ] + + operations = [ + migrations.AddField( + model_name="project", + name="allow_member_to_create_label_type", + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name="project", + name="project_type", + field=models.CharField( + choices=[ + ("DocumentClassification", "Document Classification"), + ("SequenceLabeling", "Sequence Labeling"), + ("Seq2seq", "Seq2Seq"), + ("IntentDetectionAndSlotFilling", "Intent Detection And Slot Filling"), + ("Speech2text", "Speech2Text"), + ("ImageClassification", "Image Classification"), + ("BoundingBox", "Bounding Box"), + ("Segmentation", "Segmentation"), + ("ImageCaptioning", "Image Captioning"), + ], + max_length=30, + ), + ), + ] diff --git a/backend/projects/models.py b/backend/projects/models.py index ed5d4218..c53a1059 100644 --- a/backend/projects/models.py +++ b/backend/projects/models.py @@ -38,6 +38,7 @@ class Project(PolymorphicModel): random_order = models.BooleanField(default=False) collaborative_annotation = models.BooleanField(default=False) single_class_classification = models.BooleanField(default=False) + allow_member_to_create_label_type = models.BooleanField(default=False) def add_admin(self): admin_role = Role.objects.get(name=settings.ROLE_PROJECT_ADMIN) diff --git a/backend/projects/serializers.py b/backend/projects/serializers.py index 3f8b09bc..861842d2 100644 --- a/backend/projects/serializers.py +++ b/backend/projects/serializers.py @@ -71,6 +71,7 @@ class ProjectSerializer(serializers.ModelSerializer): "author", "collaborative_annotation", "single_class_classification", + "allow_member_to_create_label_type", "is_text_project", "tags", ] diff --git a/frontend/components/label/ActionMenu.vue b/frontend/components/label/ActionMenu.vue index 229ce73f..d7239d09 100644 --- a/frontend/components/label/ActionMenu.vue +++ b/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' + } + ]) + } } } }) diff --git a/frontend/components/label/LabelList.vue b/frontend/components/label/LabelList.vue index a6acf418..ece9c14c 100644 --- a/frontend/components/label/LabelList.vue +++ b/frontend/components/label/LabelList.vue @@ -64,6 +64,10 @@ export default Vue.extend({ type: Array as PropType, 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 } } }) diff --git a/frontend/components/layout/TheSideBar.vue b/frontend/components/layout/TheSideBar.vue index 6d3fb8fd..801ab859 100644 --- a/frontend/components/layout/TheSideBar.vue +++ b/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, diff --git a/frontend/components/project/FormUpdate.vue b/frontend/components/project/FormUpdate.vue index 69ae4386..693cf9c0 100644 --- a/frontend/components/project/FormUpdate.vue +++ b/frontend/components/project/FormUpdate.vue @@ -9,33 +9,31 @@ + - + + Edit + - + > + {{ $t('generic.save') }} + + + {{ $t('generic.cancel') }} + diff --git a/frontend/domain/models/project/project.ts b/frontend/domain/models/project/project.ts index ee2bb5a4..f7b0e98a 100644 --- a/frontend/domain/models/project/project.ts +++ b/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 { diff --git a/frontend/layouts/project.vue b/frontend/layouts/project.vue index 84e95fd1..2642eb12 100644 --- a/frontend/layouts/project.vue +++ b/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 { diff --git a/frontend/middleware/isProjectAdmin.ts b/frontend/middleware/isProjectAdmin.ts new file mode 100644 index 00000000..76e8e189 --- /dev/null +++ b/frontend/middleware/isProjectAdmin.ts @@ -0,0 +1,10 @@ +import { NuxtAppOptions } from '@nuxt/types' +import _ from 'lodash' + +export default _.debounce(async ({ app, route, redirect }: NuxtAppOptions) => { + const member = await app.$repositories.member.fetchMyRole(route.params.id) + + if (!member.isProjectAdmin) { + return redirect(app.localePath('/projects/' + route.params.id)) + } +}, 1000) diff --git a/frontend/middleware/setCurrentProject.ts b/frontend/middleware/setCurrentProject.ts new file mode 100644 index 00000000..b015050f --- /dev/null +++ b/frontend/middleware/setCurrentProject.ts @@ -0,0 +1,13 @@ +import { NuxtAppOptions } from '@nuxt/types' + +export default async ({ app, route, redirect }: NuxtAppOptions) => { + const project = app.store.getters['projects/currentProject'] + const isNotSet = Object.keys(project).length === 0 && project.constructor === Object + if (isNotSet || project.id !== route.params.id) { + try { + await app.store.dispatch('projects/setCurrentProject', route.params.id) + } catch (e) { + redirect('/projects') + } + } +} diff --git a/frontend/pages/projects/_id/comments/index.vue b/frontend/pages/projects/_id/comments/index.vue index 7e669a17..9fe2434a 100644 --- a/frontend/pages/projects/_id/comments/index.vue +++ b/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) }, diff --git a/frontend/pages/projects/_id/dataset/_example_id/edit.vue b/frontend/pages/projects/_id/dataset/_example_id/edit.vue index a115ef8c..79094ed8 100644 --- a/frontend/pages/projects/_id/dataset/_example_id/edit.vue +++ b/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) => { diff --git a/frontend/pages/projects/_id/dataset/export.vue b/frontend/pages/projects/_id/dataset/export.vue index 40edc075..2c113292 100644 --- a/frontend/pages/projects/_id/dataset/export.vue +++ b/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) }, diff --git a/frontend/pages/projects/_id/dataset/import.vue b/frontend/pages/projects/_id/dataset/import.vue index aeb8b131..3e39d452 100644 --- a/frontend/pages/projects/_id/dataset/import.vue +++ b/frontend/pages/projects/_id/dataset/import.vue @@ -105,6 +105,8 @@ export default { layout: 'project', + middleware: ['isProjectAdmin'], + validate({ params }) { return /^\d+$/.test(params.id) }, diff --git a/frontend/pages/projects/_id/guideline/index.vue b/frontend/pages/projects/_id/guideline/index.vue index 47fbcebf..23fcfba8 100644 --- a/frontend/pages/projects/_id/guideline/index.vue +++ b/frontend/pages/projects/_id/guideline/index.vue @@ -24,6 +24,8 @@ export default { layout: 'project', + middleware: ['isProjectAdmin'], + validate({ params }) { return /^\d+$/.test(params.id) }, diff --git a/frontend/pages/projects/_id/labels/_label_id/edit.vue b/frontend/pages/projects/_id/labels/_label_id/edit.vue index f8c34f4e..f833cf5d 100644 --- a/frontend/pages/projects/_id/labels/_label_id/edit.vue +++ b/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 diff --git a/frontend/pages/projects/_id/labels/import.vue b/frontend/pages/projects/_id/labels/import.vue index 0c8f98f6..22cd8df9 100644 --- a/frontend/pages/projects/_id/labels/import.vue +++ b/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 diff --git a/frontend/pages/projects/_id/labels/index.vue b/frontend/pages/projects/_id/labels/index.vue index 6bb0825e..2cb6a538 100644 --- a/frontend/pages/projects/_id/labels/index.vue +++ b/frontend/pages/projects/_id/labels/index.vue @@ -12,11 +12,13 @@ - + @@ -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() }, diff --git a/frontend/pages/projects/_id/members/index.vue b/frontend/pages/projects/_id/members/index.vue index 97b59723..a957781b 100644 --- a/frontend/pages/projects/_id/members/index.vue +++ b/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) }, diff --git a/frontend/pages/projects/_id/metrics/index.vue b/frontend/pages/projects/_id/metrics/index.vue index df8e57d7..98c45aa8 100644 --- a/frontend/pages/projects/_id/metrics/index.vue +++ b/frontend/pages/projects/_id/metrics/index.vue @@ -39,6 +39,8 @@ export default { layout: 'project', + middleware: ['isProjectAdmin'], + validate({ params }) { return /^\d+$/.test(params.id) }, diff --git a/frontend/pages/projects/_id/settings/index.vue b/frontend/pages/projects/_id/settings/index.vue index b0bc3527..017d5bb8 100644 --- a/frontend/pages/projects/_id/settings/index.vue +++ b/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) }, diff --git a/frontend/pages/projects/create.vue b/frontend/pages/projects/create.vue index c05aa8cb..5b3c073b 100644 --- a/frontend/pages/projects/create.vue +++ b/frontend/pages/projects/create.vue @@ -12,6 +12,11 @@ v-model="editedItem.exclusiveCategories" :label="$t('overview.allowSingleLabel')" /> + @@ -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) } }, diff --git a/frontend/repositories/project/apiProjectRepository.ts b/frontend/repositories/project/apiProjectRepository.ts index 5489dc43..17081612 100644 --- a/frontend/repositories/project/apiProjectRepository.ts +++ b/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 } } diff --git a/frontend/services/application/project/projectApplicationService.ts b/frontend/services/application/project/projectApplicationService.ts index c6a2ecfb..154a90b4 100644 --- a/frontend/services/application/project/projectApplicationService.ts +++ b/frontend/services/application/project/projectApplicationService.ts @@ -15,6 +15,7 @@ type ProjectFields = { allowOverlappingSpans: boolean enableGraphemeMode: boolean useRelation: boolean + allowMemberToCreateLabelType: boolean } export interface SearchQueryData { @@ -52,7 +53,8 @@ export class ProjectApplicationService { enableGraphemeMode, useRelation, tags, - guideline = '' + guideline = '', + allowMemberToCreateLabelType = false }: ProjectFields): Promise { const project = Project.create( 0, @@ -66,7 +68,8 @@ export class ProjectApplicationService { allowOverlappingSpans, enableGraphemeMode, useRelation, - tags.map((tag) => TagItem.create(tag)) + tags.map((tag) => TagItem.create(tag)), + allowMemberToCreateLabelType ) try { return await this.repository.create(project) @@ -87,7 +90,8 @@ export class ProjectApplicationService { allowOverlappingSpans, enableGraphemeMode, useRelation, - guideline = '' + guideline = '', + allowMemberToCreateLabelType }: Omit ): Promise { const project = Project.create( @@ -102,7 +106,8 @@ export class ProjectApplicationService { allowOverlappingSpans, enableGraphemeMode, useRelation, - [] + [], + allowMemberToCreateLabelType ) try { diff --git a/frontend/store/projects.js b/frontend/store/projects.js index 124a8097..f3b4a558 100644 --- a/frontend/store/projects.js +++ b/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 } }