Add ability to share a snapshot
This commit is contained in:
parent
ade00205e5
commit
24c88e84ff
|
|
@ -9,7 +9,7 @@
|
|||
<div class="snapshot-created__content">
|
||||
<div class="snapshot-created__entry">
|
||||
<span class="snapshot-created__title">{{ snapshot.title }}</span>
|
||||
<span class="snapshot-created__meta">{{ created }} - {{ snapshot.creator.firstName }} {{ snapshot.creator.lastName }}</span>
|
||||
<span class="snapshot-created__meta">{{ created }} - {{ snapshot.creator }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="snapshot-header">
|
||||
<h1>Snapshot {{ id }}</h1>
|
||||
<div class="snapshot-header__meta">
|
||||
{{ created }} – {{ creator }}
|
||||
{{ created }} – {{ snapshot.creator }}
|
||||
</div>
|
||||
|
||||
<section class="snapshot-header__section">
|
||||
|
|
@ -86,10 +86,6 @@
|
|||
created() {
|
||||
return dateformat(this.snapshot.created);
|
||||
},
|
||||
creator() {
|
||||
const {firstName, lastName} = this.snapshot.creator || {};
|
||||
return `${firstName} ${lastName}`;
|
||||
},
|
||||
hiddenObjectives() {
|
||||
return _getChange(this.snapshot, 'hiddenObjectives');
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,42 +1,78 @@
|
|||
<template>
|
||||
<div class="snapshot-list-item">
|
||||
<span
|
||||
<router-link
|
||||
:to="snapshotRoute"
|
||||
class="snapshot-list-item__title"
|
||||
v-html="snapshot.title"/>
|
||||
<span
|
||||
class="snapshot-list-item__date"
|
||||
v-html="created" />
|
||||
<router-link
|
||||
:to="snapshotRoute"
|
||||
class="snapshot-list-item__link">Mit Team teilen</router-link>
|
||||
v-html="meta"/>
|
||||
<a
|
||||
class="snapshot-list-item__link"
|
||||
v-if="snapshot.mine"
|
||||
@click="share"
|
||||
>
|
||||
<template v-if="snapshot.shared">Nicht mehr teilen</template>
|
||||
<template v-else>Mit Team teilen</template>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dateformat from '@/helpers/date-format';
|
||||
import {SNAPSHOT_DETAIL} from '@/router/module.names';
|
||||
import SHARE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/share.gql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
snapshot: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
created() {
|
||||
return dateformat(this.snapshot.created);
|
||||
meta() {
|
||||
const created = dateformat(this.snapshot.created);
|
||||
if (this.snapshot.mine) {
|
||||
return created;
|
||||
}
|
||||
return `${created} - ${this.snapshot.creator}`;
|
||||
},
|
||||
snapshotRoute() {
|
||||
return {
|
||||
name: SNAPSHOT_DETAIL,
|
||||
params: {
|
||||
id: this.snapshot.id
|
||||
}
|
||||
id: this.snapshot.id,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
share() {
|
||||
this.$apollo.mutate({
|
||||
mutation: SHARE_SNAPSHOT_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
snapshot: this.snapshot.id,
|
||||
shared: !this.snapshot.shared,
|
||||
},
|
||||
},
|
||||
update(store, {data: {shareSnapshot: {snapshot: {id, shared}}}}) {
|
||||
store.writeFragment({
|
||||
id,
|
||||
fragment: gql`fragment SnapshotFragment on SnapshotNode { shared }`,
|
||||
data: {
|
||||
shared,
|
||||
__typename: 'SnapshotNode'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,7 @@ mutation CreateSnapshot($input: CreateSnapshotInput!) {
|
|||
id
|
||||
title
|
||||
created
|
||||
creator {
|
||||
username
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
creator
|
||||
}
|
||||
success
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
mutation ShareSnapshot($input: ShareSnapshotInput!) {
|
||||
shareSnapshot(input: $input) {
|
||||
success
|
||||
snapshot {
|
||||
id
|
||||
shared
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,9 @@ query ModuleSnapshotsQuery($slug: String!) {
|
|||
id
|
||||
title
|
||||
created
|
||||
mine
|
||||
shared
|
||||
creator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,7 @@ query SnapshotDetail($id: ID!) {
|
|||
hiddenContentBlocks
|
||||
hiddenObjectives
|
||||
}
|
||||
creator {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
creator
|
||||
chapters {
|
||||
id
|
||||
description
|
||||
|
|
|
|||
|
|
@ -7,13 +7,12 @@
|
|||
@select="selectedLink=$event"
|
||||
/>
|
||||
<div
|
||||
class="snapshots__list"
|
||||
v-if="selectedLink === 'mine'">
|
||||
class="snapshots__list">
|
||||
<snapshot-list-item
|
||||
:key="snapshot.id"
|
||||
:snapshot="snapshot"
|
||||
class="snapshots__snapshot"
|
||||
v-for="snapshot in module.snapshots"
|
||||
v-for="snapshot in snapshots"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -39,6 +38,16 @@
|
|||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
snapshots() {
|
||||
if (this.selectedLink === 'mine') {
|
||||
return this.module.snapshots.filter(snapshot => snapshot.mine);
|
||||
} else {
|
||||
return this.module.snapshots.filter(snapshot => snapshot.shared);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
apollo: {
|
||||
module: {
|
||||
query: MODULE_SNAPSHOTS_QUERY,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import graphene
|
||||
from django.db.models import Q
|
||||
from graphene import relay
|
||||
from graphene_django import DjangoObjectType
|
||||
from graphene_django.filter import DjangoFilterConnectionField
|
||||
|
|
@ -86,7 +87,8 @@ class ModuleNode(DjangoObjectType):
|
|||
|
||||
@staticmethod
|
||||
def resolve_snapshots(parent, info, **kwargs):
|
||||
return parent.snapshots.filter(creator=info.context.user)
|
||||
user = info.context.user
|
||||
return parent.snapshots.filter(Q(creator=user) | Q(Q(creator__team=user.team ) & Q(shared=True)))
|
||||
|
||||
|
||||
class RecentModuleNode(DjangoObjectType):
|
||||
|
|
|
|||
|
|
@ -72,6 +72,9 @@ class SnapshotNode(DjangoObjectType):
|
|||
meta_title = graphene.String()
|
||||
hero_image = graphene.String()
|
||||
changes = graphene.Field(SnapshotChangesNode)
|
||||
mine = graphene.Boolean()
|
||||
shared = graphene.Boolean(required=True)
|
||||
creator = graphene.String(required=True)
|
||||
|
||||
class Meta:
|
||||
model = Snapshot
|
||||
|
|
@ -111,3 +114,11 @@ class SnapshotNode(DjangoObjectType):
|
|||
'hidden_content_blocks': parent.hidden_content_blocks.count(),
|
||||
'new_content_blocks': parent.custom_content_blocks.count()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def resolve_mine(parent, info, **kwargs):
|
||||
return parent.creator == info.context.user
|
||||
|
||||
@staticmethod
|
||||
def resolve_creator(parent, info, **kwargs):
|
||||
return f'{parent.creator.first_name} {parent.creator.last_name}'
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from api.schema import schema
|
|||
from books.factories import ModuleFactory, ChapterFactory, ContentBlockFactory
|
||||
from books.models import Snapshot, ChapterSnapshot
|
||||
from core.tests.base_test import SkillboxTestCase
|
||||
from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory
|
||||
from users.factories import SchoolClassFactory
|
||||
from users.models import User, SchoolClass
|
||||
|
||||
|
|
@ -14,6 +15,18 @@ query ModulesQuery($slug: String, $id: ID) {
|
|||
module(slug: $slug, id: $id) {
|
||||
id
|
||||
title
|
||||
objectiveGroups {
|
||||
objectives {
|
||||
id
|
||||
text
|
||||
hiddenFor {
|
||||
name
|
||||
}
|
||||
visibleFor {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
chapters {
|
||||
id
|
||||
contentBlocks {
|
||||
|
|
@ -40,6 +53,11 @@ mutation CreateSnapshot($input: CreateSnapshotInput!) {
|
|||
creator {
|
||||
username
|
||||
}
|
||||
objectiveGroups {
|
||||
objectives {
|
||||
text
|
||||
}
|
||||
}
|
||||
chapters {
|
||||
id
|
||||
descriptionHidden
|
||||
|
|
@ -152,31 +170,55 @@ class CreateSnapshotTestCase(SkillboxTestCase):
|
|||
# we make a snapshot S of the module M
|
||||
# snapshot S looks like module M for school class X
|
||||
|
||||
objective_group = ObjectiveGroupFactory(module=self.module)
|
||||
self.visible_objective = ObjectiveFactory(text='visible-objective', group=objective_group)
|
||||
self.hidden_objective = ObjectiveFactory(text='hidden-objective', group=objective_group)
|
||||
self.custom_objective = ObjectiveFactory(text='custom-objective', group=objective_group, owner=self.teacher)
|
||||
|
||||
self.hidden_objective.hidden_for.add(self.skillbox_class)
|
||||
self.custom_objective.visible_for.add(self.skillbox_class)
|
||||
|
||||
def _test_module_visibility(self, client, school_class_name):
|
||||
result = client.execute(MODULE_QUERY, variables={
|
||||
'slug': self.module.slug
|
||||
})
|
||||
self.assertIsNone(result.get('errors'))
|
||||
module = result.get('data').get('module')
|
||||
chapter = edges_to_array(module.get('chapters'))[0]
|
||||
chapter = module.get('chapters')[0]
|
||||
self.assertIsNotNone(chapter)
|
||||
content_blocks = chapter.get('contentBlocks')
|
||||
content_block_titles = [node['title'] for node in content_blocks]
|
||||
content_block_titles = [content_block['title'] for content_block in content_blocks]
|
||||
self.assertTrue(self.title_visible in content_block_titles)
|
||||
self.assertTrue(self.title_hidden in content_block_titles)
|
||||
self.assertTrue(self.title_custom in content_block_titles)
|
||||
hidden_node = [node for node in content_blocks if
|
||||
node['title'] == self.title_hidden][0]
|
||||
custom_node = [node for node in content_blocks if
|
||||
node['title'] == self.title_custom][0]
|
||||
# check if hidden node is hidden for this school class
|
||||
hidden_content_block = [content_block for content_block in content_blocks if
|
||||
content_block['title'] == self.title_hidden][0]
|
||||
custom_content_block = [content_block for content_block in content_blocks if
|
||||
content_block['title'] == self.title_custom][0]
|
||||
# check if hidden content block is hidden for this school class
|
||||
self.assertTrue(
|
||||
school_class_name in [school_class['name'] for school_class in
|
||||
hidden_node.get('hiddenFor')])
|
||||
# check if the custom node is visible for this school class
|
||||
hidden_content_block.get('hiddenFor')])
|
||||
# check if the custom content block is visible for this school class
|
||||
self.assertTrue(
|
||||
school_class_name in [school_class['name'] for school_class in
|
||||
custom_node.get('visibleFor')])
|
||||
custom_content_block.get('visibleFor')])
|
||||
|
||||
objectives = module['objectiveGroups'][0]['objectives']
|
||||
|
||||
hidden_objective = [objective for objective in objectives if
|
||||
objective['text'] == self.hidden_objective.text][0]
|
||||
custom_objective = [objective for objective in objectives if
|
||||
objective['text'] == self.custom_objective.text][0]
|
||||
|
||||
# check if hidden objective is hidden for this school class
|
||||
self.assertTrue(
|
||||
school_class_name in [school_class['name'] for school_class in
|
||||
hidden_objective.get('hiddenFor')])
|
||||
# check if the custom objective is visible for this school class
|
||||
self.assertTrue(
|
||||
school_class_name in [school_class['name'] for school_class in
|
||||
custom_objective.get('visibleFor')])
|
||||
|
||||
def test_setup(self):
|
||||
# make sure everything is setup correctly
|
||||
|
|
@ -200,6 +242,7 @@ class CreateSnapshotTestCase(SkillboxTestCase):
|
|||
self.assertFalse(chapter['descriptionHidden'])
|
||||
_, chapter_id = from_global_id(chapter['id'])
|
||||
self.assertEqual(int(chapter_id), self.chapter.id)
|
||||
|
||||
content_blocks = chapter['contentBlocks']
|
||||
self.assertEqual(len(content_blocks), 3)
|
||||
visible, hidden, custom = content_blocks
|
||||
|
|
@ -211,6 +254,15 @@ class CreateSnapshotTestCase(SkillboxTestCase):
|
|||
self.assertEqual(custom['hidden'], False)
|
||||
self.assertEqual(ChapterSnapshot.objects.count(), 2)
|
||||
|
||||
visible, hidden, custom = snapshot['objectiveGroups'][0]['objectives']
|
||||
self.assertEqual(visible['text'], self.visible_objective.text)
|
||||
self.assertEqual(visible['hidden'], False)
|
||||
self.assertEqual(hidden['text'], self.hidden_objective.text)
|
||||
self.assertEqual(hidden['hidden'], True)
|
||||
self.assertEqual(custom['text'], self.custom_objective.text)
|
||||
self.assertEqual(custom['hidden'], False)
|
||||
|
||||
|
||||
def test_apply_snapshot(self):
|
||||
self.snapshot = Snapshot.objects.create_snapshot(module=self.module, school_class=self.skillbox_class,
|
||||
user=self.teacher)
|
||||
|
|
|
|||
|
|
@ -419,6 +419,7 @@ type CustomMutation {
|
|||
syncModuleVisibility(input: SyncModuleVisibilityInput!): SyncModuleVisibilityPayload
|
||||
createSnapshot(input: CreateSnapshotInput!): CreateSnapshotPayload
|
||||
applySnapshot(input: ApplySnapshotInput!): ApplySnapshotPayload
|
||||
shareSnapshot(input: ShareSnapshotInput!): ShareSnapshotPayload
|
||||
_debug: DjangoDebug
|
||||
}
|
||||
|
||||
|
|
@ -882,6 +883,18 @@ type SchoolClassNodeEdge {
|
|||
cursor: String!
|
||||
}
|
||||
|
||||
input ShareSnapshotInput {
|
||||
snapshot: ID!
|
||||
shared: Boolean!
|
||||
clientMutationId: String
|
||||
}
|
||||
|
||||
type ShareSnapshotPayload {
|
||||
success: Boolean!
|
||||
snapshot: SnapshotNode
|
||||
clientMutationId: String
|
||||
}
|
||||
|
||||
type SnapshotChangesNode {
|
||||
hiddenObjectives: Int!
|
||||
newObjectives: Int!
|
||||
|
|
@ -912,11 +925,13 @@ type SnapshotNode implements Node {
|
|||
chapters: [SnapshotChapterNode]
|
||||
hiddenContentBlocks(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ContentBlockNodeConnection!
|
||||
created: DateTime!
|
||||
creator: UserNode
|
||||
creator: String!
|
||||
shared: Boolean!
|
||||
title: String
|
||||
metaTitle: String
|
||||
heroImage: String
|
||||
changes: SnapshotChangesNode
|
||||
mine: Boolean
|
||||
}
|
||||
|
||||
input SpellCheckInput {
|
||||
|
|
|
|||
Loading…
Reference in New Issue