From 5069c5b5eca99ade073a509bd2afecabc441bd20 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 24 May 2022 22:49:16 +0200 Subject: [PATCH 01/14] Add new field on snapshot, add migration --- server/books/migrations/0033_snapshot_title.py | 18 ++++++++++++++++++ server/books/models/snapshot.py | 1 + 2 files changed, 19 insertions(+) create mode 100644 server/books/migrations/0033_snapshot_title.py diff --git a/server/books/migrations/0033_snapshot_title.py b/server/books/migrations/0033_snapshot_title.py new file mode 100644 index 00000000..59ce72d4 --- /dev/null +++ b/server/books/migrations/0033_snapshot_title.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.13 on 2022-05-24 19:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('books', '0032_auto_20211213_1342'), + ] + + operations = [ + migrations.AddField( + model_name='snapshot', + name='title', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/server/books/models/snapshot.py b/server/books/models/snapshot.py index e666d74b..cef603bb 100644 --- a/server/books/models/snapshot.py +++ b/server/books/models/snapshot.py @@ -119,6 +119,7 @@ class Snapshot(models.Model): 'objectives.Objective', related_name='hidden_for_snapshots' ) + title = models.CharField(max_length=255, blank=True, null=True) objects = SnapshotManager() From b4fbfce68499e7afdf52b4c8d3e9072e25078583 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 24 May 2022 22:49:39 +0200 Subject: [PATCH 02/14] Add cypress tests for new features Tests are for MS-375 and 373 --- .../frontend/modules/snapshots.spec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/client/cypress/integration/frontend/modules/snapshots.spec.js b/client/cypress/integration/frontend/modules/snapshots.spec.js index 4ce3dd67..479ecf98 100644 --- a/client/cypress/integration/frontend/modules/snapshots.spec.js +++ b/client/cypress/integration/frontend/modules/snapshots.spec.js @@ -88,4 +88,21 @@ describe('Snapshot', () => { cy.getByDataCy('module-title').should('exist'); cy.getByDataCy('snapshot-header').should('not.exist'); }); + + it('Renames Snapshot', () => { + cy.visit('module/miteinander-reden/snapshots'); + cy.getByDataCy('snapshot-title').should('equal', 'Old Title'); + cy.getByDataCy('rename-snapshot-button').click(); + cy.getByDataCy('snapshot-title-input').type('New Title'); + cy.getByDataCy('save-button').click(); + cy.getByDataCy('snapshot-title').should('equal', 'New Title'); + }); + + it('Renames Snapshot', () => { + cy.visit('module/miteinander-reden/snapshots'); + cy.getByDataCy('snapshot').should('have.length', 1); + cy.getByDataCy('delete-snapshot-button').click(); + cy.getByDataCy('confirm-button').click(); + cy.getByDataCy('snapshot').should('have.length', 0); + }); }); From 2fe7a684c5dec4a2aa62a33ce36c7498af144da0 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 25 May 2022 21:53:49 +0200 Subject: [PATCH 03/14] Update base client usage --- server/books/tests/test_snapshots.py | 52 ++++++++++++++-------------- server/core/tests/base_test.py | 4 +++ 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/server/books/tests/test_snapshots.py b/server/books/tests/test_snapshots.py index febabb84..004f3c80 100644 --- a/server/books/tests/test_snapshots.py +++ b/server/books/tests/test_snapshots.py @@ -89,8 +89,8 @@ class CreateSnapshotTestCase(SkillboxTestCase): result = client.execute(MODULE_QUERY, variables={ 'slug': self.module.slug }) - self.assertIsNone(result.get('errors')) - module = result.get('data').get('module') + self.assertIsNone(result.errors) + module = result.data.get('module') chapter = module.get('chapters')[0] self.assertIsNotNone(chapter) content_blocks = chapter.get('contentBlocks') @@ -144,8 +144,8 @@ class CreateSnapshotTestCase(SkillboxTestCase): self._test_module_visibility(self.client, 'skillbox') def _test_create_snapshot(self, result, num_snapshots=1): - self.assertIsNone(result.get('errors')) - snapshot = result.get('data').get('createSnapshot').get('snapshot') + self.assertIsNone(result.errors) + snapshot = result.data.get('createSnapshot').get('snapshot') chapter = snapshot.get('chapters')[0] self.assertIsNotNone(snapshot.get('created')) @@ -206,7 +206,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): 'selectedClass': to_global_id('SchoolClassNode', self.second_class.pk), } }) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) module = self._test_module_visibility(self.get_client(self.teacher2), self.second_class_name) original_creator = module['chapters'][0]['contentBlocks'][2].get('originalCreator') self.assertIsNotNone(original_creator) @@ -219,8 +219,8 @@ class CreateSnapshotTestCase(SkillboxTestCase): snapshot_result = self.client.execute(SNAPSHOT_MODULE_QUERY, variables={ 'id': id }) - self.assertIsNone(snapshot_result.get('errors')) - snapshot = snapshot_result.get('data').get('snapshot') + self.assertIsNone(snapshot_result.errors) + snapshot = snapshot_result.data.get('snapshot') chapters = snapshot.get('chapters') self.assertEqual(len(chapters), 2) chapter = chapters[0] @@ -259,7 +259,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): 'selectedClass': to_global_id('SchoolClassNode', third_class.pk), } }) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) snapshot_id = result['data']['createSnapshot']['snapshot']['id'] result = self.client.execute(APPLY_SNAPSHOT_MUTATION, variables={ @@ -268,12 +268,12 @@ class CreateSnapshotTestCase(SkillboxTestCase): 'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk), } }) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) result = self.client.execute(MODULE_QUERY, variables={ 'slug': self.module.slug }) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) module = result['data']['module'] chapter1, chapter2 = module['chapters'] visible, hidden, custom, custom_hidden = chapter1['contentBlocks'] @@ -286,7 +286,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): result = self.graphene_client.execute(MODULE_QUERY, variables={ 'slug': self.module.slug }) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) chapter = result['data']['module']['chapters'][0] self.assertEqual(len(chapter['contentBlocks']), 4) @@ -296,7 +296,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): 'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk), } }) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) snapshot_id = result['data']['createSnapshot']['snapshot']['id'] teacher2 = User.objects.get(username='teacher2') @@ -307,16 +307,15 @@ class CreateSnapshotTestCase(SkillboxTestCase): 'selectedClass': to_global_id('SchoolClassNode', self.second_class.pk), } }) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) result = self.graphene_client.execute(MODULE_QUERY, variables={ 'slug': self.module.slug }) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) chapter = result['data']['module']['chapters'][0] self.assertEqual(len(chapter['contentBlocks']), 4) - def test_snapshot_chapter_visibility_after_apply(self): result = self.graphene_client.execute(CREATE_SNAPSHOT_MUTATION, variables={ 'input': { @@ -335,10 +334,11 @@ class CreateSnapshotTestCase(SkillboxTestCase): a_result = self.get_client(self.teacher2).execute(MODULE_QUERY, variables={ 'slug': self.module.slug }) - self.assertIsNone(a_result.get('errors')) + self.assertIsNone(a_result.errors) a_chapter = a_result['data']['module']['chapters'][0] self.assertEqual(self.second_class_name in map(lambda x: x['name'], a_chapter['titleHiddenFor']), hidden) - self.assertEqual(self.second_class_name in map(lambda x: x['name'], a_chapter['descriptionHiddenFor']), hidden) + self.assertEqual(self.second_class_name in map(lambda x: x['name'], a_chapter['descriptionHiddenFor']), + hidden) assert_chapter_hidden(True) @@ -348,7 +348,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): 'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk), } }) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) snapshot = result['data']['createSnapshot']['snapshot'] snapshot_id = snapshot['id'] chapter = snapshot['chapters'][0] @@ -361,7 +361,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): 'selectedClass': to_global_id('SchoolClassNode', self.second_class.pk), } }) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) assert_chapter_hidden(False) @@ -384,8 +384,8 @@ class SnapshotTestCase(SkillboxTestCase): result = self.client.execute(MODULE_SNAPSHOTS_QUERY, variables={ "slug": self.slug }) - self.assertIsNone(result.get('errors')) - snapshots = result['data']['module']['snapshots'] + self.assertIsNone(result.errors) + snapshots = result.data['module']['snapshots'] self.assertEqual(len(snapshots), 1) self.assertEqual(snapshots[0]['creator'], f'{self.teacher.first_name} {self.teacher.last_name}') @@ -397,8 +397,8 @@ class SnapshotTestCase(SkillboxTestCase): 'shared': True } }) - self.assertIsNone(result.get('errors')) - data = result['data']['shareSnapshot'] + self.assertIsNone(result.errors) + data = result.data['shareSnapshot'] self.assertTrue(data['success']) self.assertTrue(data['snapshot']['shared']) snapshot = Snapshot.objects.get(pk=self.snapshot.pk) @@ -413,7 +413,7 @@ class SnapshotTestCase(SkillboxTestCase): 'shared': True } }) - self.assertIsNotNone(result.get('errors')) + self.assertIsNotNone(result.errors) def test_snapshot_without_creator(self): self.snapshot.creator = None @@ -422,5 +422,5 @@ class SnapshotTestCase(SkillboxTestCase): result = self.client.execute(MODULE_SNAPSHOTS_QUERY, variables={ "slug": self.slug }) - self.assertIsNone(result.get('errors')) - self.assertEqual(len(result.get('data').get('module').get('snapshots')), 1) + self.assertIsNone(result.errors) + self.assertEqual(len(result.data.get('module').get('snapshots')), 1) diff --git a/server/core/tests/base_test.py b/server/core/tests/base_test.py index 0f349f81..5697ed98 100644 --- a/server/core/tests/base_test.py +++ b/server/core/tests/base_test.py @@ -10,6 +10,10 @@ class GQLClient(Client): def get_result(self, *args, **kwargs): return GQLResult(self.execute(*args, **kwargs)) + def execute(self, *args, **kwargs): + res = super(GQLClient, self).execute(*args, **kwargs) + return GQLResult(res) + class SkillboxTestCase(TestCase): From 1f7ff13a13c6e46d0fe6b77ecbff644edaf4e2c5 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 25 May 2022 21:55:17 +0200 Subject: [PATCH 04/14] Add unit tests for new features --- server/books/tests/queries.py | 38 ++++++++++++++++++++-------- server/books/tests/test_snapshots.py | 32 ++++++++++++++++++++++- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/server/books/tests/queries.py b/server/books/tests/queries.py index 69df53cc..95fc7123 100644 --- a/server/books/tests/queries.py +++ b/server/books/tests/queries.py @@ -113,17 +113,6 @@ query SnapshotDetail($id: ID!) { } """ -SHARE_SNAPSHOT_MUTATION = """ -mutation ShareSnapshot($input: ShareSnapshotInput!) { - shareSnapshot(input: $input) { - success - snapshot { - shared - } - } -} -""" - MODULE_SNAPSHOTS_QUERY = """ query SnapshotQuery($slug: String!) { module(slug: $slug) { @@ -136,3 +125,30 @@ query SnapshotQuery($slug: String!) { } } """ + +SHARE_SNAPSHOT_MUTATION = """ +mutation ShareSnapshot($input: ShareSnapshotInput!) { + shareSnapshot(input: $input) { + success + snapshot { + shared + } + } +} +""" + +UPDATE_SNAPSHOT_MUTATION = """ +mutation UpdateSnapshot($input: UpdateSnapshotInput!) { + updateSnapshot(input: $input) { + snapshot { + ...on SnapshotNode { + title + id + } + ...on NotOwner { + reason + } + } + } +} +""" diff --git a/server/books/tests/test_snapshots.py b/server/books/tests/test_snapshots.py index 004f3c80..f60200c6 100644 --- a/server/books/tests/test_snapshots.py +++ b/server/books/tests/test_snapshots.py @@ -7,7 +7,7 @@ from api.utils import get_object from books.factories import ModuleFactory, ChapterFactory, ContentBlockFactory from books.models import Snapshot, ChapterSnapshot from books.tests.queries import MODULE_QUERY, SNAPSHOT_MODULE_QUERY, CREATE_SNAPSHOT_MUTATION, APPLY_SNAPSHOT_MUTATION, \ - MODULE_SNAPSHOTS_QUERY, SHARE_SNAPSHOT_MUTATION + MODULE_SNAPSHOTS_QUERY, SHARE_SNAPSHOT_MUTATION, UPDATE_SNAPSHOT_MUTATION from core.tests.base_test import SkillboxTestCase from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory from users.factories import SchoolClassFactory @@ -424,3 +424,33 @@ class SnapshotTestCase(SkillboxTestCase): }) self.assertIsNone(result.errors) self.assertEqual(len(result.data.get('module').get('snapshots')), 1) + + + def _setup_title_change(self): + self.assertIsNone(self.snapshot.title) + new_title = 'New Snapshot Title' + result = self.get_client().execute(UPDATE_SNAPSHOT_MUTATION, variables={ + 'input': { + 'id': to_global_id('Snapshot', self.snapshot.id), + 'title': new_title + } + }) + return result + + def test_update_snapshot_title(self): + result = self._setup_title_change() + self.assertIsNone(result.errors) + self.assertEqual(result.data.get('updateSnapshot').get('snapshot').get('title'), 'New Snapshot Title') + + def test_update_snapshot_not_owner_fails(self): + self.snapshot.creator = self.teacher2 + self.snapshot.save() + result = self._setup_title_change() + self.assertIsNone(result.errors) + self.assertEqual(result.data.get('updateSnapshot').get('snapshot').get('reason'), 'Not the owner') + + def test_delete_snapshot(self): + self.assertTrue(False) + + def test_delete_snapshot_not_owner_fails(self): + self.assertTrue(False) From b12d1c1a9f48a9a598b30a1f8decfa37e7334025 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 25 May 2022 21:55:55 +0200 Subject: [PATCH 05/14] Add mutation to update a snapshot --- server/books/schema/mutations/__init__.py | 3 +- server/books/schema/mutations/snapshot.py | 36 ++++++++++++++ server/books/schema/nodes/snapshot.py | 2 +- server/schema.graphql | 58 +++++++++++------------ 4 files changed, 66 insertions(+), 33 deletions(-) diff --git a/server/books/schema/mutations/__init__.py b/server/books/schema/mutations/__init__.py index fab3c1e9..2e523e34 100644 --- a/server/books/schema/mutations/__init__.py +++ b/server/books/schema/mutations/__init__.py @@ -1,7 +1,7 @@ from books.schema.mutations.chapter import UpdateChapterVisibility from books.schema.mutations.contentblock import MutateContentBlock, AddContentBlock, DeleteContentBlock from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule, SyncModuleVisibility -from books.schema.mutations.snapshot import CreateSnapshot, ApplySnapshot, ShareSnapshot +from books.schema.mutations.snapshot import CreateSnapshot, ApplySnapshot, ShareSnapshot, UpdateSnapshot from books.schema.mutations.topic import UpdateLastTopic @@ -17,3 +17,4 @@ class BookMutations(object): create_snapshot = CreateSnapshot.Field() apply_snapshot = ApplySnapshot.Field() share_snapshot = ShareSnapshot.Field() + update_snapshot = UpdateSnapshot.Field() diff --git a/server/books/schema/mutations/snapshot.py b/server/books/schema/mutations/snapshot.py index 6380e622..29d76719 100644 --- a/server/books/schema/mutations/snapshot.py +++ b/server/books/schema/mutations/snapshot.py @@ -2,6 +2,7 @@ import graphene from django.db.models import Q from graphene import relay +from api.types import FailureNode from api.utils import get_object from books.models import Module, ContentBlock, Chapter from books.models.snapshot import Snapshot @@ -9,6 +10,19 @@ from books.schema.nodes import SnapshotNode, ModuleNode from users.models import SchoolClass +class NotOwner(graphene.ObjectType): + class Meta: + interfaces = (FailureNode,) + + +NotOwnerFailure = NotOwner(reason="Not the owner") + + +class UpdateSnapshotResult(graphene.Union): + class Meta: + types = (SnapshotNode, NotOwner,) + + class CreateSnapshot(relay.ClientIDMutation): class Input: module = graphene.String(required=True) @@ -30,6 +44,28 @@ class CreateSnapshot(relay.ClientIDMutation): return cls(snapshot=snapshot, success=True) +class UpdateSnapshot(relay.ClientIDMutation): + class Input: + id = graphene.ID(required=True) + title = graphene.String() + + snapshot = graphene.Field(UpdateSnapshotResult) + + @classmethod + def mutate_and_get_payload(cls, root, info, **args): + id = args.get('id') + title = args.get('title') + user = info.context.user + + snapshot = get_object(Snapshot, id) + if snapshot.creator != user: + return cls(snapshot=NotOwnerFailure) + if title is not None: + snapshot.title = title + snapshot.save() + return cls(snapshot=snapshot) + + class ApplySnapshot(relay.ClientIDMutation): class Input: snapshot = graphene.ID(required=True) diff --git a/server/books/schema/nodes/snapshot.py b/server/books/schema/nodes/snapshot.py index 53707b9e..a2cc998f 100644 --- a/server/books/schema/nodes/snapshot.py +++ b/server/books/schema/nodes/snapshot.py @@ -141,7 +141,7 @@ class SnapshotNode(DjangoObjectType): @staticmethod def resolve_title(parent: Snapshot, info, **kwargs): - return f'Snapshot {parent.id}' + return parent.title if parent.title is not None else f'Snapshot {parent.id}' @staticmethod def resolve_meta_title(parent, info, **kwargs): diff --git a/server/schema.graphql b/server/schema.graphql index a42045bb..99c5eed1 100644 --- a/server/schema.graphql +++ b/server/schema.graphql @@ -301,16 +301,6 @@ type ContentBlockNode implements Node & ContentBlockInterface { originalCreator: PublicUserNode } -type ContentBlockNodeConnection { - pageInfo: PageInfo! - edges: [ContentBlockNodeEdge]! -} - -type ContentBlockNodeEdge { - node: ContentBlockNode - cursor: String! -} - input ContentElementInput { id: String type: InputTypes! @@ -469,6 +459,10 @@ type DuplicateName { reason: String } +interface FailureNode { + reason: String +} + type FieldError { code: String } @@ -683,6 +677,7 @@ type Mutation { createSnapshot(input: CreateSnapshotInput!): CreateSnapshotPayload applySnapshot(input: ApplySnapshotInput!): ApplySnapshotPayload shareSnapshot(input: ShareSnapshotInput!): ShareSnapshotPayload + updateSnapshot(input: UpdateSnapshotInput!): UpdateSnapshotPayload _debug: DjangoDebug } @@ -706,6 +701,10 @@ type NotFound { reason: String } +type NotOwner implements FailureNode { + reason: String +} + type NoteNode implements Node { id: ID! text: String! @@ -756,16 +755,6 @@ type ObjectiveNode implements Node { mine: Boolean } -type ObjectiveNodeConnection { - pageInfo: PageInfo! - edges: [ObjectiveNodeEdge]! -} - -type ObjectiveNodeEdge { - node: ObjectiveNode - cursor: String! -} - type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! @@ -965,20 +954,14 @@ type SnapshotContentBlockNode implements Node & ContentBlockInterface { } type SnapshotNode implements Node { - id: ID! - module: ModuleNode! - chapters: [SnapshotChapterNode] - hiddenContentBlocks(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ContentBlockNodeConnection! - created: DateTime! - creator: String! - shared: Boolean! - objectiveGroups: [SnapshotObjectiveGroupNode] - hiddenObjectives(offset: Int, before: String, after: String, first: Int, last: Int, text: String): ObjectiveNodeConnection! title: String - metaTitle: String - heroImage: String + id: ID! + chapters: [SnapshotChapterNode] changes: SnapshotChangesNode mine: Boolean + shared: Boolean! + creator: String! + objectiveGroups: [SnapshotObjectiveGroupNode] } type SnapshotNodeConnection { @@ -1431,6 +1414,19 @@ type UpdateSettingPayload { clientMutationId: String } +input UpdateSnapshotInput { + id: ID! + title: String + clientMutationId: String +} + +type UpdateSnapshotPayload { + snapshot: UpdateSnapshotResult + clientMutationId: String +} + +union UpdateSnapshotResult = SnapshotNode | NotOwner + input UpdateSolutionVisibilityInput { slug: String enabled: Boolean From af7dfad9c1ead42cf183a2bb86a5c847992dc873 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 25 May 2022 21:56:19 +0200 Subject: [PATCH 06/14] Add new types file --- server/api/types.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 server/api/types.py diff --git a/server/api/types.py b/server/api/types.py new file mode 100644 index 00000000..9afb1eaa --- /dev/null +++ b/server/api/types.py @@ -0,0 +1,5 @@ +import graphene + + +class FailureNode(graphene.Interface): + reason = graphene.String() From 22460fcf28eb4ad115862e573cb351754f996994 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 13 Jun 2022 15:37:15 +0200 Subject: [PATCH 07/14] Update unit tests --- server/assignments/tests/test_feedback.py | 24 ++++++++-------- server/assignments/tests/test_read_only.py | 8 +++--- .../tests/test_instrument_types_query.py | 2 +- server/books/tests/test_404.py | 4 +-- server/books/tests/test_content_blocks.py | 4 +-- server/books/tests/test_own_content_blocks.py | 16 +++++------ server/books/tests/test_snapshots.py | 14 +++++----- .../portfolio/tests/test_project_mutations.py | 4 +-- server/portfolio/tests/test_project_query.py | 24 ++++++++-------- server/rooms/tests/test_comments.py | 11 ++++---- server/rooms/tests/test_new_room_mutation.py | 5 ++-- .../rooms/tests/test_room_entry_mutations.py | 2 +- .../tests/test_room_visibility_mutation.py | 12 +++----- .../rooms/tests/test_room_visibility_query.py | 3 +- server/users/tests/test_school_classes.py | 18 ++++++------ server/users/tests/test_usersettings.py | 28 +++++++++---------- 16 files changed, 86 insertions(+), 93 deletions(-) diff --git a/server/assignments/tests/test_feedback.py b/server/assignments/tests/test_feedback.py index 228c057f..13e39fd0 100644 --- a/server/assignments/tests/test_feedback.py +++ b/server/assignments/tests/test_feedback.py @@ -51,7 +51,7 @@ class SubmissionFeedbackTestCase(SkillboxTestCase): }) def _fetch_assignment_student(self, user): - client = create_client(user) + client = self.get_client(user) query = ''' query AssignmentWithSubmissions($id: ID!) { assignment(id: $id) { @@ -72,7 +72,7 @@ class SubmissionFeedbackTestCase(SkillboxTestCase): }) def _fetch_assignment_teacher(self, user): - client = create_client(user) + client = self.get_client(user) query = ''' query AssignmentWithSubmissions($id: ID!) { assignment(id: $id) { @@ -116,13 +116,13 @@ class SubmissionFeedbackTestCase(SkillboxTestCase): def test_teacher_can_create_feedback(self): result = self._create_submission_feedback(self.teacher, False, 'Balalal', self.student_submission_id) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) self.assertIsNotNone( - result.get('data').get('updateSubmissionFeedback').get('updatedSubmissionFeedback').get('id')) + result.data.get('updateSubmissionFeedback').get('updatedSubmissionFeedback').get('id')) def test_student_cannot_create_feedback(self): result = self._create_submission_feedback(self.student1, False, 'Balalal', self.student_submission_id) - self.assertIsNotNone(result.get('errors')) + self.assertIsNotNone(result.errors) def test_teacher_can_update_feedback(self): assignment = AssignmentFactory( @@ -136,9 +136,9 @@ class SubmissionFeedbackTestCase(SkillboxTestCase): result = self._create_submission_feedback(self.teacher, True, 'Some', submission_feedback_id) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) - submission_feedback_response = result.get('data').get('updateSubmissionFeedback').get( + submission_feedback_response = result.data.get('updateSubmissionFeedback').get( 'updatedSubmissionFeedback') self.assertTrue(submission_feedback_response.get('final')) @@ -156,19 +156,19 @@ class SubmissionFeedbackTestCase(SkillboxTestCase): result = self._create_submission_feedback(self.teacher2, True, 'Some', submission_feedback_id) - self.assertIsNotNone(result.get('errors')) + self.assertIsNotNone(result.errors) def test_student_does_not_see_non_final_feedback(self): SubmissionFeedbackFactory(teacher=self.teacher, final=False, student_submission=self.student_submission) result = self._fetch_assignment_student(self.student1) - self.assertIsNone(result.get('data').get('submissionFeedback')) + self.assertIsNone(result.data.get('submissionFeedback')) def test_student_does_see_final_feedback(self): submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=True, student_submission=self.student_submission) result = self._fetch_assignment_student(self.student1) - self.assertEqual(result.get('data').get('assignment').get('submission').get('submissionFeedback') + self.assertEqual(result.data.get('assignment').get('submission').get('submissionFeedback') .get('text'), submission_feedback.text) def test_teacher_can_see_feedback_for_submission(self): @@ -178,7 +178,7 @@ class SubmissionFeedbackTestCase(SkillboxTestCase): self.student_submission.save() result = self._fetch_assignment_teacher(self.teacher) - self.assertEqual(result.get('data').get('assignment').get('submissions')[0].get('submissionFeedback') + self.assertEqual(result.data.get('assignment').get('submissions')[0].get('submissionFeedback') .get('text'), submission_feedback.text) def test_rogue_teacher_cannot_see_feedback(self): @@ -188,4 +188,4 @@ class SubmissionFeedbackTestCase(SkillboxTestCase): self.student_submission.save() result = self._fetch_assignment_teacher(self.teacher2) - self.assertIsNone(result.get('data').get('assignment').get('submissions')[0].get('submissionFeedback')) + self.assertIsNone(result.data.get('assignment').get('submissions')[0].get('submissionFeedback')) diff --git a/server/assignments/tests/test_read_only.py b/server/assignments/tests/test_read_only.py index b85051fe..806db0f6 100644 --- a/server/assignments/tests/test_read_only.py +++ b/server/assignments/tests/test_read_only.py @@ -32,7 +32,7 @@ class AssignmentReadOnlyTestCase(SkillboxTestCase): } } result = self.get_client(self.student1).execute(UPDATE_ASSIGNMENT_MUTATION, variables=variables) - self.assertIsNotNone(result.get('errors')) + self.assertIsNotNone(result.errors) def test_share_assignment_fails(self): variables = { @@ -46,7 +46,7 @@ class AssignmentReadOnlyTestCase(SkillboxTestCase): } } result = self.get_client(self.student1).execute(UPDATE_ASSIGNMENT_MUTATION, variables=variables) - self.assertIsNotNone(result.get('errors')) + self.assertIsNotNone(result.errors) def test_edit_feedback_fails(self): student_submission = StudentSubmissionFactory(assignment=self.assignment, student=self.student1, @@ -62,7 +62,7 @@ class AssignmentReadOnlyTestCase(SkillboxTestCase): } }) - self.assertIsNotNone(result.get('errors')) + self.assertIsNotNone(result.errors) def test_share_feedback_fails(self): @@ -79,4 +79,4 @@ class AssignmentReadOnlyTestCase(SkillboxTestCase): } }) - self.assertIsNotNone(result.get('errors')) + self.assertIsNotNone(result.errors) diff --git a/server/basicknowledge/tests/test_instrument_types_query.py b/server/basicknowledge/tests/test_instrument_types_query.py index 1073b01e..25e28b44 100644 --- a/server/basicknowledge/tests/test_instrument_types_query.py +++ b/server/basicknowledge/tests/test_instrument_types_query.py @@ -26,6 +26,6 @@ class InstrumentTypesQueryTestCase(SkillboxTestCase): InstrumentFactory(new_type=second_type) def test_instrument_types_empty_not_returned(self): - result = self.get_client().get_result(INSTRUMENT_TYPES_QUERY) + result = self.get_client().execute(INSTRUMENT_TYPES_QUERY) self.assertIsNone(result.errors) self.assertEqual(len(result.data['instrumentTypes']), 2) diff --git a/server/books/tests/test_404.py b/server/books/tests/test_404.py index 116bd2f8..66b0cc34 100644 --- a/server/books/tests/test_404.py +++ b/server/books/tests/test_404.py @@ -27,7 +27,7 @@ class ContentBlockTestCase(SkillboxTestCase): result = self.client.execute(TOPIC_QUERY, variables={ "slug": slug }) - self.assertIsNone(result.get('errors')) - topic = result.get('data').get('topic') + self.assertIsNone(result.errors) + topic = result.data.get('topic') self.assertEqual(topic.get('__typename'), 'NotFound') self.assertEqual(topic.get('reason'), 'Not Found') diff --git a/server/books/tests/test_content_blocks.py b/server/books/tests/test_content_blocks.py index d5dbb58a..bacb6546 100644 --- a/server/books/tests/test_content_blocks.py +++ b/server/books/tests/test_content_blocks.py @@ -36,8 +36,8 @@ class ContentBlockTestCase(SkillboxTestCase): result = self.client.execute(CONTENT_BLOCK_QUERY, variables={ "slug": self.slug }) - self.assertIsNone(result.get('errors')) - module = result.get('data').get('module') + self.assertIsNone(result.errors) + module = result.data.get('module') content_block = module['chapters'][0]['contentBlocks'][0] self.assertEqual(content_block['title'], 'Title') self.assertIsNotNone(content_block['type']) diff --git a/server/books/tests/test_own_content_blocks.py b/server/books/tests/test_own_content_blocks.py index 9a3f4c95..3e36ecd5 100644 --- a/server/books/tests/test_own_content_blocks.py +++ b/server/books/tests/test_own_content_blocks.py @@ -45,18 +45,18 @@ class OwnContentTestCase(SkillboxTestCase): } } """ - result = self.client.execute(chapterQuery, variables={ + result = self.get_client().execute(chapterQuery, variables={ "id": self.chapter_id }) - self.assertIsNone(result.get('errors')) - self.assertEqual(len(result.get('data').get('chapter').get('contentBlocks')), 1) + self.assertIsNone(result.errors) + self.assertEqual(len(result.data.get('chapter').get('contentBlocks')), 1) custom_content_block = ContentBlock(title='own', slug='own', user_created=True, owner=self.user) self.chapter.specific.add_child(instance=custom_content_block) result = self.client.execute(chapterQuery, variables={ "id": self.chapter_id }) - self.assertEqual(len(result.get('data').get('chapter').get('contentBlocks')), 2) + self.assertEqual(len(result.data.get('chapter').get('contentBlocks')), 2) for school_class in self.user.school_classes.all(): custom_content_block.visible_for.add(school_class) @@ -64,7 +64,7 @@ class OwnContentTestCase(SkillboxTestCase): result = self.client.execute(chapterQuery, variables={ "id": self.chapter_id }) - self.assertEqual(len(result.get('data').get('chapter').get('contentBlocks')), 2) + self.assertEqual(len(result.data.get('chapter').get('contentBlocks')), 2) def test_mutate_own_content_block(self): query = """ @@ -76,7 +76,7 @@ class OwnContentTestCase(SkillboxTestCase): } """ - res = self.get_client().get_result(query, variables={'id': self.content_block_id}) + res = self.get_client().execute(query, variables={'id': self.content_block_id}) self.assertIsNone(res.errors) self.assertEqual(res.data['contentBlock']['title'], 'bla') @@ -112,7 +112,7 @@ class OwnContentTestCase(SkillboxTestCase): } } - mutation_result = self.get_client().get_result(mutation, variables=variables) + mutation_result = self.get_client().execute(mutation, variables=variables) self.assertIsNone(mutation_result.errors) content_block = mutation_result.data['mutateContentBlock']['contentBlock'] self.assertEqual(content_block['title'], 'new title') @@ -137,7 +137,7 @@ class OwnContentTestCase(SkillboxTestCase): } } } - list_mutation_result = self.get_client().get_result(mutation, variables=other_variables) + list_mutation_result = self.get_client().execute(mutation, variables=other_variables) self.assertIsNone(list_mutation_result.errors) content_block = list_mutation_result.data['mutateContentBlock']['contentBlock'] self.assertEqual(content_block['title'], 'title for list content') diff --git a/server/books/tests/test_snapshots.py b/server/books/tests/test_snapshots.py index f60200c6..ca45fe51 100644 --- a/server/books/tests/test_snapshots.py +++ b/server/books/tests/test_snapshots.py @@ -260,7 +260,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): } }) self.assertIsNone(result.errors) - snapshot_id = result['data']['createSnapshot']['snapshot']['id'] + snapshot_id = result.data['createSnapshot']['snapshot']['id'] result = self.client.execute(APPLY_SNAPSHOT_MUTATION, variables={ 'input': { @@ -274,7 +274,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): 'slug': self.module.slug }) self.assertIsNone(result.errors) - module = result['data']['module'] + module = result.data['module'] chapter1, chapter2 = module['chapters'] visible, hidden, custom, custom_hidden = chapter1['contentBlocks'] self.assertTrue(self.skillbox_class.name not in [sc['name'] for sc in visible['hiddenFor']]) @@ -287,7 +287,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): 'slug': self.module.slug }) self.assertIsNone(result.errors) - chapter = result['data']['module']['chapters'][0] + chapter = result.data['module']['chapters'][0] self.assertEqual(len(chapter['contentBlocks']), 4) result = self.graphene_client.execute(CREATE_SNAPSHOT_MUTATION, variables={ @@ -297,7 +297,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): } }) self.assertIsNone(result.errors) - snapshot_id = result['data']['createSnapshot']['snapshot']['id'] + snapshot_id = result.data['createSnapshot']['snapshot']['id'] teacher2 = User.objects.get(username='teacher2') teacher2_client = self.get_client(user=teacher2) @@ -313,7 +313,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): 'slug': self.module.slug }) self.assertIsNone(result.errors) - chapter = result['data']['module']['chapters'][0] + chapter = result.data['module']['chapters'][0] self.assertEqual(len(chapter['contentBlocks']), 4) def test_snapshot_chapter_visibility_after_apply(self): @@ -335,7 +335,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): 'slug': self.module.slug }) self.assertIsNone(a_result.errors) - a_chapter = a_result['data']['module']['chapters'][0] + a_chapter = a_result.data['module']['chapters'][0] self.assertEqual(self.second_class_name in map(lambda x: x['name'], a_chapter['titleHiddenFor']), hidden) self.assertEqual(self.second_class_name in map(lambda x: x['name'], a_chapter['descriptionHiddenFor']), hidden) @@ -349,7 +349,7 @@ class CreateSnapshotTestCase(SkillboxTestCase): } }) self.assertIsNone(result.errors) - snapshot = result['data']['createSnapshot']['snapshot'] + snapshot = result.data['createSnapshot']['snapshot'] snapshot_id = snapshot['id'] chapter = snapshot['chapters'][0] self.assertEqual(chapter['titleHidden'], False) diff --git a/server/portfolio/tests/test_project_mutations.py b/server/portfolio/tests/test_project_mutations.py index 00417afb..d7aef96b 100644 --- a/server/portfolio/tests/test_project_mutations.py +++ b/server/portfolio/tests/test_project_mutations.py @@ -87,7 +87,7 @@ mutation UpdateProjectMutation($input: UpdateProjectInput!){ 'title': 'BAD! THIS IS BAD!' } } - result = self.get_client(self.student2).get_result(mutation, variables={ + result = self.get_client(self.student2).execute(mutation, variables={ 'input': input }) self.assertIsNotNone(result.errors) @@ -115,7 +115,7 @@ mutation UpdateProjectMutation($input: UpdateProjectInput!){ 'title': 'Good! THIS IS good!' } } - result = self.get_client(self.student).get_result(mutation, variables={ + result = self.get_client(self.student).execute(mutation, variables={ 'input': input }) self.assertIsNone(result.errors) diff --git a/server/portfolio/tests/test_project_query.py b/server/portfolio/tests/test_project_query.py index 9c66de0c..64908be3 100644 --- a/server/portfolio/tests/test_project_query.py +++ b/server/portfolio/tests/test_project_query.py @@ -17,7 +17,7 @@ query ProjectQuery($id: ID!) { class ProjectQueryTestCase(SkillboxTestCase): def _test_direct_project_access(self, user: User, should_have_access: bool): - result = self.get_client(user).get_result(project_query, variables={ + result = self.get_client(user).execute(project_query, variables={ 'id': self.project1.graphql_id }) self.assertIsNone(result.errors) @@ -57,22 +57,22 @@ class ProjectQueryTestCase(SkillboxTestCase): result = self.get_client(self.student1).execute(self.query) - self.assertIsNone(result.get('errors')) - self.assertEqual(result.get('data').get('projects')[0].get('title'), self.project1.title) + self.assertIsNone(result.errors) + self.assertEqual(result.data.get('projects')[0].get('title'), self.project1.title) def test_should_not_see_other_projects(self): self.assertEqual(Project.objects.count(), 1) result = self.get_client(self.student2).execute(self.query) - self.assertIsNone(result.get('errors')) - self.assertEqual(len(result.get('data').get('projects')), 0) + self.assertIsNone(result.errors) + self.assertEqual(len(result.data.get('projects')), 0) def test_teacher_should_not_see_unfinished_projects(self): result = self.get_client().execute(self.query) - self.assertIsNone(result.get('errors')) - self.assertEqual(len(result.get('data').get('projects')), 0) + self.assertIsNone(result.errors) + self.assertEqual(len(result.data.get('projects')), 0) def test_teacher_should_only_see_finished_projects(self): self.project1.final = True @@ -81,8 +81,8 @@ class ProjectQueryTestCase(SkillboxTestCase): result = self.get_client().execute(self.query) - self.assertIsNone(result.get('errors')) - self.assertEqual(result.get('data').get('projects')[0].get('title'), + self.assertIsNone(result.errors) + self.assertEqual(result.data.get('projects')[0].get('title'), self.project1.title) def test_other_teacher_should_not_see_projects(self): @@ -92,8 +92,8 @@ class ProjectQueryTestCase(SkillboxTestCase): result = self.get_client(self.teacher2).execute(self.query) - self.assertIsNone(result.get('errors')) - self.assertEqual(len(result.get('data').get('projects')), 0) + self.assertIsNone(result.errors) + self.assertEqual(len(result.data.get('projects')), 0) def test_direct_project_access(self): # student can access own project directly @@ -128,7 +128,7 @@ query ProjectQuery($id: ID!) { } } """ - result = self.get_client(self.student1).get_result(query, variables={ + result = self.get_client(self.student1).execute(query, variables={ 'id': self.project1.graphql_id }) self.assertEqual(result.data['project']['student']['email'], self.student1.email) diff --git a/server/rooms/tests/test_comments.py b/server/rooms/tests/test_comments.py index c87f1380..3f42034c 100644 --- a/server/rooms/tests/test_comments.py +++ b/server/rooms/tests/test_comments.py @@ -33,7 +33,7 @@ mutation AddComment($input: AddCommentInput!) { 'comment': self.text } }) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) self.assertEqual(room_entry.comments.count(), 1) comment = room_entry.comments.first() self.assertEqual(comment.text, self.text) @@ -53,8 +53,8 @@ query CommentsQuery($id: ID!) { } """ result = self.get_client().execute(query, variables={"id": self.room_entry_id}) - self.assertIsNone(result.get('errors')) - comment_node = result.get('data').get('roomEntry').get('comments')[0] + self.assertIsNone(result.errors) + comment_node = result.data.get('roomEntry').get('comments')[0] self.assertEqual(comment_node['text'], self.text) def test_get_comment_for_other_user(self): @@ -70,6 +70,5 @@ query CommentsQuery($id: ID!) { """ result = self.get_client(self.student_second_class).execute(query, variables={"id": self.room_entry_id}) - gql_result = GQLResult(result) - self.assertIsNone(gql_result.errors) - self.assertIsNone(gql_result.data.get('roomEntry')) + self.assertIsNone(result.errors) + self.assertIsNone(result.data.get('roomEntry')) diff --git a/server/rooms/tests/test_new_room_mutation.py b/server/rooms/tests/test_new_room_mutation.py index 7ed22dba..08e712c1 100644 --- a/server/rooms/tests/test_new_room_mutation.py +++ b/server/rooms/tests/test_new_room_mutation.py @@ -55,7 +55,7 @@ class NewRoomMutationTestCase(SkillboxTestCase): self.assertEqual(Room.objects.count(), 0) title = 'some title' appearance = 'blue' - res = self.get_client().execute(self.mutation, variables={ + result = self.get_client().execute(self.mutation, variables={ 'input': { 'room': { 'title': title, @@ -64,7 +64,6 @@ class NewRoomMutationTestCase(SkillboxTestCase): } } }) - result = GQLResult(res) self.assertIsNone(result.errors) room = GQLRoom(result.data.get('addRoom').get('room')) self.assertEqual(room.title, title) @@ -75,7 +74,7 @@ class NewRoomMutationTestCase(SkillboxTestCase): def test_create_new_room_for_other_school_class(self): self.assertEqual(Room.objects.count(), 0) - result = self.get_client(self.teacher2).get_result(self.mutation, variables={ + result = self.get_client(self.teacher2).execute(self.mutation, variables={ 'input': { 'room': { 'title': 'BIG NO NO!', diff --git a/server/rooms/tests/test_room_entry_mutations.py b/server/rooms/tests/test_room_entry_mutations.py index 76128b68..aaa5a00f 100644 --- a/server/rooms/tests/test_room_entry_mutations.py +++ b/server/rooms/tests/test_room_entry_mutations.py @@ -175,7 +175,7 @@ mutation AddRoomEntry($input: AddRoomEntryInput!){ 'room': self.room.graphql_id } - result = self.get_client(self.yet_another_user).get_result(mutation, variables={ + result = self.get_client(self.yet_another_user).execute(mutation, variables={ 'input': { 'roomEntry': room_entry } diff --git a/server/rooms/tests/test_room_visibility_mutation.py b/server/rooms/tests/test_room_visibility_mutation.py index 7bc40e27..b3f2ce2c 100644 --- a/server/rooms/tests/test_room_visibility_mutation.py +++ b/server/rooms/tests/test_room_visibility_mutation.py @@ -30,41 +30,37 @@ query RoomQuery ($slug: String!) { """ def test_restricted_query(self): - res = self.get_client().execute(self.query, variables={ + result = self.get_client().execute(self.query, variables={ 'slug': self.room.slug }) - result = GQLResult(res) self.assertIsNone(result.errors) self.assertFalse(result.data.get('room').get('restricted')) def test_successful_mutation(self): - res = self.get_client().execute(self.mutation, variables={ + result = self.get_client().execute(self.mutation, variables={ 'input': { 'id': self.room_id, 'restricted': True } }) - result = GQLResult(res) self.assertIsNone(result.errors) self.assertEqual(result.data.get('updateRoomVisibility').get('room').get('restricted'), True) def test_permission_denied(self): - res = self.get_client(self.student1).execute(self.mutation, variables={ + result = self.get_client(self.student1).execute(self.mutation, variables={ 'input': { 'id': self.room_id, 'restricted': True } }) - result = GQLResult(res) self.assertIsNotNone(result.errors) - res = self.get_client(self.teacher2).execute(self.mutation, variables={ + result = self.get_client(self.teacher2).execute(self.mutation, variables={ 'input': { 'id': self.room_id, 'restricted': True } }) - result = GQLResult(res) self.assertIsNotNone(result.errors) diff --git a/server/rooms/tests/test_room_visibility_query.py b/server/rooms/tests/test_room_visibility_query.py index 97b7be0d..ca1c49cf 100644 --- a/server/rooms/tests/test_room_visibility_query.py +++ b/server/rooms/tests/test_room_visibility_query.py @@ -34,10 +34,9 @@ query RoomQuery ($slug: String!) { """ def _test_room(self, user, length, restricted=True): - res = self.get_client(user).execute(self.query, variables={ + result = self.get_client(user).execute(self.query, variables={ 'slug': self.room.slug }) - result = GQLResult(res) self.assertIsNone(result.errors) room = GQLRoom(result.data.get('room')) self.assertEqual(room.restricted, restricted) diff --git a/server/users/tests/test_school_classes.py b/server/users/tests/test_school_classes.py index 79d686eb..1dad68e0 100644 --- a/server/users/tests/test_school_classes.py +++ b/server/users/tests/test_school_classes.py @@ -62,13 +62,13 @@ class ModifySchoolClassTest(SkillboxTestCase): 'name': class_name } }) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) school_class = get_object(SchoolClass, id) self.assertEqual(school_class.name, class_name) def test_update_school_class_not_in_class_fails(self): - client = Client(schema=schema) + client = self.get_client() teacher = TeacherFactory(username='conan') context = Context(user=teacher) school_class = SchoolClass.objects.get(name='skillbox') @@ -81,7 +81,7 @@ class ModifySchoolClassTest(SkillboxTestCase): } } result = client.execute(UPDATE_SCHOOL_CLASS_MUTATION, variables=variables, context=context) - self.assertIsNone(result.get('errors')) + self.assertIsNone(result.errors) def test_update_school_class_fail(self): class_name = 'Nanana' @@ -96,7 +96,7 @@ class ModifySchoolClassTest(SkillboxTestCase): 'name': class_name } }) - self.assertIsNotNone(result.get('errors')) + self.assertIsNotNone(result.errors) def test_create_school_class(self): self.assertEqual(SchoolClass.objects.count(), 2) @@ -107,8 +107,8 @@ class ModifySchoolClassTest(SkillboxTestCase): 'name': class_name } }) - self.assertIsNone(query_result.get('errors')) - result = query_result.get('data').get('createSchoolClass').get('result') + self.assertIsNone(query_result.errors) + result = query_result.data.get('createSchoolClass').get('result') self.assertEqual(result.get('__typename'), 'SchoolClassNode') id = result.get('id') self.assertEqual(SchoolClass.objects.count(), 3) @@ -129,8 +129,8 @@ class ModifySchoolClassTest(SkillboxTestCase): 'name': class_name } }) - self.assertIsNone(query_result.get('errors')) - result = query_result.get('data').get('createSchoolClass').get('result') + self.assertIsNone(query_result.errors) + result = query_result.data.get('createSchoolClass').get('result') self.assertEqual(result.get('__typename'), 'DuplicateName') reason = result.get('reason') self.assertEqual(reason, 'Dieser Name wird bereits verwendet.') @@ -144,4 +144,4 @@ class ModifySchoolClassTest(SkillboxTestCase): 'name': 'No School' } }) - self.assertIsNotNone(result.get('errors')) + self.assertIsNotNone(result.errors) diff --git a/server/users/tests/test_usersettings.py b/server/users/tests/test_usersettings.py index c262027f..eec90e00 100644 --- a/server/users/tests/test_usersettings.py +++ b/server/users/tests/test_usersettings.py @@ -50,17 +50,17 @@ class UserSettingTests(SkillboxTestCase): def test_selects_first_class_on_first_call(self): result = self.make_query() first_class = self.user.school_classes.first() - self.assertIsNone(result.get('errors')) - self.assertEqual(result.get('data').get('me').get('selectedClass').get('name'), first_class.name) - self.assertFalse(result.get('data').get('me').get('selectedClass').get('readOnly')) + self.assertIsNone(result.errors) + self.assertEqual(result.data.get('me').get('selectedClass').get('name'), first_class.name) + self.assertFalse(result.data.get('me').get('selectedClass').get('readOnly')) def test_returns_selected_class(self): selected_class = self.user.school_classes.all()[1] setting = UserSetting.objects.create(user=self.user, selected_class=selected_class) setting.save() result = self.make_query() - self.assertIsNone(result.get('errors')) - self.assertEqual(result.get('data').get('me').get('selectedClass').get('name'), + self.assertIsNone(result.errors) + self.assertEqual(result.data.get('me').get('selectedClass').get('name'), selected_class.name) def test_user_can_select_class(self): @@ -70,19 +70,19 @@ class UserSettingTests(SkillboxTestCase): selected_class = self.user.school_classes.all()[1] mutation_result = self.make_mutation(selected_class.pk) - self.assertIsNone(mutation_result.get('errors')) + self.assertIsNone(mutation_result.errors) query_result = self.make_query() - self.assertIsNone(query_result.get('errors')) - self.assertEqual(query_result.get('data').get('me').get('selectedClass').get('name'), + self.assertIsNone(query_result.errors) + self.assertEqual(query_result.data.get('me').get('selectedClass').get('name'), selected_class.name) def test_user_can_select_class_even_no_settings_exist(self): selected_class = self.user.school_classes.all()[1] mutation_result = self.make_mutation(selected_class.pk) - self.assertIsNone(mutation_result.get('errors')) + self.assertIsNone(mutation_result.errors) query_result = self.make_query() - self.assertIsNone(query_result.get('errors')) - self.assertEqual(query_result.get('data').get('me').get('selectedClass').get('name'), + self.assertIsNone(query_result.errors) + self.assertEqual(query_result.data.get('me').get('selectedClass').get('name'), selected_class.name) @@ -93,7 +93,7 @@ class UserSettingTests(SkillboxTestCase): setting.save() mutation_result = self.make_mutation(self.class3.pk) - self.assertIsNotNone(mutation_result.get('errors')) + self.assertIsNotNone(mutation_result.errors) def test_inactive_class_as_selected_class(self): selected_class = self.class2 @@ -106,6 +106,6 @@ class UserSettingTests(SkillboxTestCase): setting.save() result = self.make_query() - self.assertIsNone(result.get('errors')) - self.assertTrue(result.get('data').get('me').get('selectedClass').get('readOnly')) + self.assertIsNone(result.errors) + self.assertTrue(result.data.get('me').get('selectedClass').get('readOnly')) From 14b2905470119d2bd728a6bb5101fa8a4cb04ff0 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 13 Jun 2022 15:51:53 +0200 Subject: [PATCH 08/14] Fix failing test --- server/books/tests/test_own_content_blocks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/books/tests/test_own_content_blocks.py b/server/books/tests/test_own_content_blocks.py index 3e36ecd5..c743eaaa 100644 --- a/server/books/tests/test_own_content_blocks.py +++ b/server/books/tests/test_own_content_blocks.py @@ -53,7 +53,7 @@ class OwnContentTestCase(SkillboxTestCase): custom_content_block = ContentBlock(title='own', slug='own', user_created=True, owner=self.user) self.chapter.specific.add_child(instance=custom_content_block) - result = self.client.execute(chapterQuery, variables={ + result = self.get_client().execute(chapterQuery, variables={ "id": self.chapter_id }) self.assertEqual(len(result.data.get('chapter').get('contentBlocks')), 2) @@ -61,7 +61,7 @@ class OwnContentTestCase(SkillboxTestCase): for school_class in self.user.school_classes.all(): custom_content_block.visible_for.add(school_class) - result = self.client.execute(chapterQuery, variables={ + result = self.get_client().execute(chapterQuery, variables={ "id": self.chapter_id }) self.assertEqual(len(result.data.get('chapter').get('contentBlocks')), 2) From e5641b7f9346d5a3a5d09576d31e799bd4bdd66a Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 14 Jun 2022 11:27:32 +0200 Subject: [PATCH 09/14] Update unit test and add mutation --- server/api/types.py | 4 +++ server/books/schema/mutations/__init__.py | 3 ++- server/books/schema/mutations/snapshot.py | 32 ++++++++++++++++++++++- server/books/tests/queries.py | 13 +++++++++ server/books/tests/test_snapshots.py | 11 +++++--- 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/server/api/types.py b/server/api/types.py index 9afb1eaa..7f155b90 100644 --- a/server/api/types.py +++ b/server/api/types.py @@ -3,3 +3,7 @@ import graphene class FailureNode(graphene.Interface): reason = graphene.String() + + +class SuccessNode(graphene.Interface): + message = graphene.String() diff --git a/server/books/schema/mutations/__init__.py b/server/books/schema/mutations/__init__.py index 2e523e34..3b0c01e9 100644 --- a/server/books/schema/mutations/__init__.py +++ b/server/books/schema/mutations/__init__.py @@ -1,7 +1,7 @@ from books.schema.mutations.chapter import UpdateChapterVisibility from books.schema.mutations.contentblock import MutateContentBlock, AddContentBlock, DeleteContentBlock from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule, SyncModuleVisibility -from books.schema.mutations.snapshot import CreateSnapshot, ApplySnapshot, ShareSnapshot, UpdateSnapshot +from books.schema.mutations.snapshot import CreateSnapshot, ApplySnapshot, ShareSnapshot, UpdateSnapshot, DeleteSnapshot from books.schema.mutations.topic import UpdateLastTopic @@ -18,3 +18,4 @@ class BookMutations(object): apply_snapshot = ApplySnapshot.Field() share_snapshot = ShareSnapshot.Field() update_snapshot = UpdateSnapshot.Field() + delete_snapshot = DeleteSnapshot.Field() diff --git a/server/books/schema/mutations/snapshot.py b/server/books/schema/mutations/snapshot.py index 29d76719..767db74a 100644 --- a/server/books/schema/mutations/snapshot.py +++ b/server/books/schema/mutations/snapshot.py @@ -2,7 +2,7 @@ import graphene from django.db.models import Q from graphene import relay -from api.types import FailureNode +from api.types import FailureNode, SuccessNode from api.utils import get_object from books.models import Module, ContentBlock, Chapter from books.models.snapshot import Snapshot @@ -15,7 +15,13 @@ class NotOwner(graphene.ObjectType): interfaces = (FailureNode,) +class Success(graphene.ObjectType): + class Meta: + interfaces = (SuccessNode,) + + NotOwnerFailure = NotOwner(reason="Not the owner") +DeleteSnapshotSuccess = Success(message='Snapshot deleted successfully') class UpdateSnapshotResult(graphene.Union): @@ -23,6 +29,11 @@ class UpdateSnapshotResult(graphene.Union): types = (SnapshotNode, NotOwner,) +class DeleteSnapshotResult(graphene.Union): + class Meta: + types = (Success, NotOwner,) + + class CreateSnapshot(relay.ClientIDMutation): class Input: module = graphene.String(required=True) @@ -66,6 +77,25 @@ class UpdateSnapshot(relay.ClientIDMutation): return cls(snapshot=snapshot) +class DeleteSnapshot(relay.ClientIDMutation): + class Input: + id = graphene.ID(required=True) + + result = graphene.Field(DeleteSnapshotResult) + + @classmethod + def mutate_and_get_payload(cls, root, info, **args): + id = args.get('id') + user = info.context.user + + snapshot = get_object(Snapshot, id) + if snapshot.creator != user: + return cls(result=NotOwnerFailure) + + snapshot.delete() + return cls(result=DeleteSnapshotResult) + + class ApplySnapshot(relay.ClientIDMutation): class Input: snapshot = graphene.ID(required=True) diff --git a/server/books/tests/queries.py b/server/books/tests/queries.py index 95fc7123..ba3db692 100644 --- a/server/books/tests/queries.py +++ b/server/books/tests/queries.py @@ -152,3 +152,16 @@ mutation UpdateSnapshot($input: UpdateSnapshotInput!) { } } """ + +DELETE_SNAPSHOT_MUTATION = """ +mutation DeleteSnapshot($id: ID!) { + deleteSnapshot(id: $id) { + result { + success + ...on NotOwner { + reason + } + } + } +} +""" diff --git a/server/books/tests/test_snapshots.py b/server/books/tests/test_snapshots.py index ca45fe51..41d3ac1d 100644 --- a/server/books/tests/test_snapshots.py +++ b/server/books/tests/test_snapshots.py @@ -7,7 +7,7 @@ from api.utils import get_object from books.factories import ModuleFactory, ChapterFactory, ContentBlockFactory from books.models import Snapshot, ChapterSnapshot from books.tests.queries import MODULE_QUERY, SNAPSHOT_MODULE_QUERY, CREATE_SNAPSHOT_MUTATION, APPLY_SNAPSHOT_MUTATION, \ - MODULE_SNAPSHOTS_QUERY, SHARE_SNAPSHOT_MUTATION, UPDATE_SNAPSHOT_MUTATION + MODULE_SNAPSHOTS_QUERY, SHARE_SNAPSHOT_MUTATION, UPDATE_SNAPSHOT_MUTATION, DELETE_SNAPSHOT_MUTATION from core.tests.base_test import SkillboxTestCase from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory from users.factories import SchoolClassFactory @@ -425,7 +425,6 @@ class SnapshotTestCase(SkillboxTestCase): self.assertIsNone(result.errors) self.assertEqual(len(result.data.get('module').get('snapshots')), 1) - def _setup_title_change(self): self.assertIsNone(self.snapshot.title) new_title = 'New Snapshot Title' @@ -450,7 +449,13 @@ class SnapshotTestCase(SkillboxTestCase): self.assertEqual(result.data.get('updateSnapshot').get('snapshot').get('reason'), 'Not the owner') def test_delete_snapshot(self): - self.assertTrue(False) + result = self.get_client().execute(DELETE_SNAPSHOT_MUTATION, variables={ + 'input': { + 'id': self.snapshot + } + }) + self.assertIsNone(result.errors) + self.assertEqual(result.data.get('deleteSnapshot').get('result').get('__typename'), 'SuccessNode') def test_delete_snapshot_not_owner_fails(self): self.assertTrue(False) From 0d7b948e446165783f59f354506102ad7bea6763 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 14 Jun 2022 14:33:17 +0200 Subject: [PATCH 10/14] Fix unit tests --- server/books/schema/mutations/snapshot.py | 3 +-- server/books/tests/queries.py | 9 ++++++--- server/books/tests/test_snapshots.py | 16 +++++++++++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/server/books/schema/mutations/snapshot.py b/server/books/schema/mutations/snapshot.py index 767db74a..c9164e61 100644 --- a/server/books/schema/mutations/snapshot.py +++ b/server/books/schema/mutations/snapshot.py @@ -1,5 +1,4 @@ import graphene -from django.db.models import Q from graphene import relay from api.types import FailureNode, SuccessNode @@ -93,7 +92,7 @@ class DeleteSnapshot(relay.ClientIDMutation): return cls(result=NotOwnerFailure) snapshot.delete() - return cls(result=DeleteSnapshotResult) + return cls(result=DeleteSnapshotSuccess) class ApplySnapshot(relay.ClientIDMutation): diff --git a/server/books/tests/queries.py b/server/books/tests/queries.py index ba3db692..ccfc63a3 100644 --- a/server/books/tests/queries.py +++ b/server/books/tests/queries.py @@ -154,13 +154,16 @@ mutation UpdateSnapshot($input: UpdateSnapshotInput!) { """ DELETE_SNAPSHOT_MUTATION = """ -mutation DeleteSnapshot($id: ID!) { - deleteSnapshot(id: $id) { +mutation DeleteSnapshot($input: DeleteSnapshotInput!) { + deleteSnapshot(input: $input) { result { - success + __typename ...on NotOwner { reason } + ...on Success { + message + } } } } diff --git a/server/books/tests/test_snapshots.py b/server/books/tests/test_snapshots.py index 41d3ac1d..aeda62c7 100644 --- a/server/books/tests/test_snapshots.py +++ b/server/books/tests/test_snapshots.py @@ -451,11 +451,21 @@ class SnapshotTestCase(SkillboxTestCase): def test_delete_snapshot(self): result = self.get_client().execute(DELETE_SNAPSHOT_MUTATION, variables={ 'input': { - 'id': self.snapshot + 'id': to_global_id('Snapshot', self.snapshot.id), } }) self.assertIsNone(result.errors) - self.assertEqual(result.data.get('deleteSnapshot').get('result').get('__typename'), 'SuccessNode') + self.assertEqual(result.data.get('deleteSnapshot').get('result').get('__typename'), 'Success') def test_delete_snapshot_not_owner_fails(self): - self.assertTrue(False) + self.snapshot.creator = self.teacher2 + self.snapshot.save() + result = self.get_client().execute(DELETE_SNAPSHOT_MUTATION, variables={ + 'input': { + 'id': to_global_id('Snapshot', self.snapshot.id), + } + }) + self.assertIsNone(result.errors) + result = result.data.get('deleteSnapshot').get('result') + self.assertEqual(result.get('__typename'), 'NotOwner') + self.assertEqual(result.get('reason'), 'Not the owner') From 9f86b3c68580e47889c33a50aa21b828f3f7ec37 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 14 Jun 2022 14:38:41 +0200 Subject: [PATCH 11/14] Update schema --- server/schema.graphql | 55 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/server/schema.graphql b/server/schema.graphql index 99c5eed1..4b3f0434 100644 --- a/server/schema.graphql +++ b/server/schema.graphql @@ -301,6 +301,16 @@ type ContentBlockNode implements Node & ContentBlockInterface { originalCreator: PublicUserNode } +type ContentBlockNodeConnection { + pageInfo: PageInfo! + edges: [ContentBlockNodeEdge]! +} + +type ContentBlockNodeEdge { + node: ContentBlockNode + cursor: String! +} + input ContentElementInput { id: String type: InputTypes! @@ -434,6 +444,18 @@ type DeleteRoomPayload { clientMutationId: String } +input DeleteSnapshotInput { + id: ID! + clientMutationId: String +} + +type DeleteSnapshotPayload { + result: DeleteSnapshotResult + clientMutationId: String +} + +union DeleteSnapshotResult = Success | NotOwner + type DjangoDebug { sql: [DjangoDebugSQL] } @@ -678,6 +700,7 @@ type Mutation { applySnapshot(input: ApplySnapshotInput!): ApplySnapshotPayload shareSnapshot(input: ShareSnapshotInput!): ShareSnapshotPayload updateSnapshot(input: UpdateSnapshotInput!): UpdateSnapshotPayload + deleteSnapshot(input: DeleteSnapshotInput!): DeleteSnapshotPayload _debug: DjangoDebug } @@ -755,6 +778,16 @@ type ObjectiveNode implements Node { mine: Boolean } +type ObjectiveNodeConnection { + pageInfo: PageInfo! + edges: [ObjectiveNodeEdge]! +} + +type ObjectiveNodeEdge { + node: ObjectiveNode + cursor: String! +} + type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! @@ -954,14 +987,20 @@ type SnapshotContentBlockNode implements Node & ContentBlockInterface { } type SnapshotNode implements Node { - title: String id: ID! + module: ModuleNode! chapters: [SnapshotChapterNode] + hiddenContentBlocks(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ContentBlockNodeConnection! + created: DateTime! + creator: String! + shared: Boolean! + objectiveGroups: [SnapshotObjectiveGroupNode] + hiddenObjectives(offset: Int, before: String, after: String, first: Int, last: Int, text: String): ObjectiveNodeConnection! + title: String + metaTitle: String + heroImage: String changes: SnapshotChangesNode mine: Boolean - shared: Boolean! - creator: String! - objectiveGroups: [SnapshotObjectiveGroupNode] } type SnapshotNodeConnection { @@ -1048,6 +1087,14 @@ type SubmissionFeedbackNode implements Node { id: ID! } +type Success implements SuccessNode { + message: String +} + +interface SuccessNode { + message: String +} + type SurveyNode implements Node { id: ID! title: String! From 5c05ed2a862d931755e639bd2bb07e362c6d4b67 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 14 Jun 2022 20:27:20 +0200 Subject: [PATCH 12/14] Add components and mutations for snapshot updating and deleting Also modify the tests for MS-373 and MS-375 --- .../frontend/modules/snapshots.spec.js | 50 +++++++++-- client/src/App.vue | 2 + .../components/modules/SnapshotListItem.vue | 90 ++++++++++++++++--- .../src/components/profile/EditNameWizard.vue | 6 +- .../snapshots/EditSnapshotTitleWizard.vue | 46 ++++++++++ .../gql/mutations/snapshots/delete.gql | 13 +++ .../gql/mutations/snapshots/update.gql | 13 +++ client/src/pages/me/myTeam.vue | 1 + 8 files changed, 200 insertions(+), 21 deletions(-) create mode 100644 client/src/components/snapshots/EditSnapshotTitleWizard.vue create mode 100644 client/src/graphql/gql/mutations/snapshots/delete.gql create mode 100644 client/src/graphql/gql/mutations/snapshots/update.gql diff --git a/client/cypress/integration/frontend/modules/snapshots.spec.js b/client/cypress/integration/frontend/modules/snapshots.spec.js index 479ecf98..da66ca67 100644 --- a/client/cypress/integration/frontend/modules/snapshots.spec.js +++ b/client/cypress/integration/frontend/modules/snapshots.spec.js @@ -1,5 +1,35 @@ import module from '../../../fixtures/module.minimal'; import {getMinimalMe} from '../../../support/helpers'; +import {hasOperationName} from '../../../support/graphql'; + +const mockUpdateSnapshot = (title) => { + cy.intercept('POST', '/api/graphql', (req) => { + if (hasOperationName(req, 'UpdateSnapshot')) { + let snapshot; + if (title) { + snapshot = { + __typename: 'SnapshotNode', + id: 'U25hcHNob3ROb2RlOjQ=', + title, + }; + } else { + snapshot = { + __typename: 'NotOwner', + reason: 'Not the owner' + }; + } + req.reply({ + data: { + updateSnapshot: { + snapshot, + }, + }, + }); + } + }); + +}; + describe('Snapshot', () => { const operations = isTeacher => ({ @@ -25,8 +55,8 @@ describe('Snapshot', () => { ...module, snapshots: [ { - id: 'snapshot-id', - title: 'title', + id: 'U25hcHNob3ROb2RlOjQ=', + title: 'Old Title', created: '2020-01-01', mine: true, shared: false, @@ -90,17 +120,21 @@ describe('Snapshot', () => { }); it('Renames Snapshot', () => { + cy.mockGraphqlOps(operations(true)); + const newTitle = 'New Title'; + mockUpdateSnapshot(newTitle); cy.visit('module/miteinander-reden/snapshots'); - cy.getByDataCy('snapshot-title').should('equal', 'Old Title'); + cy.getByDataCy('snapshot-link').should('have.text', 'Old Title'); cy.getByDataCy('rename-snapshot-button').click(); - cy.getByDataCy('snapshot-title-input').type('New Title'); - cy.getByDataCy('save-button').click(); - cy.getByDataCy('snapshot-title').should('equal', 'New Title'); + cy.getByDataCy('edit-name-input').clear().type(newTitle); + cy.getByDataCy('modal-save-button').click(); + cy.getByDataCy('snapshot-link').should('have.text', 'New Title'); }); - it('Renames Snapshot', () => { + it('Deletes Snapshot', () => { + cy.mockGraphqlOps(operations(true)); cy.visit('module/miteinander-reden/snapshots'); - cy.getByDataCy('snapshot').should('have.length', 1); + cy.getByDataCy('snapshot-entry').should('have.length', 1); cy.getByDataCy('delete-snapshot-button').click(); cy.getByDataCy('confirm-button').click(); cy.getByDataCy('snapshot').should('have.length', 0); diff --git a/client/src/App.vue b/client/src/App.vue index 9dcdbda5..cd1dfe2b 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -36,6 +36,7 @@ const EditNoteWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/notes/EditNoteWizard'); const EditClassNameWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/school-class/EditClassNameWizard'); const EditTeamNameWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/profile/EditTeamNameWizard'); + const EditSnapshotTitleWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/snapshots/EditSnapshotTitleWizard'); const DefaultLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/DefaultLayout'); const SimpleLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/SimpleLayout'); const FullScreenLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/FullScreenLayout'); @@ -66,6 +67,7 @@ EditNoteWizard, EditClassNameWizard, EditTeamNameWizard, + EditSnapshotTitleWizard, ...modals }, diff --git a/client/src/components/modules/SnapshotListItem.vue b/client/src/components/modules/SnapshotListItem.vue index aa76c87a..25830075 100644 --- a/client/src/components/modules/SnapshotListItem.vue +++ b/client/src/components/modules/SnapshotListItem.vue @@ -1,6 +1,9 @@ @@ -26,7 +45,12 @@ import dateformat from '@/helpers/date-format'; import {SNAPSHOT_DETAIL} from '@/router/module.names'; import SHARE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/share.gql'; + import UPDATE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/update.gql'; + import DELETE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/delete.gql'; + import SNAPSHOTS_QUERY from 'gql/queries/moduleSnapshots.gql'; import gql from 'graphql-tag'; + import PenIcon from '@/components/icons/PenIcon'; + import TrashIcon from '@/components/icons/TrashIcon'; export default { props: { @@ -35,6 +59,10 @@ default: () => ({}), }, }, + components: { + PenIcon, + TrashIcon, + }, computed: { meta() { @@ -55,6 +83,35 @@ }, methods: { + changeTitle() { + this.$modal.open('edit-snapshot-title-wizard', {name: this.snapshot.title}) + .then((title) => { + console.log(title); + this.$apollo.mutate({ + mutation: UPDATE_SNAPSHOT_MUTATION, + variables: { + input: { + id: this.snapshot.id, + title: title, + }, + }, + update(store, {data: {updateSnapshot: {snapshot}}}) { + if (snapshot.__typename === 'SnapshotNode') { + const {id, title} = snapshot; + store.writeFragment({ + id, + fragment: gql`fragment SnapshotFragment on SnapshotNode {title}`, + data: { + title, + __typename: 'SnapshotNode', + }, + }); + } + }, + }); + }) + .catch(); + }, share() { this.$apollo.mutate({ mutation: SHARE_SNAPSHOT_MUTATION, @@ -70,10 +127,10 @@ fragment: gql`fragment SnapshotFragment on SnapshotNode { shared }`, data: { shared, - __typename: 'SnapshotNode' - } + __typename: 'SnapshotNode', + }, }); - } + }, }); }, }, @@ -100,6 +157,15 @@ &__link { @include default-link; color: $color-brand; + } + + &__icon { + @include default-icon; + } + + &__actions { + display: flex; + align-items: center; margin-left: auto; } } diff --git a/client/src/components/profile/EditNameWizard.vue b/client/src/components/profile/EditNameWizard.vue index de691aab..879c87a1 100644 --- a/client/src/components/profile/EditNameWizard.vue +++ b/client/src/components/profile/EditNameWizard.vue @@ -9,7 +9,7 @@ @@ -43,6 +43,10 @@ type: String, default: '', }, + placeholder: { + type: String, + default: 'Namen bearbeiten' + } }, components: { Modal, diff --git a/client/src/components/snapshots/EditSnapshotTitleWizard.vue b/client/src/components/snapshots/EditSnapshotTitleWizard.vue new file mode 100644 index 00000000..03eb6034 --- /dev/null +++ b/client/src/components/snapshots/EditSnapshotTitleWizard.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/client/src/graphql/gql/mutations/snapshots/delete.gql b/client/src/graphql/gql/mutations/snapshots/delete.gql new file mode 100644 index 00000000..12e80df3 --- /dev/null +++ b/client/src/graphql/gql/mutations/snapshots/delete.gql @@ -0,0 +1,13 @@ +mutation DeleteSnapshot($input: DeleteSnapshotInput!) { + deleteSnapshot(input: $input) { + result { + __typename + ...on NotOwner { + reason + } + ...on Success { + message + } + } + } +} diff --git a/client/src/graphql/gql/mutations/snapshots/update.gql b/client/src/graphql/gql/mutations/snapshots/update.gql new file mode 100644 index 00000000..1736fb2a --- /dev/null +++ b/client/src/graphql/gql/mutations/snapshots/update.gql @@ -0,0 +1,13 @@ +mutation UpdateSnapshot($input: UpdateSnapshotInput!) { + updateSnapshot(input: $input) { + snapshot { + ...on SnapshotNode { + title + id + } + ...on NotOwner { + reason + } + } + } +} diff --git a/client/src/pages/me/myTeam.vue b/client/src/pages/me/myTeam.vue index 8c3aa711..d8377042 100644 --- a/client/src/pages/me/myTeam.vue +++ b/client/src/pages/me/myTeam.vue @@ -71,6 +71,7 @@ methods: { editTeamName() { + // todo: use this.$modal this.$store.dispatch('editTeamName'); }, leaveTeam() { From 3577ca1a2ffd33351de983a07c367cf94c0c0969 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 14 Jun 2022 22:10:13 +0200 Subject: [PATCH 13/14] Add first part of delete mutation call --- .../components/modules/SnapshotListItem.vue | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/client/src/components/modules/SnapshotListItem.vue b/client/src/components/modules/SnapshotListItem.vue index 25830075..62781f87 100644 --- a/client/src/components/modules/SnapshotListItem.vue +++ b/client/src/components/modules/SnapshotListItem.vue @@ -112,6 +112,34 @@ }) .catch(); }, + deleteSnapshot() { + this.$modal.open('confirm') + .then(() => { + this.$apollo.mutate({ + mutation: DELETE_SNAPSHOT_MUTATION, + variables: { + input: { + id: this.snapshot.id, + }, + }, + update: (store, {data: {deleteSnapshot: {result}}}) => { + if (result.__typename === 'Success') { + const slug = this.$route.params.slug; + const query = SNAPSHOTS_QUERY; + const variables = { + slug + }; + const module = store.readQuery({ + query, + variables + }); + + } + }, + }); + }) + .catch(); + }, share() { this.$apollo.mutate({ mutation: SHARE_SNAPSHOT_MUTATION, From 03a4217412e99be6fb49b41ee69ef77350b4c279 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 15 Jun 2022 11:52:38 +0200 Subject: [PATCH 14/14] Finish mutation call for snapshot deletion Resolves MS-373 --- .../frontend/modules/snapshots.spec.js | 32 +++++++++++++++++-- .../components/modules/SnapshotListItem.vue | 22 +++++++++++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/client/cypress/integration/frontend/modules/snapshots.spec.js b/client/cypress/integration/frontend/modules/snapshots.spec.js index da66ca67..2cf28352 100644 --- a/client/cypress/integration/frontend/modules/snapshots.spec.js +++ b/client/cypress/integration/frontend/modules/snapshots.spec.js @@ -2,6 +2,33 @@ import module from '../../../fixtures/module.minimal'; import {getMinimalMe} from '../../../support/helpers'; import {hasOperationName} from '../../../support/graphql'; +const mockDeleteSnapshot = (success) => { + cy.intercept('POST', '/api/graphql', (req) => { + if (hasOperationName(req, 'DeleteSnapshot')) { + let result; + if (success) { + result = { + message: 'yay!', + __typename: 'Success' + }; + } else { + result = { + reason: 'Not the owner', + __typename: 'NotOwner' + }; + } + req.reply({ + data: { + deleteSnapshot: { + result + } + }, + }); + + } + }); +}; + const mockUpdateSnapshot = (title) => { cy.intercept('POST', '/api/graphql', (req) => { if (hasOperationName(req, 'UpdateSnapshot')) { @@ -133,10 +160,11 @@ describe('Snapshot', () => { it('Deletes Snapshot', () => { cy.mockGraphqlOps(operations(true)); + mockDeleteSnapshot(true); cy.visit('module/miteinander-reden/snapshots'); cy.getByDataCy('snapshot-entry').should('have.length', 1); cy.getByDataCy('delete-snapshot-button').click(); - cy.getByDataCy('confirm-button').click(); - cy.getByDataCy('snapshot').should('have.length', 0); + cy.getByDataCy('modal-save-button').click(); + cy.getByDataCy('snapshot-entry').should('have.length', 0); }); }); diff --git a/client/src/components/modules/SnapshotListItem.vue b/client/src/components/modules/SnapshotListItem.vue index 62781f87..d8eaed5a 100644 --- a/client/src/components/modules/SnapshotListItem.vue +++ b/client/src/components/modules/SnapshotListItem.vue @@ -51,6 +51,7 @@ import gql from 'graphql-tag'; import PenIcon from '@/components/icons/PenIcon'; import TrashIcon from '@/components/icons/TrashIcon'; + import { removeAtIndex } from '@/graphql/immutable-operations'; export default { props: { @@ -127,11 +128,26 @@ const slug = this.$route.params.slug; const query = SNAPSHOTS_QUERY; const variables = { - slug + slug, }; - const module = store.readQuery({ + const {module} = store.readQuery({ query, - variables + variables, + }); + const index = module.snapshots.findIndex(snapshot => snapshot.id === this.snapshot.id); + const snapshots = removeAtIndex(module.snapshots, index); + + const data = { + module: { + ...module, + snapshots + } + }; + + store.writeQuery({ + query, + variables, + data }); }