diff --git a/backend/annotation/serializers.py b/backend/annotation/serializers.py index 34f66a3..fde805b 100644 --- a/backend/annotation/serializers.py +++ b/backend/annotation/serializers.py @@ -80,7 +80,7 @@ def get_removable(self, annotation: KnowledgeBaseAnnotation) -> bool: if user is None or user.is_anonymous: return False - return user.has_perm("annotation.delete_knowledgebaseannotation") + return user.has_perm("annotation.change_knowledgebaseannotation") def validate_id(self, value): """Validate that the KnowledgeBaseAnnotation ID exists if provided.""" diff --git a/backend/annotation/serializers_test.py b/backend/annotation/serializers_test.py index c688f08..de578b5 100644 --- a/backend/annotation/serializers_test.py +++ b/backend/annotation/serializers_test.py @@ -65,7 +65,7 @@ def test_kb_annotation_update(kb_annotation): def test_kb_annotation_get_removable_with_permission(kb_annotation, annotator): """Test that get_removable returns True when user has permission.""" permission = Permission.objects.get( - codename="delete_knowledgebaseannotation", content_type__app_label="annotation" + codename="change_knowledgebaseannotation", content_type__app_label="annotation" ) annotator.user_permissions.add(permission) annotator.refresh_from_db() diff --git a/backend/problem/serializers.py b/backend/problem/serializers.py index 778bfe5..5fec960 100644 --- a/backend/problem/serializers.py +++ b/backend/problem/serializers.py @@ -1,4 +1,5 @@ from rest_framework import serializers +from django.utils import timezone from annotation.serializers import KnowledgeBaseAnnotationSerializer from annotation.models import ( @@ -77,11 +78,9 @@ class ProblemInputSerializer(serializers.Serializer): base = serializers.IntegerField(required=False, allow_null=True) def validate_id(self, value): - """Validate that the Problem ID, if provided, exists and belongs to a user-created problem.""" + """Validate that the Problem ID, if provided, exists.""" if value is not None: - if not Problem.objects.filter( - id=value, dataset=Problem.Dataset.USER - ).exists(): + if not Problem.objects.filter(id=value).exists(): raise serializers.ValidationError( f"Problem with ID {value} does not exist." ) @@ -122,8 +121,7 @@ def create(self, validated_data: dict) -> Problem: problem.premises.set(premise_sentences) kb_items = validated_data.get("kbItems", []) - if kb_items: - self._create_update_kb_annotations(problem, kb_items) + self._handle_kb_annotations(problem, kb_items) return problem @@ -159,21 +157,42 @@ def _create_update_kb_annotation( serializer.is_valid(raise_exception=True) serializer.save(problem=problem, session=session, created_by=session.user) - def _create_update_kb_annotations( + def _mark_kb_not_in_input_as_removed( + self, problem: Problem, kb_items: list[dict], session: AnnotationSession + ) -> None: + """ + Marks KnowledgeBase annotations for a problem that are not included in + the provided list of kb_items as removed. + """ + kb_item_ids = {kb_item.get("id") for kb_item in kb_items if kb_item.get("id") is not None} + + annotations_to_delete = KnowledgeBaseAnnotation.objects.filter( + problem=problem, + removed_at__isnull=True + ).exclude(id__in=kb_item_ids) + + current_time = timezone.now() + + for annotation in annotations_to_delete: + annotation.removed_at = current_time + annotation.removed_by = session.user + annotation.save() + + def _handle_kb_annotations( self, problem: Problem, kb_items: list[dict] ) -> None: """ - Creates or update KnowledgeBase and Label annotations for a problem. + Creates, updates and deletes KnowledgeBase annotations for a problem. Creates an annotation session if it does not exist. - - TODO: handle deletions! """ request = self.context.get("request", None) - if not request or not request.user.is_authenticated: + if not request or not request.user.is_authenticated or not request.user.can_edit_kb: return session = AnnotationSession.objects.create(user=request.user) + self._mark_kb_not_in_input_as_removed(problem, kb_items, session) + for kb_item in kb_items: self._create_update_kb_annotation(kb_item, problem, session) @@ -185,8 +204,7 @@ def update(self, instance: Problem, validated_data: dict) -> Problem: # KB annotations can be made for all problems. kb_items = validated_data.get("kbItems", []) - if kb_items: - self._create_update_kb_annotations(instance, kb_items) + self._handle_kb_annotations(instance, kb_items) # Other fields can only be updated for user-created problems. if instance.dataset != Problem.Dataset.USER: diff --git a/backend/problem/serializers_test.py b/backend/problem/serializers_test.py index 6699d08..aec3156 100644 --- a/backend/problem/serializers_test.py +++ b/backend/problem/serializers_test.py @@ -1,9 +1,9 @@ import pytest from rest_framework.exceptions import ValidationError -from annotation.models import AnnotationSession, KnowledgeBaseAnnotation - -from .serializers import ProblemInputSerializer, ProblemSerializer +from annotation.models import KnowledgeBaseAnnotation +from problem.serializers_test_utils import input_serializer_with_user +from .serializers import ProblemInputSerializer from .models import Problem, Sentence @@ -90,21 +90,6 @@ def test_invalid_id_non_existent(): assert "does not exist" in str(exc_info.value) -@pytest.mark.django_db -def test_invalid_id_not_user_problem(non_user_problem): - """Test that a non-user problem ID is invalid.""" - data = { - "id": non_user_problem.pk, - "premises": ["premise"], - "hypothesis": "hypothesis", - "kbItems": [], - } - serializer = ProblemInputSerializer(data=data) - with pytest.raises(ValidationError) as exc_info: - serializer.is_valid(raise_exception=True) - assert "does not exist" in str(exc_info.value) - - @pytest.mark.django_db def test_empty_premises_invalid(): """Test that an empty list of premises is invalid.""" @@ -131,3 +116,406 @@ def test_blank_hypothesis_invalid(): assert not serializer.is_valid() assert "hypothesis" in serializer.errors + +@pytest.mark.django_db +def test_kb_create_no_permission(user_problem, visitor): + """Test that KB annotations cannot be created without permission.""" + kb_input = [ + { + "entity1": "cat", + "entity2": "feline", + "relationship": "equal", + "notes": "Test note", + } + ] + + serializer = input_serializer_with_user(visitor) + serializer._handle_kb_annotations(user_problem, kb_input) # type: ignore + + assert ( + KnowledgeBaseAnnotation.objects.filter(problem=user_problem).count() == 0 + ), "No KB annotations should be created without permission." + + +@pytest.mark.django_db +def test_kb_update_no_permission(user_problem, kb_annotation, visitor): + """Test that KB annotations cannot be updated without permission.""" + kb_annotation.problem = user_problem + kb_annotation.save() + original_entity1 = kb_annotation.entity1 + + updated_entity_1 = "updated_entity" + + kb_input = [ + { + "id": kb_annotation.pk, + "entity1": updated_entity_1, + "entity2": kb_annotation.entity2, + "relationship": kb_annotation.relationship, + } + ] + + # Preconditions + assert original_entity1 != updated_entity_1 + + serializer = input_serializer_with_user(visitor) + serializer._handle_kb_annotations(user_problem, kb_input) # type: ignore + + # Verify KB annotation was not updated + kb_annotation.refresh_from_db() + assert ( + kb_annotation.entity1 == original_entity1 + ), "KB annotation should not have been modified without permission." + assert updated_entity_1 != original_entity1 + + +@pytest.mark.django_db +def test_kb_mark_removed_no_permission(user_problem, kb_annotation, visitor): + """Test that KB annotations cannot be marked as removed without permission.""" + kb_annotation.problem = user_problem + kb_annotation.save() + + kb_input = [] # Empty list should mark any existing KB items as removed. + + serializer = input_serializer_with_user(visitor) + serializer._handle_kb_annotations(user_problem, kb_input) # type: ignore + + # Verify KB annotation was not removed + kb_annotation.refresh_from_db() + assert ( + kb_annotation.removed_at is None + ), "KB annotation should not have been marked as removed without permission." + + +@pytest.mark.django_db +def test_create_single_kb_annotation(user_problem, annotator): + """Test creating a single KB annotation for a problem.""" + kb_input = [ + { + "entity1": "dog", + "entity2": "canine", + "relationship": "equal", + "notes": "Test note", + } + ] + + serializer = input_serializer_with_user(annotator) + serializer._handle_kb_annotations(user_problem, kb_input) + + kb_annotations = KnowledgeBaseAnnotation.objects.filter(problem=user_problem) + assert kb_annotations.count() == 1, "One KB annotation should have been created." + + kb = kb_annotations.first() + assert kb is not None + assert kb.entity1 == "dog" + assert kb.entity2 == "canine" + assert kb.relationship == "equal" + assert kb.notes == "Test note" + assert kb.created_by == annotator + + +@pytest.mark.django_db +def test_update_single_kb_annotation(user_problem, kb_annotation, annotator): + """Test updating a single KB annotation for a problem.""" + kb_annotation.problem = user_problem + kb_annotation.save() + + kb_input = [ + { + "id": kb_annotation.pk, + "entity1": "updated_entity1", + "entity2": "updated_entity2", + "relationship": "subset", + "notes": "Updated note", + } + ] + + serializer = input_serializer_with_user(annotator) + serializer._handle_kb_annotations(user_problem, kb_input) + + # Verify KB annotation was updated + kb_annotation.refresh_from_db() + assert kb_annotation.entity1 == "updated_entity1" + assert kb_annotation.entity2 == "updated_entity2" + assert kb_annotation.relationship == "subset" + assert kb_annotation.notes == "Updated note" + assert kb_annotation.removed_at is None + + +@pytest.mark.django_db +def test_mark_kb_annotation_as_removed(user_problem, kb_annotation, annotator): + """Test marking a KB annotation as removed for a problem.""" + kb_annotation.problem = user_problem + kb_annotation.save() + + kb_input = [] # Empty list should mark existing as removed + + serializer = input_serializer_with_user(annotator) + serializer._handle_kb_annotations(user_problem, kb_input) + + # Verify KB annotation was marked as removed + kb_annotation.refresh_from_db() + assert kb_annotation.removed_at is not None + assert kb_annotation.removed_by == annotator + + +@pytest.mark.django_db +def test_create_and_update_multiple_kb_annotations( + user_problem, kb_annotation, annotator +): + """Test creating and updating multiple KB annotations for a problem.""" + kb_annotation.problem = user_problem + kb_annotation.save() + + kb_input = [ + { + "id": kb_annotation.pk, + "entity1": "updated_e1", + "entity2": "updated_e2", + "relationship": "not_equal", + }, + { + "entity1": "new_e1", + "entity2": "new_e2", + "relationship": "subset", + }, + { + "entity1": "another_e1", + "entity2": "another_e2", + "relationship": "superset", + }, + ] + + serializer = input_serializer_with_user(annotator) + serializer._handle_kb_annotations(user_problem, kb_input) + + kb_annotations = KnowledgeBaseAnnotation.objects.filter( + problem=user_problem, removed_at__isnull=True + ) + assert ( + kb_annotations.count() == 3 + ), "There should be three new active KB annotations after update." + + # Verify the updated annotation + kb_annotation.refresh_from_db() + assert kb_annotation.entity1 == "updated_e1" + assert kb_annotation.entity2 == "updated_e2" + assert kb_annotation.relationship == "not_equal" + + # Verify the new annotations + new_annotations = kb_annotations.exclude(id=kb_annotation.pk) + assert ( + new_annotations.count() == 2 + ), "There should be two new KB annotations after update." + + entities = [(kb.entity1, kb.entity2) for kb in new_annotations] + assert ("new_e1", "new_e2") in entities + assert ("another_e1", "another_e2") in entities + + +@pytest.mark.django_db +def test_create_update_and_remove_kb_annotations( + user_problem, annotator, annotator_session +): + """Test creating, updating and removing multiple KB annotations for a problem.""" + # Create initial KB annotations + kb1 = KnowledgeBaseAnnotation.objects.create( + problem=user_problem, + entity1="keep_e1", + entity2="keep_e2", + relationship="equal", + session=annotator_session, + created_by=annotator, + ) + + kb2 = KnowledgeBaseAnnotation.objects.create( + problem=user_problem, + entity1="remove_e1", + entity2="remove_e2", + relationship="equal", + session=annotator_session, + created_by=annotator, + ) + + # request = Mock(user=annotator) + kb_input = [ + { + "id": kb1.pk, + "entity1": "updated_keep_e1", + "entity2": "updated_keep_e2", + "relationship": "subset", + }, + { + "entity1": "new_e1", + "entity2": "new_e2", + "relationship": "superset", + }, + ] + + serializer = input_serializer_with_user(annotator) + serializer._handle_kb_annotations(user_problem, kb_input) + + # Verify kb1 was updated. + kb1.refresh_from_db() + assert kb1.entity1 == "updated_keep_e1" + assert kb1.entity2 == "updated_keep_e2" + assert kb1.relationship == "subset" + assert kb1.removed_at is None + + # Verify kb2 was marked as removed. + kb2.refresh_from_db() + assert kb2.removed_at is not None + assert kb2.removed_by == annotator + + # Verify new annotation was created. + active_annotations = KnowledgeBaseAnnotation.objects.filter( + problem=user_problem, removed_at__isnull=True + ) + assert active_annotations.count() == 2 + + new_annotation = active_annotations.exclude(id=kb1.pk).first() + assert new_annotation is not None + assert new_annotation.entity1 == "new_e1" + assert new_annotation.entity2 == "new_e2" + assert new_annotation.relationship == "superset" + + +@pytest.mark.django_db +def test_create_problem_with_kb_annotations(annotator): + """Test creating a problem with KB annotations through create().""" + data = { + "premises": ["A cat is running."], + "hypothesis": "A cat is moving.", + "kbItems": [ + { + "entity1": "cat", + "entity2": "feline", + "relationship": "equal", + "notes": "Test note", + }, + { + "entity1": "running", + "entity2": "moving", + "relationship": "subset", + }, + ], + } + + serializer = input_serializer_with_user(annotator, data=data) + assert serializer.is_valid(raise_exception=True) + problem = serializer.save() + + # Verify problem was created + assert problem.pk is not None + assert problem.dataset == Problem.Dataset.USER + assert problem.hypothesis.text == "A cat is moving." + assert problem.premises.count() == 1 + assert problem.premises.first().text == "A cat is running." + + # Verify KB annotations were created + kb_annotations = KnowledgeBaseAnnotation.objects.filter(problem=problem) + assert kb_annotations.count() == 2 + + kb1 = kb_annotations.get(entity1="cat") + assert kb1.entity2 == "feline" + assert kb1.relationship == "equal" + assert kb1.notes == "Test note" + assert kb1.created_by == annotator + + kb2 = kb_annotations.get(entity1="running") + assert kb2.entity2 == "moving" + assert kb2.relationship == "subset" + assert kb2.created_by == annotator + + +@pytest.mark.django_db +def test_create_problem_with_multiple_premises_and_kb(annotator): + """Test creating a problem with multiple premises.""" + data = { + "premises": ["Birds can fly.", "Penguins are birds."], + "hypothesis": "Penguins can fly.", + } + + serializer = input_serializer_with_user(annotator, data=data) + assert serializer.is_valid(raise_exception=True) + problem = serializer.save() + + assert problem.premises.count() == 2 + premise_texts = [p.text for p in problem.premises.all()] + assert "Birds can fly." in premise_texts + assert "Penguins are birds." in premise_texts + assert problem.hypothesis.text == "Penguins can fly." + + +@pytest.mark.django_db +def test_update_user_problem_with_new_kb_annotations(user_problem, annotator): + """Test updating a user problem adds new KB annotations through update().""" + data = { + "id": user_problem.pk, + "premises": ["Updated premise."], + "hypothesis": "Updated hypothesis.", + "kbItems": [ + { + "entity1": "new_entity1", + "entity2": "new_entity2", + "relationship": "subset", + } + ], + } + + assert ( + KnowledgeBaseAnnotation.objects.filter(problem=user_problem).count() == 0 + ), "Precondition: user_problem should have no KB annotations." + + serializer = input_serializer_with_user(annotator, data=data, instance=user_problem) + assert serializer.is_valid(raise_exception=True) + updated_problem = serializer.save() + + # Verify problem was updated + assert updated_problem.pk == user_problem.pk + assert updated_problem.hypothesis.text == "Updated hypothesis." + assert updated_problem.premises.first().text == "Updated premise." + + # Verify KB annotation was added + kb_annotations = KnowledgeBaseAnnotation.objects.filter(problem=updated_problem) + assert kb_annotations.count() == 1 + kb_annotation = kb_annotations.first() + assert kb_annotation is not None + assert kb_annotation.entity1 == "new_entity1" + + +@pytest.mark.django_db +def test_update_non_user_problem_adds_kb_only(non_user_problem, annotator): + """Test updating a non-user problem only adds KB annotations, not other fields.""" + original_hypothesis = non_user_problem.hypothesis.text + original_premise_count = non_user_problem.premises.count() + + data = { + "id": non_user_problem.pk, + "premises": ["This should be ignored."], + "hypothesis": "This should also be ignored.", + "kbItems": [ + { + "entity1": "entity1", + "entity2": "entity2", + "relationship": "equal", + } + ], + } + + # Preconditions + assert KnowledgeBaseAnnotation.objects.filter(problem=non_user_problem).count() == 0, "Non_user_problem should have no KB annotations to begin with." + assert original_hypothesis != data["hypothesis"] + + serializer = input_serializer_with_user(annotator, data=data, instance=non_user_problem) + assert serializer.is_valid(raise_exception=True) + updated_problem = serializer.save() + + # Verify problem fields were not updated + assert updated_problem.hypothesis.text == original_hypothesis + assert updated_problem.premises.count() == original_premise_count + + # Verify KB annotation was added + kb_annotations = KnowledgeBaseAnnotation.objects.filter(problem=updated_problem) + assert kb_annotations.count() == 1 + diff --git a/backend/problem/serializers_test_utils.py b/backend/problem/serializers_test_utils.py new file mode 100644 index 0000000..c357c98 --- /dev/null +++ b/backend/problem/serializers_test_utils.py @@ -0,0 +1,16 @@ +from unittest.mock import Mock +from problem.serializers import ProblemInputSerializer +from user.models import User + + +def input_serializer_with_user( + user: User, data: dict | None = None, instance=None +) -> ProblemInputSerializer: + """ + Helper function to create a ProblemInputSerializer with a mock request + containing the specified user. The data argument can be used to provide + initial data for the serializer. + """ + return ProblemInputSerializer( + data=data, instance=instance, context={"request": Mock(user=user)} + ) diff --git a/backend/problem/views/problem.py b/backend/problem/views/problem.py index 39f9a0e..ea607b8 100644 --- a/backend/problem/views/problem.py +++ b/backend/problem/views/problem.py @@ -25,7 +25,7 @@ def has_permission(self, request, view): class EditProblemPermission(IsAuthenticated): def has_permission(self, request, view): - return super().has_permission(request, view) and request.user.can_edit_problem + return super().has_permission(request, view) and (request.user.can_edit_problem or request.user.can_edit_kb) class ProblemView(ModelViewSet): @@ -155,7 +155,7 @@ def _handle_update_create_problem( status = HTTP_201_CREATED else: problem_instance = get_object_or_404( - Problem, id=problem_id, dataset=Problem.Dataset.USER + Problem, id=problem_id ) problem: Problem = serializer.update(problem_instance, validated_input) status = HTTP_200_OK diff --git a/backend/problem/views/problem_test.py b/backend/problem/views/problem_test.py index 09ab45c..364fb67 100644 --- a/backend/problem/views/problem_test.py +++ b/backend/problem/views/problem_test.py @@ -136,7 +136,7 @@ def test_unauthenticated_user_cannot_update_problem( def test_visitor_cannot_update_problem( self, client, visitor, sample_problem, problem_input_data ): - """Visitors should not be able to update problems.""" + """Visitors should not be able to update problems or KB items.""" client.force_login(user=visitor) response = client.patch( f"/api/problem/{sample_problem.id}/", @@ -145,17 +145,17 @@ def test_visitor_cannot_update_problem( ) assert response.status_code == status.HTTP_403_FORBIDDEN - def test_annotator_cannot_update_problem( + def test_annotator_can_update_problem( self, client, annotator, sample_problem, problem_input_data ): - """Annotators should not be able to update problems.""" + """Annotators should be able to update KB items (but not problems).""" client.force_login(user=annotator) response = client.patch( f"/api/problem/{sample_problem.id}/", problem_input_data, content_type="application/json", ) - assert response.status_code == status.HTTP_403_FORBIDDEN + assert response.status_code == status.HTTP_200_OK def test_master_annotator_can_update_problem( self, client, master_annotator, sample_problem, problem_input_data diff --git a/backend/user/models.py b/backend/user/models.py index d85489b..7b37efb 100644 --- a/backend/user/models.py +++ b/backend/user/models.py @@ -58,6 +58,16 @@ def can_create_problem(self) -> bool: """ return self.has_perm("problem.add_problem") + @property + def can_edit_kb(self) -> bool: + """ + Determines whether the user can edit knowledge base items. + + This includes adding, editing and deleting, as these are all part of + the same permission in our current implementation. + """ + return self.has_perm("annotation.change_knowledgebaseannotation") + def can_remove_label(self, label_annotation: LabelAnnotation) -> bool: """ Determines whether the user can remove a specific label (as part of an annotation). diff --git a/backend/user/permissions.py b/backend/user/permissions.py index 1e4a03d..cb37fc2 100644 --- a/backend/user/permissions.py +++ b/backend/user/permissions.py @@ -1,11 +1,9 @@ # Django permissions are uniquely identified by their combination of a `content_type__app_label` and a `codename`. ANNOTATOR_PERMISSIONS = [ ("problem", "view_silver_problems"), - ("annotation", "add_knowledgebaseannotation"), - ("annotation", "add_labelannotation"), ("annotation", "change_knowledgebaseannotation"), + ("annotation", "add_labelannotation"), ("annotation", "change_labelannotation"), - ("annotation", "delete_knowledgebaseannotation"), ("annotation", "delete_own_labelannotation"), ] diff --git a/backend/user/serializers.py b/backend/user/serializers.py index 4f2ee5f..4f2e8a4 100644 --- a/backend/user/serializers.py +++ b/backend/user/serializers.py @@ -11,6 +11,7 @@ class CustomUserDetailsSerializer(UserDetailsSerializer): canCreateProblem = serializers.BooleanField( read_only=True, source="can_create_problem" ) + canEditKb = serializers.BooleanField(read_only=True, source="can_edit_kb") class Meta(UserDetailsSerializer.Meta): @@ -24,5 +25,6 @@ class Meta(UserDetailsSerializer.Meta): "role", "canEditProblem", "canCreateProblem", + "canEditKb", ) read_only_fields = ["isStaff", "id", "email"] diff --git a/backend/user/tests/test_user_views.py b/backend/user/tests/test_user_views.py index 5731cc5..013cd60 100644 --- a/backend/user/tests/test_user_views.py +++ b/backend/user/tests/test_user_views.py @@ -14,6 +14,7 @@ def test_user_details(user_client, user_data): "role": "visitor", "canCreateProblem": False, "canEditProblem": False, + "canEditKb": False, } diff --git a/frontend/src/app/annotate/annotation-input/annotation-input.component.html b/frontend/src/app/annotate/annotation-input/annotation-input.component.html index dd23196..6b1ce9e 100644 --- a/frontend/src/app/annotate/annotation-input/annotation-input.component.html +++ b/frontend/src/app/annotate/annotation-input/annotation-input.component.html @@ -15,7 +15,7 @@ @if (appMode === 'browse') { @@ -34,7 +34,7 @@ Copy problem } - @if ((appMode === 'edit' || appMode === 'add') && userProblem) { + @if (this.form.dirty) { @@ -7,7 +7,7 @@ class="form-label h6 fw-bold d-flex align-items-center justify-content-between" > Knowledge base items - @if (appMode === 'add' || appMode === 'edit') { + @if (canEdit) { - @if (appMode === 'add' || appMode === 'edit') { + @if (canEdit) { = { @@ -26,7 +26,7 @@ const relationshipDisplayMapping: Record = { styleUrls: ["./knowledge-base-form.component.scss"], }) export class KnowledgeBaseFormComponent { - private problemService = inject(ProblemService); + private authService = inject(AuthService); public form = input.required(); @@ -35,7 +35,9 @@ export class KnowledgeBaseFormComponent { public faPlus = faPlus; public faTrash = faTrash; - public appMode$ = this.problemService.appMode$; + public canEditKb$ = this.authService.currentUser$.pipe( + map(user => user?.canEditKb ?? false) + ); public addKnowledgeBaseItem(): void { const newItem = new FormGroup({ @@ -69,5 +71,6 @@ export class KnowledgeBaseFormComponent { public removeKnowledgeBaseItem(index: number): void { this.form().controls.kbItems.removeAt(index); + this.form().markAsDirty(); } } diff --git a/frontend/src/app/menu/user-menu/user-menu.component.spec.ts b/frontend/src/app/menu/user-menu/user-menu.component.spec.ts index 1010491..ee9275b 100644 --- a/frontend/src/app/menu/user-menu/user-menu.component.spec.ts +++ b/frontend/src/app/menu/user-menu/user-menu.component.spec.ts @@ -23,6 +23,7 @@ const fakeUserResponse: UserResponse = { role: UserRole.VISITOR, canCreateProblem: false, canEditProblem: false, + canEditKb: false }; const fakeAdminResponse: UserResponse = { @@ -35,6 +36,7 @@ const fakeAdminResponse: UserResponse = { role: UserRole.MASTER_ANNOTATOR, canCreateProblem: true, canEditProblem: true, + canEditKb: false }; describe("UserMenuComponent", () => { diff --git a/frontend/src/app/user/models/user.ts b/frontend/src/app/user/models/user.ts index 58dbae0..c3a2c8e 100644 --- a/frontend/src/app/user/models/user.ts +++ b/frontend/src/app/user/models/user.ts @@ -8,6 +8,7 @@ export interface UserResponse { role: string; canEditProblem: boolean; canCreateProblem: boolean; + canEditKb: boolean; } // Corresponds to frontend user type. @@ -29,6 +30,7 @@ export class User { public role: UserRole, public canEditProblem: boolean, public canCreateProblem: boolean, + public canEditKb: boolean, ) { } } diff --git a/frontend/src/app/user/user-settings/user-settings.component.spec.ts b/frontend/src/app/user/user-settings/user-settings.component.spec.ts index e153d92..dac6dcb 100644 --- a/frontend/src/app/user/user-settings/user-settings.component.spec.ts +++ b/frontend/src/app/user/user-settings/user-settings.component.spec.ts @@ -26,6 +26,7 @@ const fakeUser: User = { role: UserRole.VISITOR, canCreateProblem: false, canEditProblem: false, + canEditKb: false, }; @Injectable({ providedIn: "root" }) diff --git a/frontend/src/app/user/utils.spec.ts b/frontend/src/app/user/utils.spec.ts index 816f0ff..1079800 100644 --- a/frontend/src/app/user/utils.spec.ts +++ b/frontend/src/app/user/utils.spec.ts @@ -42,6 +42,7 @@ describe("User utils", () => { role: "visitor", canCreateProblem: false, canEditProblem: false, + canEditKb: false, }; const user = parseUserData(result); expect(user).toBeInstanceOf(User); diff --git a/frontend/src/app/user/utils.ts b/frontend/src/app/user/utils.ts index bf43f57..de47913 100644 --- a/frontend/src/app/user/utils.ts +++ b/frontend/src/app/user/utils.ts @@ -32,6 +32,7 @@ export const parseUserData = (result: UserResponse | null): User | null => { result.role, result.canEditProblem, result.canCreateProblem, + result.canEditKb, ); };