Merged in feature/topic-404 (pull request #106)
Feature/topic 404 Approved-by: Christian Cueni
This commit is contained in:
commit
7da52b03a1
|
|
@ -9,6 +9,7 @@ import {typeDefs} from '@/graphql/typedefs';
|
||||||
import {resolvers} from '@/graphql/resolvers';
|
import {resolvers} from '@/graphql/resolvers';
|
||||||
import cache from './cache';
|
import cache from './cache';
|
||||||
|
|
||||||
|
import {router} from '@/router';
|
||||||
|
|
||||||
export default function (uri, networkErrorCallback) {
|
export default function (uri, networkErrorCallback) {
|
||||||
const httpLink = createHttpLink({
|
const httpLink = createHttpLink({
|
||||||
|
|
@ -64,19 +65,37 @@ export default function (uri, networkErrorCallback) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const notFoundLink = new ApolloLink((operation, forward) => {
|
||||||
|
return forward(operation).map(response => {
|
||||||
|
const {data} = response;
|
||||||
|
if (data) {
|
||||||
|
if (data.topic && data.topic.__typename === 'NotFound') {
|
||||||
|
// redirect to general 404 page.
|
||||||
|
// todo: specific topic not found page, with navigation?
|
||||||
|
router.push({
|
||||||
|
name: 'not-found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
let composedLink;
|
let composedLink;
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
composedLink = ApolloLink.from([
|
composedLink = ApolloLink.from([
|
||||||
createOmitTypenameLink,
|
createOmitTypenameLink,
|
||||||
errorLink,
|
errorLink,
|
||||||
httpLink
|
notFoundLink,
|
||||||
|
httpLink,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
composedLink = ApolloLink.from([
|
composedLink = ApolloLink.from([
|
||||||
consoleLink,
|
consoleLink,
|
||||||
createOmitTypenameLink,
|
createOmitTypenameLink,
|
||||||
errorLink,
|
errorLink,
|
||||||
httpLink
|
notFoundLink,
|
||||||
|
httpLink,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fragment NotFoundParts on NotFound {
|
||||||
|
reason
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#import "../fragments/topicParts.gql"
|
#import "../fragments/topicParts.gql"
|
||||||
|
#import "../fragments/notFoundParts.gql"
|
||||||
query Topic($slug: String!){
|
query Topic($slug: String!){
|
||||||
topic(slug: $slug) {
|
topic(slug: $slug) {
|
||||||
...TopicParts
|
...TopicParts
|
||||||
|
...NotFoundParts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
BookTopicNavigation,
|
BookTopicNavigation,
|
||||||
ModuleTeaser,
|
ModuleTeaser,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
BulbIcon
|
BulbIcon,
|
||||||
},
|
},
|
||||||
|
|
||||||
apollo: {
|
apollo: {
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
return {
|
return {
|
||||||
query: TOPIC_QUERY,
|
query: TOPIC_QUERY,
|
||||||
variables: {
|
variables: {
|
||||||
slug: this.$route.params.topicSlug
|
slug: this.$route.params.topicSlug,
|
||||||
},
|
},
|
||||||
update(data) {
|
update(data) {
|
||||||
return this.$getRidOfEdges(data).topic || {};
|
return this.$getRidOfEdges(data).topic || {};
|
||||||
|
|
@ -81,26 +81,26 @@
|
||||||
this.saveMe = false;
|
this.saveMe = false;
|
||||||
this.updateLastVisitedTopic(this.topic.id);
|
this.updateLastVisitedTopic(this.topic.id);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
topic: {
|
topic: {
|
||||||
modules: {
|
modules: {
|
||||||
edges: []
|
edges: [],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
saveMe: false
|
saveMe: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
modules() {
|
modules() {
|
||||||
return this.topic.modules;
|
return this.topic.modules;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
@ -116,12 +116,15 @@
|
||||||
this.$store.dispatch('showFullscreenVideo', this.topic.vimeoId);
|
this.$store.dispatch('showFullscreenVideo', this.topic.vimeoId);
|
||||||
},
|
},
|
||||||
updateLastVisitedTopic(topicId) {
|
updateLastVisitedTopic(topicId) {
|
||||||
|
if (!topicId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$apollo.mutate({
|
this.$apollo.mutate({
|
||||||
mutation: UPDATE_LAST_TOPIC_MUTATION,
|
mutation: UPDATE_LAST_TOPIC_MUTATION,
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
id: topicId
|
id: topicId,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
update(store, {data: {updateLastTopic: {topic}}}) {
|
update(store, {data: {updateLastTopic: {topic}}}) {
|
||||||
if (topic) {
|
if (topic) {
|
||||||
|
|
@ -131,15 +134,15 @@
|
||||||
const data = {
|
const data = {
|
||||||
me: {
|
me: {
|
||||||
...me,
|
...me,
|
||||||
lastTopic: topic
|
lastTopic: topic,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
store.writeQuery({query, data});
|
store.writeQuery({query, data});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,18 @@ const submission = () => import('@/pages/studentSubmission');
|
||||||
|
|
||||||
const postLoginRedirectUrlKey = 'postLoginRedirectionUrl';
|
const postLoginRedirectUrlKey = 'postLoginRedirectionUrl';
|
||||||
|
|
||||||
|
const notFoundRoute = {
|
||||||
|
component: p404,
|
||||||
|
meta: {
|
||||||
|
layout: 'blank',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'home',
|
name: 'home',
|
||||||
component: start
|
component: start,
|
||||||
},
|
},
|
||||||
...moduleRoutes,
|
...moduleRoutes,
|
||||||
...authRoutes,
|
...authRoutes,
|
||||||
|
|
@ -85,15 +92,17 @@ const routes = [
|
||||||
default:
|
default:
|
||||||
return '/unknown-auth-error';
|
return '/unknown-auth-error';
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{path: '/styleguide', component: styleGuidePage},
|
{path: '/styleguide', component: styleGuidePage},
|
||||||
|
{
|
||||||
|
path: '/not-found',
|
||||||
|
name: 'not-found',
|
||||||
|
...notFoundRoute
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
component: p404,
|
...notFoundRoute
|
||||||
meta: {
|
|
||||||
layout: 'blank',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,14 @@ from graphene_django.filter import DjangoFilterConnectionField
|
||||||
from books.models import Topic, Module
|
from books.models import Topic, Module
|
||||||
from books.schema.nodes import ModuleNode
|
from books.schema.nodes import ModuleNode
|
||||||
|
|
||||||
|
class NotFoundFailure:
|
||||||
|
reason = 'Not Found'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class NotFound(graphene.ObjectType):
|
||||||
|
reason = graphene.String()
|
||||||
|
|
||||||
|
|
||||||
class TopicNode(DjangoObjectType):
|
class TopicNode(DjangoObjectType):
|
||||||
pk = graphene.Int()
|
pk = graphene.Int()
|
||||||
|
|
@ -27,3 +35,14 @@ class TopicNode(DjangoObjectType):
|
||||||
|
|
||||||
def resolve_modules(self, *args, **kwargs):
|
def resolve_modules(self, *args, **kwargs):
|
||||||
return Module.get_by_parent(self)
|
return Module.get_by_parent(self)
|
||||||
|
|
||||||
|
|
||||||
|
class TopicOr404Node(graphene.Union):
|
||||||
|
class Meta:
|
||||||
|
types = (TopicNode, NotFound)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_type(cls, instance, info):
|
||||||
|
if type(instance).__name__ == "Topic":
|
||||||
|
return TopicNode
|
||||||
|
return NotFound
|
||||||
|
|
|
||||||
|
|
@ -5,7 +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 .connections import TopicConnection, ModuleConnection
|
from .connections import TopicConnection, ModuleConnection
|
||||||
from .nodes import ContentBlockNode, ChapterNode, ModuleNode, TopicNode, SnapshotNode
|
from .nodes import ContentBlockNode, ChapterNode, ModuleNode, NotFoundFailure, SnapshotNode, \
|
||||||
|
TopicOr404Node
|
||||||
from ..models import Book, Topic, Module, Chapter, Snapshot
|
from ..models import Book, Topic, Module, Chapter, Snapshot
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
@ -13,7 +14,7 @@ logger = get_logger(__name__)
|
||||||
|
|
||||||
class BookQuery(object):
|
class BookQuery(object):
|
||||||
node = relay.Node.Field()
|
node = relay.Node.Field()
|
||||||
topic = graphene.Field(TopicNode, slug=graphene.String())
|
topic = graphene.Field(TopicOr404Node, slug=graphene.String())
|
||||||
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)
|
||||||
|
|
@ -63,5 +64,8 @@ class BookQuery(object):
|
||||||
if id is not None:
|
if id is not None:
|
||||||
return get_object(Topic, id)
|
return get_object(Topic, id)
|
||||||
if slug is not None:
|
if slug is not None:
|
||||||
return Topic.objects.get(slug=slug)
|
try:
|
||||||
|
return Topic.objects.get(slug=slug)
|
||||||
|
except Topic.DoesNotExist:
|
||||||
|
return NotFoundFailure
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
from graphql.error import GraphQLLocatedError
|
||||||
|
|
||||||
|
from core.tests.base_test import SkillboxTestCase
|
||||||
|
|
||||||
|
TOPIC_QUERY = """
|
||||||
|
query TopicQuery($slug: String!) {
|
||||||
|
topic(slug: $slug) {
|
||||||
|
__typename
|
||||||
|
...on TopicNode {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
...on NotFound {
|
||||||
|
reason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ContentBlockTestCase(SkillboxTestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.createDefault()
|
||||||
|
self.client = self.get_client()
|
||||||
|
|
||||||
|
def test_topic(self):
|
||||||
|
slug = "non-existing"
|
||||||
|
result = self.client.execute(TOPIC_QUERY, variables={
|
||||||
|
"slug": slug
|
||||||
|
})
|
||||||
|
self.assertIsNone(result.get('errors'))
|
||||||
|
topic = result.get('data').get('topic')
|
||||||
|
self.assertEqual(topic.get('__typename'), 'NotFound')
|
||||||
|
self.assertEqual(topic.get('reason'), 'Not Found')
|
||||||
|
|
@ -702,6 +702,10 @@ interface Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NotFound {
|
||||||
|
reason: String
|
||||||
|
}
|
||||||
|
|
||||||
type NoteNode implements Node {
|
type NoteNode implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
text: String!
|
text: String!
|
||||||
|
|
@ -852,7 +856,7 @@ type Query {
|
||||||
assignment(id: ID!): AssignmentNode
|
assignment(id: ID!): AssignmentNode
|
||||||
assignments: [AssignmentNode]
|
assignments: [AssignmentNode]
|
||||||
node(id: ID!): Node
|
node(id: ID!): Node
|
||||||
topic(slug: String): TopicNode
|
topic(slug: String): TopicOr404Node
|
||||||
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
|
||||||
|
|
@ -1127,6 +1131,8 @@ type TopicNode implements Node {
|
||||||
modules(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection
|
modules(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
union TopicOr404Node = TopicNode | NotFound
|
||||||
|
|
||||||
scalar UUID
|
scalar UUID
|
||||||
|
|
||||||
input UpdateAnswerArgument {
|
input UpdateAnswerArgument {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue