Repository: oppia/oppia

Issue:
Backend api service in frontend should return domain object instead of dict
Currently, the backend API services make https requests and return dict objects to the caller method. The expected behavior for these services are to return a domain object instead. 

PR's for reference: #8121 , #8147 

To be assigned to a file or for any queries, comment on the thread and tag @gp201 

In the listed files below, do the changes such that the services return domain object instead of the dict:
- [ ] core/templates/domain/exploration/editable-exploration-backend-api.service.ts @agizoni 
- [ ] core/templates/domain/exploration/read-only-exploration-backend-api.service.ts
- [ ] core/templates/domain/question/editable-question-backend-api.service.ts
- [ ] core/templates/domain/skill/skill-backend-api.service.ts @gnurris
- [ ] core/templates/domain/question/pretest-question-backend-api.service.ts @cgwon
- [ ] core/templates/domain/question/question-backend-api.service.ts 
- [ ] core/templates/domain/story/editable-story-backend-api.service.ts
- [ ] core/templates/domain/summary/exploration-summary-backend-api.service.ts
- [ ] core/templates/domain/topic/editable-topic-backend-api.service.ts
- [ ] core/templates/domain/topic/topic-rights-backend-api.service.ts
- [ ] core/templates/pages/admin-page/services/admin-backend-api.service.ts


<details><summary>Completed files (for reference)</summary>

- [x] core/templates/services/exploration-features-backend-api.service.ts @nishantwrp 
- [x] core/templates/services/playthrough-issues-backend-api.service.ts @nishantwrp 
- [x] core/templates/services/state-top-answers-stats-backend-api.service.ts @nishantwrp
- [x] core/templates/domain/topics_and_skills_dashboard/topics-and-skills-dashboard-backend-api.service.ts @nishantwrp
- [x] core/templates/pages/community-dashboard-page/services/contribution-opportunities-backend-api.service.ts @nishantwrp
- [x] core/templates/domain/topic_viewer/topic-viewer-backend-api.service.ts @nishantwrp
- [x] core/templates/domain/story_viewer/story-viewer-backend-api.service.ts @nishantwrp
- [x] core/templates/domain/subtopic_viewer/subtopic-viewer-backend-api.service.ts @nishantwrp 
- [x] core/templates/domain/skill/editable-skill-backend-api.service.ts @nishantwrp
- [x] core/templates/domain/skill/skill-mastery-backend-api.service.ts @MegrezZhu 
- [x] core/templates/domain/skill/skill-rights-backend-api.service.ts @nishantwrp
- [x] core/templates/domain/statistics/learner-answer-details-backend-api.service.ts @nishantwrp
- [x] core/templates/domain/classroom/classroom-backend-api.service.ts @nishantwrp
- [x] core/templates/domain/collection/collection-rights-backend-api.service.ts @shavavo
- [x] core/templates/domain/collection/editable-collection-backend-api.service.ts @nishantwrp
- [x] core/templates/domain/collection/read-only-collection-backend-api.service.ts @nishantwrp
- [x] core/templates/domain/collection/search-explorations-backend-api.service.ts @nishantwrp
</details>

Note: 
- You have to find the relevant domain class in the frontend to create an object if they don't exist create a new one and use them to create object out of the dict and return the domain object: 
- You have to refactor the files which use these services so that they are in sync with the changes.

Note: For a guide on how to access Oppia's webpages, see [this](https://github.com/oppia/oppia/wiki/How-to-access-Oppia-webpages).


Discussion:
### How to solve a part of this issue:
Assume we are working for: core/templates/dev/head/domain/classroom/classroom-backend-api.service.ts

1. Open the file and check what's the return type of the https request.
     - In our case, I see we are collecting `topic_summary_dicts` around line 34
https://github.com/oppia/oppia/blob/0ca2649a9acd31e71e46a7f9f2d3e05ae80ce72a/core/templates/dev/head/domain/classroom/classroom-backend-api.service.ts#L33-L35
2. Now, check whether there exists any such class for the return type?  
   - I started looking up for topic summary domain class and luckily I found one at "core/templates/dev/head/domain/topic/TopicSummaryObjectFactory.ts" (I found it through `git grep -i TopicSummaryObject` assuming we follow camelCase in the frontend, if  you won't find anything through this I would suggest looking into `core/templates/dev/head/domain` folder.)
3. If a domain class exist then use the domain class to create domain object out of the dict returned by the https request else create a new domain class inside `core/templates/dev/head/domain` folder and use it.

4. Now I started refactoring the files which were using this service.
    - Run `git grep ClassroomBackendApiService` this will show us all the places where this service is used.
    - Now, look at the `core/templates/dev/head/domain/topic/TopicSummaryObjectFactory.ts` file and what are the methods or data provided by this domain object and change all the files as required.
    - If required (in a worst-case) you can create new methods in the domain class for your requirements.      

5. The most important step "Testing":
    - Start with running the server and add a few `console.log` message in the backend-api.service as well as the file which is using that service to make sure the https calls are getting triggered.
    - The file and service name are self-explanatory so it would be easy to understand how I can trigger the https call. For a new contributor, this can be complicated, if you don't know how to proceed to feel free to raise your question on Gitter and/or in this thread.   

Hi Sandeep, thank you for the amazing explanation. I would like to get assigned to this issue.
I'm also curious about the reason for such a change. Is it mainly to adhere to a certain style?
@DubeySandeep I would like to work on `core/templates/dev/head/domain/classroom/classroom-backend-api.service.ts`
Hi @nishantwrp, just a friendly notice. I am still in progress on core/templates/dev/head/domain/classroom/classroom-backend-api.service.ts. 
Hi @aunghoo, I'm Sorry didn't see that. I'd like to work on  `core/templates/dev/head/domain/subtopic_viewer/subtopic-viewer-backend-api.service.ts` then.
No problem!
Assigning @Shruti6814 to `core/templates/dev/head/domain/collection/editable-collection-backend-api.service.ts` 
Hello! I would like to work on core/templates/dev/head/domain/skill/concept-card
-backend-api.service.ts . 

@sudiptapradhan1181 Assigned you.
I have refactored the core/templates/dev/head/domain/skill/concept-card
-backend-api.service.ts file, and also refactored the associated files, but when I am trying to push the changes, there are frontend test errors in the /concept-card
-backend-api.service.spec.ts files which I am not able to understand how to solve. Can somebody please tell how to properly refactor the .spec files, like if there is any resource I need to go through to solve the issue.
Hi @sudiptapradhan1181, Here are a few links which you can find helpful:
 1. Guidelines for [writing frontend unit tests](https://github.com/oppia/oppia/wiki/Frontend-test-best-practices) 
2. As you are writing a test for the API services you'll find the 5th bullet of [this wiki page](https://github.com/oppia/oppia/wiki/Frontend-test-best-practices#best-practices-for-good-tests) helpful.
3. [Jasmin official tutorial](https://jasmine.github.io/tutorials/your_first_suite) for writing unit tests. 


In general, if you need any help then try adding more information about the error you are facing (like adding error logs, what you have tried to resolve the issue, etc.) so that others can help.
Thanks, @DubeySandeep . I will go through the resources and try to solve the issues. If I still face problems, I will ask for help with additional information.
I am slightly confused, in core/templates/dev/head/domain/story_viewer/story-viewer-backend-api.service.ts should we return a story object instead of story data dictionary.
I would like to work on core/templates/dev/head/domain/question/question-backend-api.service.ts
#8422 Could someone add a `CHANGELOG` label? And, how do I find the codeowner information?
Hi @DubeySandeep , I would like to fix `core/templates/dev/head/domain/topic_viewer/topic-viewer-backend-api.service.ts`. Are there steps to create domain class as I'll need for `topicDataObjectFactory`. 
@seanlip Could you rerun the e2e tests for #8500. I don't know why they failed.
Also, I submitted a PR for `pretest-question-backend-api.service.ts` and didn't ask to be assigned to it. Is that okay?
I just tried restarting it, let's see if it helps.

In general, please ask to be assigned to an issue before taking it up. You don't have to wait for someone to say yes -- you can start working on it as soon as you've mentioned that you'd like to take it up -- but this signals to others that you're working on it and helps to prevent cases of overlap.
@seanlip I am sorry about that. Could you assign `pretest-question-backend-api.service.ts` to me?
Sure! Done.
I would like to work on `core/templates/dev/head/domain/skill/editable-skill-backend-api.service.ts`. Can it be assigned to me?
Hi @hmtj99, I've assigned you to the issue, you can go ahead and start working on it! :) 
Thanks.
Hi @hmtj99, any progress on the issue?

Please let us know if you need any help.
I would like to work on : core/templates/dev/head/domain/summary/exploration-summary-backend-api.service.ts

Could someone assign it to me?
Sorry I got a little sick for a while and was not able to work on it. I'll
get back to it as soon as possible and report the fix by tuesday.

On Tue, Feb 18, 2020 at 4:25 PM Nitish Bansal <notifications@github.com>
wrote:

> Hi @hmtj99 <https://github.com/hmtj99>, any progress on the issue?
>
> Please let us know if you need any help.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/oppia/oppia/issues/8015?email_source=notifications&email_token=AK453VZDROVO4DEIZ2RQY7TRDO5BBA5CNFSM4JOFQIJ2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEMBQVHI#issuecomment-587401885>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AK453V2K4Z6KXYZG2L4YCRDRDO5BBANCNFSM4JOFQIJQ>
> .
>

Hey @hmtj99, I hope you got better. Could you please update us with your progress?
I am not working on `pretest-question-backend-api.service.ts`. Please unassign me.
Hey @hmtj99, could you please let me know your progress on this?
I am also not working on the file, Please unassign me.

On Fri, Mar 20, 2020 at 9:41 AM Nitish Bansal <notifications@github.com>
wrote:

> Hey @hmtj99 <https://github.com/hmtj99>, could you please let me know
> your progress on this?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/oppia/oppia/issues/8015#issuecomment-601519722>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AK453VYQTGDO5O7ZFI2UNDDRILUGRANCNFSM4JOFQIJQ>
> .
>

Hi @DubeySandeep can I work on core/templates/dev/head/domain/statistics/learner-answer-details-backend-api.service.ts?
Hi @yudani-s, welcome! Please follow [these instructions](https://github.com/oppia/oppia/wiki/Contributing-code-to-Oppia#setting-things-up) first -- once you've done so, we'd be happy to assign you to an issue. Thanks!
@seanlip I think I have completed all of those instructions now.

I would like to help work on this issue, but I cannot seem to find a dev folder within templates. I can find the domain folder directly in the templates folder, however. Is this a problem?
Hi @yudani-s, thanks for the note! This was an error and I have updated the description. (We removed the additional subdirectories a little while back but forgot to fix this issue description.) Thanks for bringing it to our attention! 
@seanlip My pleasure. It seems like core/templates/domain/question/question-backend-api.service.ts has not been updated in a while. Could I work on this?
Sure, assigning you. Thanks!
If I try to import QuestionObjectFactory in core/templates/domain/question/pretest-question-backend-api.service.ts, I get an error saying QuestionObjectFactory is not a module. I believe this is due to a difference in Angular versions. Does anyone know a way to work around this?
Hm, I think QuestionObjectFactory might need to be upgraded for this but I'm not sure. Tagging @bansalnitish and @ankita240796 for advice.
I seem to have reached a dead end working on core/templates/domain/question/question-backend-api.service.ts. Could I work on core/templates/domain/question/editable-question-backend-api.service.ts instead?

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
@@ -71,13 +71,17 @@ def get(self, topic_name):
                 additional_story_id) for additional_story_id
             in additional_story_ids]
 
-        canonical_story_dicts = [
-            summary.to_human_readable_dict() for summary
-            in canonical_story_summaries]
-
-        additional_story_dicts = [
-            summary.to_human_readable_dict() for summary
-            in additional_story_summaries]
+        canonical_story_dicts = []
+        for story_summary in canonical_story_summaries:
+            story_summary_dict = story_summary.to_human_readable_dict()
+            story_summary_dict['published'] = True
+            canonical_story_dicts.append(story_summary_dict)
+
+        additional_story_dicts = []
+        for story_summary in additional_story_summaries:
+            story_summary_dict = story_summary.to_human_readable_dict()
+            story_summary_dict['published'] = True
+            additional_story_dicts.append(story_summary_dict)
 
         uncategorized_skill_ids = topic.get_all_uncategorized_skill_ids()
         subtopics = topic.get_all_subtopics()
diff --git a/core/domain/story_domain.py b/core/domain/story_domain.py
--- a/core/domain/story_domain.py
+++ b/core/domain/story_domain.py
@@ -1192,5 +1192,6 @@ def to_human_readable_dict(self):
         return {
             'id': self.id,
             'title': self.title,
-            'description': self.description
+            'description': self.description,
+            'node_count': self.node_count
         }
diff --git a/scripts/check_frontend_coverage.py b/scripts/check_frontend_coverage.py
--- a/scripts/check_frontend_coverage.py
+++ b/scripts/check_frontend_coverage.py
@@ -280,6 +280,7 @@
     'QuestionSummaryObjectFactory.ts',
     'rating-computation.service.ts',
     'read-only-exploration-backend-api.service.ts',
+    'read-only-topic-object.factory.ts',
     'ReadOnlyStoryNodeObjectFactory.ts',
     'ReadOnlySubtopicPageObjectFactory.ts',
     'replace-inputs-with-ellipses.filter.ts',


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
@@ -43,14 +43,22 @@ def setUp(self):
         self.admin = user_services.UserActionsInfo(self.admin_id)
 
         self.topic_id = 'topic'
-        self.story_id = 'story'
+        self.story_id_1 = 'story_id_1'
+        self.story_id_2 = 'story_id_2'
         self.topic_id_1 = 'topic1'
+        self.topic_id_2 = 'topic2'
         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)
-        self.story.description = 'story_description'
+        self.story_1 = story_domain.Story.create_default_story(
+            self.story_id_1, 'story_title', self.topic_id_1)
+        self.story_1.description = 'story_description'
+        self.story_1.node_count = 0
+
+        self.story_2 = story_domain.Story.create_default_story(
+            self.story_id_2, 'story_title', self.topic_id_2)
+        self.story_2.description = 'story_description'
+        self.story_2.node_count = 0
 
         self.topic = topic_domain.Topic.create_default_topic(
             self.topic_id, 'public_topic_name', 'abbrev')
@@ -61,9 +69,14 @@ def setUp(self):
         self.topic.thumbnail_filename = 'Image.png'
         self.topic.canonical_story_references.append(
             topic_domain.StoryReference.create_default_story_reference(
-                self.story_id))
+                self.story_id_1))
+        self.topic.additional_story_references.append(
+            topic_domain.StoryReference.create_default_story_reference(
+                self.story_id_2))
+
         topic_services.save_new_topic(self.admin_id, self.topic)
-        story_services.save_new_story(self.admin_id, self.story)
+        story_services.save_new_story(self.admin_id, self.story_1)
+        story_services.save_new_story(self.admin_id, self.story_2)
 
         self.topic = topic_domain.Topic.create_default_topic(
             self.topic_id_1, 'private_topic_name', 'abbrev')
@@ -72,7 +85,9 @@ def setUp(self):
 
         topic_services.publish_topic(self.topic_id, self.admin_id)
         topic_services.publish_story(
-            self.topic_id, self.story_id, self.admin_id)
+            self.topic_id, self.story_id_1, self.admin_id)
+        topic_services.publish_story(
+            self.topic_id, self.story_id_2, self.admin_id)
 
         self.save_new_skill(
             self.skill_id_1, self.user_id, description='Skill Description 1')
@@ -122,11 +137,19 @@ def test_get_with_no_user_logged_in(self):
                 '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
+                    'id': self.story_1.id,
+                    'title': self.story_1.title,
+                    'description': self.story_1.description,
+                    'node_count': self.story_1.node_count,
+                    'published': True
+                }],
+                'additional_story_dicts': [{
+                    'id': self.story_2.id,
+                    'title': self.story_2.title,
+                    'description': self.story_2.description,
+                    'node_count': self.story_2.node_count,
+                    'published': True
                 }],
-                'additional_story_dicts': [],
                 'uncategorized_skill_ids': [self.skill_id_1],
                 'subtopics': [{
                     u'skill_ids': [self.skill_id_2],
@@ -168,11 +191,19 @@ def test_get_with_user_logged_in(self):
                     '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
+                        'id': self.story_1.id,
+                        'title': self.story_1.title,
+                        'description': self.story_1.description,
+                        'node_count': self.story_1.node_count,
+                        'published': True
+                    }],
+                    'additional_story_dicts': [{
+                        'id': self.story_2.id,
+                        'title': self.story_2.title,
+                        'description': self.story_2.description,
+                        'node_count': self.story_2.node_count,
+                        'published': True
                     }],
-                    'additional_story_dicts': [],
                     'uncategorized_skill_ids': [self.skill_id_1],
                     'subtopics': [{
                         u'skill_ids': [self.skill_id_2],
diff --git a/core/domain/story_domain_test.py b/core/domain/story_domain_test.py
--- a/core/domain/story_domain_test.py
+++ b/core/domain/story_domain_test.py
@@ -264,7 +264,8 @@ def test_to_human_readable_dict(self):
         expected_dict = {
             'id': self.STORY_ID,
             'title': 'Title',
-            'description': 'Description'
+            'description': 'Description',
+            'node_count': 0
         }
 
         self.assertEqual(expected_dict, story_summary.to_human_readable_dict())