Repository: oppia/oppia

Issue:
Show skill mastery values in the topic viewer
Add a skill tab in the topic viewer that will show skill mastery of all skills in that topic (Once we have enough skill mastery information for the skill)

Milestone 3.2 in @sophiewu6 's GSoC project


Discussion:

Resolving patch:
diff --git a/core/controllers/topic_viewer.py b/core/controllers/topic_viewer.py
--- a/core/controllers/topic_viewer.py
+++ b/core/controllers/topic_viewer.py
@@ -17,6 +17,7 @@
 from constants import constants
 from core.controllers import acl_decorators
 from core.controllers import base
+from core.domain import skill_services
 from core.domain import story_fetchers
 from core.domain import topic_fetchers
 import feconf
@@ -74,12 +75,26 @@ def get(self, topic_name):
         uncategorized_skill_ids = topic.get_all_uncategorized_skill_ids()
         subtopics = topic.get_all_subtopics()
 
+        assigned_skill_ids = topic.get_all_skill_ids()
+        skill_descriptions = skill_services.get_skill_descriptions_by_ids(
+            topic.id, assigned_skill_ids)
+
+        if self.user_id:
+            degrees_of_mastery = skill_services.get_multi_user_skill_mastery(
+                self.user_id, assigned_skill_ids)
+        else:
+            degrees_of_mastery = {}
+            for skill_id in assigned_skill_ids:
+                degrees_of_mastery[skill_id] = None
+
         self.values.update({
             'topic_id': topic.id,
             'topic_name': topic.name,
             'canonical_story_dicts': canonical_story_dicts,
             'additional_story_dicts': additional_story_dicts,
             'uncategorized_skill_ids': uncategorized_skill_ids,
-            'subtopics': subtopics
+            'subtopics': subtopics,
+            'degrees_of_mastery': degrees_of_mastery,
+            'skill_descriptions': skill_descriptions
         })
         self.render_json(self.values)


Test patch:
diff --git a/core/controllers/topic_viewer_test.py b/core/controllers/topic_viewer_test.py
--- a/core/controllers/topic_viewer_test.py
+++ b/core/controllers/topic_viewer_test.py
@@ -15,6 +15,7 @@
 """Tests for the topic viewer page."""
 
 from constants import constants
+from core.domain import skill_services
 from core.domain import story_domain
 from core.domain import story_services
 from core.domain import topic_domain
@@ -29,6 +30,8 @@ class BaseTopicViewerControllerTests(test_utils.GenericTestBase):
     def setUp(self):
         """Completes the sign-up process for the various users."""
         super(BaseTopicViewerControllerTests, self).setUp()
+        self.signup(self.NEW_USER_EMAIL, self.NEW_USER_USERNAME)
+        self.user_id = self.get_user_id_from_email(self.NEW_USER_EMAIL)
         self.signup(self.ADMIN_EMAIL, self.ADMIN_USERNAME)
         self.admin_id = self.get_user_id_from_email(self.ADMIN_EMAIL)
         self.set_admins([self.ADMIN_USERNAME])
@@ -37,6 +40,8 @@ def setUp(self):
         self.topic_id = 'topic'
         self.story_id = 'story'
         self.topic_id_1 = 'topic1'
+        self.skill_id_1 = skill_services.get_new_skill_id()
+        self.skill_id_2 = skill_services.get_new_skill_id()
 
         self.story = story_domain.Story.create_default_story(
             self.story_id, 'story_title', self.topic_id_1)
@@ -44,9 +49,9 @@ def setUp(self):
 
         self.topic = topic_domain.Topic.create_default_topic(
             self.topic_id, 'public_topic_name')
-        self.topic.uncategorized_skill_ids.append('skill_id_1')
+        self.topic.uncategorized_skill_ids.append(self.skill_id_1)
         self.topic.subtopics.append(topic_domain.Subtopic(
-            1, 'subtopic_name', ['skill_id_2']))
+            1, 'subtopic_name', [self.skill_id_2]))
         self.topic.next_subtopic_id = 2
         self.topic.canonical_story_references.append(
             topic_domain.StoryReference.create_default_story_reference(
@@ -62,6 +67,15 @@ def setUp(self):
         topic_services.publish_story(
             self.topic_id, self.story_id, self.admin_id)
 
+        self.save_new_skill(
+            self.skill_id_1, self.user_id, 'Skill Description 1')
+        self.save_new_skill(
+            self.skill_id_2, self.user_id, 'Skill Description 2')
+        skill_services.create_user_skill_mastery(
+            self.user_id, self.skill_id_1, 0.3)
+        skill_services.create_user_skill_mastery(
+            self.user_id, self.skill_id_2, 0.5)
+
 
 class TopicViewerPageTests(BaseTopicViewerControllerTests):
 
@@ -88,7 +102,7 @@ def test_get_fails_when_new_structures_not_enabled(self):
 
 class TopicPageDataHandlerTests(BaseTopicViewerControllerTests):
 
-    def test_get(self):
+    def test_get_with_no_user_logged_in(self):
         with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', True):
             json_response = self.get_json(
                 '%s/%s' % (feconf.TOPIC_DATA_HANDLER, 'public_topic_name'))
@@ -101,14 +115,54 @@ def test_get(self):
                     'description': self.story.description
                 }],
                 'additional_story_dicts': [],
-                'uncategorized_skill_ids': [u'skill_id_1'],
+                'uncategorized_skill_ids': [self.skill_id_1],
                 'subtopics': [{
-                    u'skill_ids': [u'skill_id_2'],
+                    u'skill_ids': [self.skill_id_2],
                     u'id': 1,
-                    u'title': u'subtopic_name'}]
+                    u'title': u'subtopic_name'}],
+                'degrees_of_mastery': {
+                    self.skill_id_1: None,
+                    self.skill_id_2: None
+                },
+                'skill_descriptions': {
+                    self.skill_id_1: 'Skill Description 1',
+                    self.skill_id_2: 'Skill Description 2'
+                }
             }
             self.assertDictContainsSubset(expected_dict, json_response)
 
+    def test_get_with_user_logged_in(self):
+        with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', True):
+            self.login(self.NEW_USER_EMAIL)
+            json_response = self.get_json(
+                '%s/%s' % (feconf.TOPIC_DATA_HANDLER, 'public_topic_name'))
+            expected_dict = {
+                'topic_name': 'public_topic_name',
+                'topic_id': self.topic_id,
+                'canonical_story_dicts': [{
+                    'id': self.story.id,
+                    'title': self.story.title,
+                    'description': self.story.description
+                }],
+                'additional_story_dicts': [],
+                'uncategorized_skill_ids': [self.skill_id_1],
+                'subtopics': [{
+                    u'skill_ids': [self.skill_id_2],
+                    u'id': 1,
+                    u'title': u'subtopic_name'}],
+                'degrees_of_mastery': {
+                    self.skill_id_1: 0.3,
+                    self.skill_id_2: 0.5
+                },
+                'skill_descriptions': {
+                    self.skill_id_1: 'Skill Description 1',
+                    self.skill_id_2: 'Skill Description 2'
+                }
+            }
+            self.assertDictContainsSubset(expected_dict, json_response)
+
+            self.logout()
+
     def test_get_fails_when_new_structures_not_enabled(self):
         with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', False):
             self.get_json(