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