Merged in feature/topic-404 (pull request #106)

Feature/topic 404

Approved-by: Christian Cueni
This commit is contained in:
Ramon Wenger 2022-05-23 18:21:37 +00:00
commit 7da52b03a1
9 changed files with 124 additions and 26 deletions

View File

@ -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,
]);
}

View File

@ -0,0 +1,3 @@
fragment NotFoundParts on NotFound {
reason
}

View File

@ -1,6 +1,8 @@
#import "../fragments/topicParts.gql"
#import "../fragments/notFoundParts.gql"
query Topic($slug: String!){
topic(slug: $slug) {
...TopicParts
...NotFoundParts
}
}

View File

@ -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>

View File

@ -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
},
];

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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 {