Add snapshot module detail query

This commit is contained in:
Ramon Wenger 2021-05-04 15:25:28 +02:00
parent a71f893e0b
commit 85a3131680
13 changed files with 249 additions and 67 deletions

View File

@ -0,0 +1,12 @@
query SnapshotDetail($id: ID!) {
snapshot(id: $id) {
id
chapters {
id
description
title
titleHidden
descriptionHidden
}
}
}

View File

@ -2,11 +2,12 @@
<div> <div>
Hello Hello
{{ id }} {{ id }}
</div> </div>
</template> </template>
<script> <script>
import SNAPSHOT_DETAIL_QUERY from 'gql/queries/snapshots/details.gql'; import SNAPSHOT_DETAIL_QUERY from '@/graphql/gql/queries/snapshots/detail.gql';
export default { export default {
props: { props: {
@ -17,7 +18,15 @@
}, },
apollo: { apollo: {
snapshot: SNAPSHOT_DETAIL_QUERY, snapshot: {
query: SNAPSHOT_DETAIL_QUERY,
variables() {
return {
id: this.id
};
}
},
}, },
}; };

View File

@ -27,11 +27,8 @@ class ChapterSnapshot(models.Model):
class SnapshotManager(models.Manager): class SnapshotManager(models.Manager):
def create_snapshot(self, module, school_class, user, *args, **kwargs): def create_snapshot(self, module, school_class, user, *args, **kwargs):
snapshot = self.create(module=module, creator=user, *args, **kwargs) snapshot = self.create(module=module, creator=user, *args, **kwargs)
chapters_with_hidden_properties = Chapter.get_by_parent(module).filter( chapters = Chapter.get_by_parent(module)
Q(description_hidden_for=school_class) for chapter in chapters:
| Q(title_hidden_for=school_class)
)
for chapter in chapters_with_hidden_properties:
ChapterSnapshot.objects.create( ChapterSnapshot.objects.create(
chapter=chapter, chapter=chapter,
snapshot=snapshot, snapshot=snapshot,

View File

@ -3,4 +3,5 @@ from graphene import relay
from graphene_django.filter import DjangoFilterConnectionField from graphene_django.filter import DjangoFilterConnectionField
class ChapterInterface(relay.Node): class ChapterInterface(relay.Node):
content_blocks = DjangoFilterConnectionField('books.schema.nodes.ContentBlockNode') description = graphene.String()
title = graphene.String()

View File

@ -0,0 +1,17 @@
import graphene
from graphene import relay
from api.graphene_wagtail import GenericStreamFieldType
class ContentBlockType(graphene.Enum):
NORMAL = 'normal'
BASE_COMMUNICATION = 'base_communication'
TASK = 'task'
BASE_SOCIETY = 'base_society'
BASE_INTERDISCIPLINARY = 'base_interdisciplinary'
class ContentBlockInterface(relay.Node):
title = graphene.String()
contents = GenericStreamFieldType()
type = ContentBlockType()

View File

@ -4,6 +4,14 @@ from graphene import relay
class ModuleInterface(relay.Node): class ModuleInterface(relay.Node):
pk = graphene.Int() pk = graphene.Int()
hero_image = graphene.String()
topic = graphene.Field('books.schema.nodes.TopicNode')
def resolve_pk(self, info, **kwargs): @staticmethod
return self.id def resolve_pk(parent, info, **kwargs):
return parent.id
@staticmethod
def resolve_hero_image(parent, info, **kwargs):
if parent.hero_image:
return parent.hero_image.file.url

View File

@ -2,6 +2,7 @@ import graphene
from django.db.models import Q 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 graphql_relay import to_global_id from graphql_relay import to_global_id
from books.models import Chapter, ContentBlock from books.models import Chapter, ContentBlock
@ -13,6 +14,7 @@ from notes.schema import ChapterBookmarkNode
class ChapterNode(DjangoObjectType): class ChapterNode(DjangoObjectType):
bookmark = graphene.Field(ChapterBookmarkNode) bookmark = graphene.Field(ChapterBookmarkNode)
content_blocks = DjangoFilterConnectionField('books.schema.nodes.ContentBlockNode')
class Meta: class Meta:
model = Chapter model = Chapter
@ -50,7 +52,7 @@ class ChapterNode(DjangoObjectType):
).first() ).first()
class SnapshotChapterNode(DjangoObjectType): class ChapterInSnapshotNode(DjangoObjectType):
title_hidden = graphene.Boolean() title_hidden = graphene.Boolean()
description_hidden = graphene.Boolean() description_hidden = graphene.Boolean()
title = graphene.String() title = graphene.String()

View File

@ -1,8 +1,9 @@
import graphene import graphene
from graphene import relay from graphene import relay, ObjectType
from graphene_django import DjangoObjectType from graphene_django import DjangoObjectType
from books.models import ContentBlock from books.models import ContentBlock
from books.schema.interfaces.contentblock import ContentBlockInterface
from books.utils import are_solutions_enabled_for from books.utils import are_solutions_enabled_for
from notes.models import ContentBlockBookmark from notes.models import ContentBlockBookmark
from notes.schema import ContentBlockBookmarkNode from notes.schema import ContentBlockBookmarkNode
@ -39,8 +40,6 @@ class ContentBlockNode(DjangoObjectType):
mine = graphene.Boolean() mine = graphene.Boolean()
bookmarks = graphene.List(ContentBlockBookmarkNode) bookmarks = graphene.List(ContentBlockBookmarkNode)
# contents = graphene.List(ContentNode)
class Meta: class Meta:
model = ContentBlock model = ContentBlock
only_fields = [ only_fields = [
@ -49,7 +48,7 @@ class ContentBlockNode(DjangoObjectType):
filter_fields = [ filter_fields = [
'slug', 'title', 'slug', 'title',
] ]
interfaces = (relay.Node,) interfaces = (ContentBlockInterface,)
def resolve_mine(self, info, **kwargs): def resolve_mine(self, info, **kwargs):
return self.owner is not None and self.owner.pk == info.context.user.pk return self.owner is not None and self.owner.pk == info.context.user.pk
@ -79,6 +78,7 @@ class ContentBlockNode(DjangoObjectType):
) )
def process_module_room_slug_block(content): def process_module_room_slug_block(content):
if content['type'] == 'module_room_slug': if content['type'] == 'module_room_slug':
try: try:

View File

@ -15,17 +15,6 @@ from surveys.schema import AnswerNode
class ModuleNode(DjangoObjectType): class ModuleNode(DjangoObjectType):
chapters = DjangoFilterConnectionField(ChapterNode)
topic = graphene.Field('books.schema.nodes.TopicNode')
hero_image = graphene.String()
solutions_enabled = graphene.Boolean()
bookmark = graphene.Field(ModuleBookmarkNode)
my_submissions = DjangoFilterConnectionField(StudentSubmissionNode)
my_answers = DjangoFilterConnectionField(AnswerNode)
my_content_bookmarks = DjangoFilterConnectionField(ContentBlockBookmarkNode)
my_chapter_bookmarks = DjangoFilterConnectionField(ChapterBookmarkNode)
snapshots = graphene.List('books.schema.nodes.SnapshotNode')
class Meta: class Meta:
model = Module model = Module
only_fields = [ only_fields = [
@ -37,9 +26,16 @@ class ModuleNode(DjangoObjectType):
} }
interfaces = (ModuleInterface, ) interfaces = (ModuleInterface, )
def resolve_hero_image(self, info, **kwargs):
if self.hero_image: chapters = DjangoFilterConnectionField(ChapterNode)
return self.hero_image.file.url solutions_enabled = graphene.Boolean()
bookmark = graphene.Field(ModuleBookmarkNode)
my_submissions = DjangoFilterConnectionField(StudentSubmissionNode)
my_answers = DjangoFilterConnectionField(AnswerNode)
my_content_bookmarks = DjangoFilterConnectionField(ContentBlockBookmarkNode)
my_chapter_bookmarks = DjangoFilterConnectionField(ChapterBookmarkNode)
snapshots = graphene.List('books.schema.nodes.SnapshotNode')
def resolve_chapters(self, info, **kwargs): def resolve_chapters(self, info, **kwargs):
return Chapter.get_by_parent(self) return Chapter.get_by_parent(self)
@ -88,8 +84,9 @@ class ModuleNode(DjangoObjectType):
return parent.objective_groups.all() \ return parent.objective_groups.all() \
.prefetch_related('hidden_for') .prefetch_related('hidden_for')
def resolve_snapshots(self, info, **kwargs): @staticmethod
return self.snapshots.all() def resolve_snapshots(parent, info, **kwargs):
return parent.snapshots.all()
class RecentModuleNode(DjangoObjectType): class RecentModuleNode(DjangoObjectType):
class Meta: class Meta:

View File

@ -1,25 +1,88 @@
import graphene import graphene
from graphene import relay from django.db.models import Q
from graphene import relay, ObjectType
from graphene_django import DjangoObjectType from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField from graphene_django.filter import DjangoFilterConnectionField
from .chapter import SnapshotChapterNode
from . import ChapterInSnapshotNode
from books.models.snapshot import Snapshot from books.models.snapshot import Snapshot
from ..interfaces import ModuleInterface, ChapterInterface
from ..interfaces.contentblock import ContentBlockInterface
from ...models import Module, Chapter, ChapterSnapshot, ContentBlock
class SnapshotContentBlock:
def __init__(self, content_block, snapshot):
self.title = content_block.title
self.contents = content_block.title
self.type = content_block.type
self.hidden = snapshot.hidden_content_blocks.filter(id=content_block.id).exists()
class SnapshotChapter:
def __init__(self, chapter, snapshot, description_hidden=False, title_hidden=False):
self.title = chapter.title
self.description = chapter.description
self.title_hidden = title_hidden
self.description_hidden = description_hidden
self.content_blocks = []
base_qs = ContentBlock.get_by_parent(chapter)
without_owner = Q(owner__isnull=True)
this_snapshot = Q(contentblocksnapshot__snapshot=snapshot)
self.content_blocks = [
SnapshotContentBlock(
content_block=content_block,
snapshot=snapshot
) for content_block in
base_qs.filter(without_owner | this_snapshot)
]
# all from module without owner
# all with snapshotcontentblock with this snapshot
class SnapshotContentBlockNode(ObjectType):
class Meta:
interfaces = (ContentBlockInterface,)
hidden = graphene.Boolean()
class SnapshotChapterNode(ObjectType):
class Meta:
interfaces = (ChapterInterface,)
content_blocks = graphene.List(SnapshotContentBlockNode)
description_hidden = graphene.Boolean()
title_hidden = graphene.Boolean()
class SnapshotNode(DjangoObjectType): class SnapshotNode(DjangoObjectType):
title = graphene.String() title = graphene.String()
# chapters = graphene.Field(SnapshotChapterNode)
snapshot_chapters = DjangoFilterConnectionField(ChapterInSnapshotNode)
chapters = graphene.List(SnapshotChapterNode)
class Meta: class Meta:
model = Snapshot model = Snapshot
interfaces = (relay.Node,) interfaces = (relay.Node,)
# chapters = relay.ConnectionField('books.schema.connections.ChapterSnapshotConnection') @staticmethod
chapters = DjangoFilterConnectionField(SnapshotChapterNode) def resolve_snapshot_chapters(parent, info, **kwargs):
def resolve_chapters(self, info, **kwargs):
# return Chapter.objects.filter(chapter_snapshots__snapshot=self) # return Chapter.objects.filter(chapter_snapshots__snapshot=self)
return self.chapters.through.objects.all() return parent.chapters.through.objects.all()
@staticmethod
def resolve_chapters(parent, info, **kwargs):
return [
SnapshotChapter(
chapter_snapshot.chapter,
snapshot=parent,
title_hidden=chapter_snapshot.title_hidden,
description_hidden=chapter_snapshot.description_hidden
)
for chapter_snapshot in parent.chapters.through.objects.all()
]
@staticmethod @staticmethod
def resolve_title(parent, info, **kwargs): def resolve_title(parent, info, **kwargs):

View File

@ -5,8 +5,8 @@ from graphene_django.filter import DjangoFilterConnectionField
from api.utils import get_object from api.utils import get_object
from core.logger import get_logger from core.logger import get_logger
from ..models import Book, Topic, Module, Chapter from ..models import Book, Topic, Module, Chapter, Snapshot
from .nodes import ContentBlockNode, ChapterNode, ModuleNode, TopicNode from .nodes import ContentBlockNode, ChapterNode, ModuleNode, TopicNode, SnapshotNode
from .connections import TopicConnection, ModuleConnection from .connections import TopicConnection, ModuleConnection
logger = get_logger(__name__) logger = get_logger(__name__)
@ -18,6 +18,7 @@ class BookQuery(object):
module = graphene.Field(ModuleNode, slug=graphene.String(), id=graphene.ID()) module = graphene.Field(ModuleNode, slug=graphene.String(), id=graphene.ID())
chapter = relay.Node.Field(ChapterNode) chapter = relay.Node.Field(ChapterNode)
content_block = relay.Node.Field(ContentBlockNode) content_block = relay.Node.Field(ContentBlockNode)
snapshot = relay.Node.Field(SnapshotNode)
topics = relay.ConnectionField(TopicConnection) topics = relay.ConnectionField(TopicConnection)
modules = relay.ConnectionField(ModuleConnection) modules = relay.ConnectionField(ModuleConnection)
@ -35,6 +36,11 @@ class BookQuery(object):
def resolve_chapters(self, *args, **kwargs): def resolve_chapters(self, *args, **kwargs):
return Chapter.objects.filter(**kwargs).live() return Chapter.objects.filter(**kwargs).live()
def resolve_snapshot(self, info, **kwargs):
id = kwargs.get('id')
snapshot = get_object(Snapshot, id)
return snapshot
def resolve_module(self, info, **kwargs): def resolve_module(self, info, **kwargs):
slug = kwargs.get('slug') slug = kwargs.get('slug')
id = kwargs.get('id') id = kwargs.get('id')

View File

@ -4,7 +4,7 @@ from graphql_relay import to_global_id, from_global_id
from api.schema import schema from api.schema import schema
from books.factories import ModuleFactory, ChapterFactory, ContentBlockFactory from books.factories import ModuleFactory, ChapterFactory, ContentBlockFactory
from books.models import Snapshot from books.models import Snapshot, ChapterSnapshot
from users.models import User, SchoolClass from users.models import User, SchoolClass
from users.services import create_users from users.services import create_users
@ -55,7 +55,7 @@ mutation CreateSnapshot($input: CreateSnapshotInput!) {
creator { creator {
username username
} }
chapters { snapshotChapters {
edges { edges {
node { node {
id id
@ -87,6 +87,26 @@ mutation ApplySnapshot($input: ApplySnapshotInput!) {
} }
""" """
SNAPSHOT_MODULE_QUERY = """
query SnapshotDetail($id: ID!) {
snapshot(id: $id) {
id
chapters {
id
description
title
titleHidden
descriptionHidden
contentBlocks {
id
title
hidden
}
}
}
}
"""
def edges_to_array(entity): def edges_to_array(entity):
return [edge['node'] for edge in entity.get('edges')] return [edge['node'] for edge in entity.get('edges')]
@ -103,6 +123,7 @@ class CreateSnapshotTestCase(TestCase):
# module M has a chapter # module M has a chapter
self.chapter = ChapterFactory(parent=self.module, slug='some-chapter') self.chapter = ChapterFactory(parent=self.module, slug='some-chapter')
ChapterFactory(parent=self.module, slug='some-other-chapter')
# chapter has some content blocks a, b, c # chapter has some content blocks a, b, c
self.title_visible = 'visible' self.title_visible = 'visible'
@ -169,7 +190,7 @@ class CreateSnapshotTestCase(TestCase):
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.get('errors'))
snapshot = result.get('data').get('createSnapshot').get('snapshot') snapshot = result.get('data').get('createSnapshot').get('snapshot')
chapter = snapshot.get('chapters').get('edges')[0]['node'] chapter = snapshot.get('snapshotChapters').get('edges')[0]['node']
self.assertIsNotNone(snapshot.get('created')) self.assertIsNotNone(snapshot.get('created'))
self.assertEqual(snapshot.get('creator').get('username'), self.teacher.username) self.assertEqual(snapshot.get('creator').get('username'), self.teacher.username)
@ -182,6 +203,7 @@ class CreateSnapshotTestCase(TestCase):
self.assertEqual(len(content_blocks), 2) self.assertEqual(len(content_blocks), 2)
self.assertEqual(content_blocks[0]['title'], self.title_visible) self.assertEqual(content_blocks[0]['title'], self.title_visible)
self.assertEqual(content_blocks[1]['title'], self.title_custom) self.assertEqual(content_blocks[1]['title'], self.title_custom)
self.assertEqual(ChapterSnapshot.objects.count(), 2)
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,
@ -201,3 +223,23 @@ class CreateSnapshotTestCase(TestCase):
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.get('errors'))
self._test_module_visibility(client, school_class_name) self._test_module_visibility(client, school_class_name)
def test_display_snapshot_module(self):
self.snapshot = Snapshot.objects.create_snapshot(module=self.module, school_class=self.skillbox_class,
user=self.teacher)
id = to_global_id('SnapshotNode', self.snapshot.id)
snapshot_result = self.client.execute(SNAPSHOT_MODULE_QUERY, variables={
'id': id
})
self.assertIsNone(snapshot_result.get('errors'))
snapshot = snapshot_result.get('data').get('snapshot')
chapters = snapshot.get('chapters')
self.assertEqual(len(chapters), 2)
chapter = chapters[0]
content_blocks = chapter.get('contentBlocks')
self.assertEqual(len(content_blocks), 3)
first, second, third = content_blocks
self.assertEqual(first['title'], 'visible')
self.assertEqual(second['title'], 'hidden')
self.assertEqual(second['hidden'], True)
self.assertEqual(third['title'], 'custom')

View File

@ -218,20 +218,41 @@ type ChapterBookmarkNodeEdge {
cursor: String! cursor: String!
} }
type ChapterInSnapshotNode implements ChapterInterface {
id: ID!
chapter: ChapterNode!
snapshot: SnapshotNode!
titleHidden: Boolean
descriptionHidden: Boolean
description: String
title: String
}
type ChapterInSnapshotNodeConnection {
pageInfo: PageInfo!
edges: [ChapterInSnapshotNodeEdge]!
}
type ChapterInSnapshotNodeEdge {
node: ChapterInSnapshotNode
cursor: String!
}
interface ChapterInterface { interface ChapterInterface {
id: ID! id: ID!
contentBlocks(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ContentBlockNodeConnection description: String
title: String
} }
type ChapterNode implements ChapterInterface { type ChapterNode implements ChapterInterface {
title: String! title: String
slug: String! slug: String!
description: String! description: String
titleHiddenFor(offset: Int, before: String, after: String, first: Int, last: Int, name: String): SchoolClassNodeConnection! titleHiddenFor(offset: Int, before: String, after: String, first: Int, last: Int, name: String): SchoolClassNodeConnection!
descriptionHiddenFor(offset: Int, before: String, after: String, first: Int, last: Int, name: String): SchoolClassNodeConnection! descriptionHiddenFor(offset: Int, before: String, after: String, first: Int, last: Int, name: String): SchoolClassNodeConnection!
id: ID! id: ID!
contentBlocks(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ContentBlockNodeConnection
bookmark: ChapterBookmarkNode bookmark: ChapterBookmarkNode
contentBlocks(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ContentBlockNodeConnection
} }
type ChapterNodeConnection { type ChapterNodeConnection {
@ -278,14 +299,21 @@ input ContentBlockInput {
visibility: [UserGroupBlockVisibility] visibility: [UserGroupBlockVisibility]
} }
type ContentBlockNode implements Node { interface ContentBlockInterface {
title: String! id: ID!
title: String
contents: GenericStreamFieldType
type: ContentBlockType
}
type ContentBlockNode implements ContentBlockInterface {
title: String
slug: String! slug: String!
hiddenFor(offset: Int, before: String, after: String, first: Int, last: Int, name: String): SchoolClassNodeConnection! hiddenFor(offset: Int, before: String, after: String, first: Int, last: Int, name: String): SchoolClassNodeConnection!
visibleFor(offset: Int, before: String, after: String, first: Int, last: Int, name: String): SchoolClassNodeConnection! visibleFor(offset: Int, before: String, after: String, first: Int, last: Int, name: String): SchoolClassNodeConnection!
userCreated: Boolean! userCreated: Boolean!
contents: GenericStreamFieldType contents: GenericStreamFieldType
type: ContentBlockType! type: ContentBlockType
id: ID! id: ID!
mine: Boolean mine: Boolean
bookmarks: [ContentBlockBookmarkNode] bookmarks: [ContentBlockBookmarkNode]
@ -438,6 +466,7 @@ type CustomQuery {
module(slug: String, id: ID): ModuleNode module(slug: String, id: ID): ModuleNode
chapter(id: ID!): ChapterNode chapter(id: ID!): ChapterNode
contentBlock(id: ID!): ContentBlockNode contentBlock(id: ID!): ContentBlockNode
snapshot(id: ID!): SnapshotNode
topics(before: String, after: String, first: Int, last: Int): TopicConnection topics(before: String, after: String, first: Int, last: Int): TopicConnection
modules(before: String, after: String, first: Int, last: Int): ModuleConnection modules(before: String, after: String, first: Int, last: Int): ModuleConnection
chapters(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ChapterNodeConnection chapters(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ChapterNodeConnection
@ -638,6 +667,8 @@ type ModuleEdge {
interface ModuleInterface { interface ModuleInterface {
id: ID! id: ID!
pk: Int pk: Int
heroImage: String
topic: TopicNode
} }
type ModuleNode implements ModuleInterface { type ModuleNode implements ModuleInterface {
@ -651,8 +682,8 @@ type ModuleNode implements ModuleInterface {
objectiveGroups(offset: Int, before: String, after: String, first: Int, last: Int, title: String, module_Slug: String): ObjectiveGroupNodeConnection! objectiveGroups(offset: Int, before: String, after: String, first: Int, last: Int, title: String, module_Slug: String): ObjectiveGroupNodeConnection!
id: ID! id: ID!
pk: Int pk: Int
chapters(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ChapterNodeConnection
topic: TopicNode topic: TopicNode
chapters(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ChapterNodeConnection
solutionsEnabled: Boolean solutionsEnabled: Boolean
bookmark: ModuleBookmarkNode bookmark: ModuleBookmarkNode
mySubmissions(offset: Int, before: String, after: String, first: Int, last: Int): StudentSubmissionNodeConnection mySubmissions(offset: Int, before: String, after: String, first: Int, last: Int): StudentSubmissionNodeConnection
@ -882,34 +913,31 @@ type SchoolClassNodeEdge {
type SnapshotChapterNode implements ChapterInterface { type SnapshotChapterNode implements ChapterInterface {
id: ID! id: ID!
chapter: ChapterNode!
snapshot: SnapshotNode!
titleHidden: Boolean
descriptionHidden: Boolean
contentBlocks(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ContentBlockNodeConnection
title: String
description: String description: String
title: String
contentBlocks: [SnapshotContentBlockNode]
descriptionHidden: Boolean
titleHidden: Boolean
} }
type SnapshotChapterNodeConnection { type SnapshotContentBlockNode implements ContentBlockInterface {
pageInfo: PageInfo! id: ID!
edges: [SnapshotChapterNodeEdge]! title: String
} contents: GenericStreamFieldType
type: ContentBlockType
type SnapshotChapterNodeEdge { hidden: Boolean
node: SnapshotChapterNode
cursor: String!
} }
type SnapshotNode implements Node { type SnapshotNode implements Node {
id: ID! id: ID!
module: ModuleNode! module: ModuleNode!
chapters(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): SnapshotChapterNodeConnection 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: UserNode
chapterSnapshots(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): SnapshotChapterNodeConnection! chapterSnapshots(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ChapterInSnapshotNodeConnection!
title: String title: String
snapshotChapters(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ChapterInSnapshotNodeConnection
} }
input SpellCheckInput { input SpellCheckInput {