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 cache from './cache';
|
||||
|
||||
import {router} from '@/router';
|
||||
|
||||
export default function (uri, networkErrorCallback) {
|
||||
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;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
composedLink = ApolloLink.from([
|
||||
createOmitTypenameLink,
|
||||
errorLink,
|
||||
httpLink
|
||||
notFoundLink,
|
||||
httpLink,
|
||||
]);
|
||||
} else {
|
||||
composedLink = ApolloLink.from([
|
||||
consoleLink,
|
||||
createOmitTypenameLink,
|
||||
errorLink,
|
||||
httpLink
|
||||
notFoundLink,
|
||||
httpLink,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
fragment NotFoundParts on NotFound {
|
||||
reason
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#import "../fragments/topicParts.gql"
|
||||
#import "../fragments/notFoundParts.gql"
|
||||
query Topic($slug: String!){
|
||||
topic(slug: $slug) {
|
||||
...TopicParts
|
||||
...NotFoundParts
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
BookTopicNavigation,
|
||||
ModuleTeaser,
|
||||
PlayIcon,
|
||||
BulbIcon
|
||||
BulbIcon,
|
||||
},
|
||||
|
||||
apollo: {
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
return {
|
||||
query: TOPIC_QUERY,
|
||||
variables: {
|
||||
slug: this.$route.params.topicSlug
|
||||
slug: this.$route.params.topicSlug,
|
||||
},
|
||||
update(data) {
|
||||
return this.$getRidOfEdges(data).topic || {};
|
||||
|
|
@ -81,26 +81,26 @@
|
|||
this.saveMe = false;
|
||||
this.updateLastVisitedTopic(this.topic.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
topic: {
|
||||
modules: {
|
||||
edges: []
|
||||
}
|
||||
edges: [],
|
||||
},
|
||||
},
|
||||
saveMe: false
|
||||
saveMe: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
modules() {
|
||||
return this.topic.modules;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
|
@ -116,12 +116,15 @@
|
|||
this.$store.dispatch('showFullscreenVideo', this.topic.vimeoId);
|
||||
},
|
||||
updateLastVisitedTopic(topicId) {
|
||||
if (!topicId) {
|
||||
return;
|
||||
}
|
||||
this.$apollo.mutate({
|
||||
mutation: UPDATE_LAST_TOPIC_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
id: topicId
|
||||
}
|
||||
id: topicId,
|
||||
},
|
||||
},
|
||||
update(store, {data: {updateLastTopic: {topic}}}) {
|
||||
if (topic) {
|
||||
|
|
@ -131,15 +134,15 @@
|
|||
const data = {
|
||||
me: {
|
||||
...me,
|
||||
lastTopic: topic
|
||||
}
|
||||
lastTopic: topic,
|
||||
},
|
||||
};
|
||||
store.writeQuery({query, data});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -33,11 +33,18 @@ const submission = () => import('@/pages/studentSubmission');
|
|||
|
||||
const postLoginRedirectUrlKey = 'postLoginRedirectionUrl';
|
||||
|
||||
const notFoundRoute = {
|
||||
component: p404,
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
},
|
||||
};
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: start
|
||||
component: start,
|
||||
},
|
||||
...moduleRoutes,
|
||||
...authRoutes,
|
||||
|
|
@ -85,15 +92,17 @@ const routes = [
|
|||
default:
|
||||
return '/unknown-auth-error';
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{path: '/styleguide', component: styleGuidePage},
|
||||
{
|
||||
path: '/not-found',
|
||||
name: 'not-found',
|
||||
...notFoundRoute
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
component: p404,
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
},
|
||||
...notFoundRoute
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@ from graphene_django.filter import DjangoFilterConnectionField
|
|||
from books.models import Topic, Module
|
||||
from books.schema.nodes import ModuleNode
|
||||
|
||||
class NotFoundFailure:
|
||||
reason = 'Not Found'
|
||||
|
||||
|
||||
|
||||
class NotFound(graphene.ObjectType):
|
||||
reason = graphene.String()
|
||||
|
||||
|
||||
class TopicNode(DjangoObjectType):
|
||||
pk = graphene.Int()
|
||||
|
|
@ -27,3 +35,14 @@ class TopicNode(DjangoObjectType):
|
|||
|
||||
def resolve_modules(self, *args, **kwargs):
|
||||
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 core.logger import get_logger
|
||||
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
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
|
@ -13,7 +14,7 @@ logger = get_logger(__name__)
|
|||
|
||||
class BookQuery(object):
|
||||
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())
|
||||
chapter = relay.Node.Field(ChapterNode)
|
||||
content_block = relay.Node.Field(ContentBlockNode)
|
||||
|
|
@ -63,5 +64,8 @@ class BookQuery(object):
|
|||
if id is not None:
|
||||
return get_object(Topic, id)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
}
|
||||
|
||||
type NotFound {
|
||||
reason: String
|
||||
}
|
||||
|
||||
type NoteNode implements Node {
|
||||
id: ID!
|
||||
text: String!
|
||||
|
|
@ -852,7 +856,7 @@ type Query {
|
|||
assignment(id: ID!): AssignmentNode
|
||||
assignments: [AssignmentNode]
|
||||
node(id: ID!): Node
|
||||
topic(slug: String): TopicNode
|
||||
topic(slug: String): TopicOr404Node
|
||||
module(slug: String, id: ID): ModuleNode
|
||||
chapter(id: ID!): ChapterNode
|
||||
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
|
||||
}
|
||||
|
||||
union TopicOr404Node = TopicNode | NotFound
|
||||
|
||||
scalar UUID
|
||||
|
||||
input UpdateAnswerArgument {
|
||||
|
|
|
|||
Loading…
Reference in New Issue