We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
"Invalid Date" Shown for Feedback in Learner Dashboard
**Describe the bug**
When opening a feedback thread in the learner dashboard, "Invalid Date" is shown in place of the date.

**To Reproduce**
Steps to reproduce the behavior:
 1. Leave feedback on an exploration suggesting a change
 2. Open the learner dashboard
 3. Open the thread for the feedback you just left

**Observed behavior**
"Invalid Date" is shown in place of the timestamp:

![Screen Shot 2020-02-09 at 15 13 59](https://user-images.githubusercontent.com/19878639/74112170-2bf1fc00-4b4f-11ea-9e21-ccdcd121b1c1.png)

**Expected behavior**
The correct timestamp should be shown.

**Desktop (please complete the following information; delete this section if the issue does not arise on desktop):**
 - OS: macOS
 - Browser: Firefox
 - Version: `release-2.9.0`

**Additional context**
The correct timestamp is shown before opening a thread:

![Screen Shot 2020-02-09 at 15 13 52](https://user-images.githubusercontent.com/19878639/74112150-006f1180-4b4f-11ea-8b07-7c8f7652baeb.png)
"Invalid Date" Shown for Feedback in Learner Dashboard
**Describe the bug**
When opening a feedback thread in the learner dashboard, "Invalid Date" is shown in place of the date.

**To Reproduce**
Steps to reproduce the behavior:
 1. Leave feedback on an exploration suggesting a change
 2. Open the learner dashboard
 3. Open the thread for the feedback you just left

**Observed behavior**
"Invalid Date" is shown in place of the timestamp:

![Screen Shot 2020-02-09 at 15 13 59](https://user-images.githubusercontent.com/19878639/74112170-2bf1fc00-4b4f-11ea-9e21-ccdcd121b1c1.png)

**Expected behavior**
The correct timestamp should be shown.

**Desktop (please complete the following information; delete this section if the issue does not arise on desktop):**
 - OS: macOS
 - Browser: Firefox
 - Version: `release-2.9.0`

**Additional context**
The correct timestamp is shown before opening a thread:

![Screen Shot 2020-02-09 at 15 13 52](https://user-images.githubusercontent.com/19878639/74112150-006f1180-4b4f-11ea-8b07-7c8f7652baeb.png)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `core/controllers/learner_dashboard.py`
Content:
```
1 # Copyright 2017 The Oppia Authors. All Rights Reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS-IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """Controllers for the learner dashboard."""
16 
17 from __future__ import absolute_import  # pylint: disable=import-only-modules
18 from __future__ import unicode_literals  # pylint: disable=import-only-modules
19 
20 from core.controllers import acl_decorators
21 from core.controllers import base
22 from core.domain import exp_fetchers
23 from core.domain import feedback_services
24 from core.domain import learner_progress_services
25 from core.domain import subscription_services
26 from core.domain import suggestion_services
27 from core.domain import summary_services
28 from core.domain import user_services
29 import feconf
30 import python_utils
31 import utils
32 
33 
34 class LearnerDashboardPage(base.BaseHandler):
35     """Page showing the user's learner dashboard."""
36 
37     @acl_decorators.can_access_learner_dashboard
38     def get(self):
39         """Handles GET requests."""
40         self.render_template('learner-dashboard-page.mainpage.html')
41 
42 
43 class LearnerDashboardHandler(base.BaseHandler):
44     """Provides data for the user's learner dashboard page."""
45 
46     GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON
47 
48     @acl_decorators.can_access_learner_dashboard
49     def get(self):
50         """Handles GET requests."""
51         (
52             learner_progress, number_of_nonexistent_activities,
53             completed_to_incomplete_collections) = (
54                 learner_progress_services.get_activity_progress(self.user_id))
55 
56         completed_exp_summary_dicts = (
57             summary_services.get_displayable_exp_summary_dicts(
58                 learner_progress.completed_exp_summaries))
59 
60         incomplete_exp_summary_dicts = (
61             summary_services.get_displayable_exp_summary_dicts(
62                 learner_progress.incomplete_exp_summaries))
63 
64         completed_collection_summary_dicts = (
65             learner_progress_services.get_collection_summary_dicts(
66                 learner_progress.completed_collection_summaries))
67         incomplete_collection_summary_dicts = (
68             learner_progress_services.get_collection_summary_dicts(
69                 learner_progress.incomplete_collection_summaries))
70 
71         exploration_playlist_summary_dicts = (
72             summary_services.get_displayable_exp_summary_dicts(
73                 learner_progress.exploration_playlist_summaries))
74         collection_playlist_summary_dicts = (
75             learner_progress_services.get_collection_summary_dicts(
76                 learner_progress.collection_playlist_summaries))
77 
78         full_thread_ids = subscription_services.get_all_threads_subscribed_to(
79             self.user_id)
80         if len(full_thread_ids) > 0:
81             thread_summaries, number_of_unread_threads = (
82                 feedback_services.get_thread_summaries(
83                     self.user_id, full_thread_ids))
84         else:
85             thread_summaries, number_of_unread_threads = [], 0
86 
87         creators_subscribed_to = (
88             subscription_services.get_all_creators_subscribed_to(self.user_id))
89         creators_settings = user_services.get_users_settings(
90             creators_subscribed_to)
91         subscription_list = []
92 
93         for index, creator_settings in enumerate(creators_settings):
94             subscription_summary = {
95                 'creator_picture_data_url': (
96                     creator_settings.profile_picture_data_url),
97                 'creator_username': creator_settings.username,
98                 'creator_impact': (
99                     user_services.get_user_impact_score(
100                         creators_subscribed_to[index]))
101             }
102 
103             subscription_list.append(subscription_summary)
104 
105         self.values.update({
106             'completed_explorations_list': completed_exp_summary_dicts,
107             'completed_collections_list': completed_collection_summary_dicts,
108             'incomplete_explorations_list': incomplete_exp_summary_dicts,
109             'incomplete_collections_list': incomplete_collection_summary_dicts,
110             'exploration_playlist': exploration_playlist_summary_dicts,
111             'collection_playlist': collection_playlist_summary_dicts,
112             'number_of_nonexistent_activities': (
113                 number_of_nonexistent_activities),
114             'completed_to_incomplete_collections': (
115                 completed_to_incomplete_collections),
116             'thread_summaries': [s.to_dict() for s in thread_summaries],
117             'number_of_unread_threads': number_of_unread_threads,
118             'subscription_list': subscription_list
119         })
120         self.render_json(self.values)
121 
122 
123 class LearnerDashboardIdsHandler(base.BaseHandler):
124     """Gets the progress of the learner.
125 
126     Gets the ids of all explorations and collections completed by the user,
127     the activities currently being pursued, and the activities present in
128     the playlist.
129     """
130     GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON
131 
132     @acl_decorators.can_access_learner_dashboard
133     def get(self):
134         """Handles GET requests."""
135         learner_dashboard_activities = (
136             learner_progress_services.get_learner_dashboard_activities( # pylint: disable=line-too-long
137                 self.user_id))
138 
139         self.values.update({
140             'learner_dashboard_activity_ids': (
141                 learner_dashboard_activities.to_dict())
142         })
143         self.render_json(self.values)
144 
145 
146 class LearnerDashboardFeedbackThreadHandler(base.BaseHandler):
147     """Gets all the messages in a thread."""
148 
149     GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON
150 
151     @acl_decorators.can_access_learner_dashboard
152     def get(self, thread_id):
153         """Handles GET requests."""
154         messages = feedback_services.get_messages(thread_id)
155         author_ids = [m.author_id for m in messages]
156         authors_settings = user_services.get_users_settings(author_ids)
157 
158         message_ids = [m.message_id for m in messages]
159         feedback_services.update_messages_read_by_the_user(
160             self.user_id, thread_id, message_ids)
161 
162         message_summary_list = []
163         suggestion = suggestion_services.get_suggestion_by_id(thread_id)
164         suggestion_thread = feedback_services.get_thread(thread_id)
165 
166         exploration_id = feedback_services.get_exp_id_from_thread_id(thread_id)
167         if suggestion:
168             exploration = exp_fetchers.get_exploration_by_id(exploration_id)
169             current_content_html = (
170                 exploration.states[
171                     suggestion.change.state_name].content.html)
172             suggestion_summary = {
173                 'suggestion_html': suggestion.change.new_value['html'],
174                 'current_content_html': current_content_html,
175                 'description': suggestion_thread.subject,
176                 'author_username': authors_settings[0].username,
177                 'author_picture_data_url': (
178                     authors_settings[0].profile_picture_data_url)
179             }
180             message_summary_list.append(suggestion_summary)
181             messages.pop(0)
182             authors_settings.pop(0)
183 
184         for m, author_settings in python_utils.ZIP(messages, authors_settings):
185 
186             if author_settings is None:
187                 author_username = None
188                 author_picture_data_url = None
189             else:
190                 author_username = author_settings.username
191                 author_picture_data_url = (
192                     author_settings.profile_picture_data_url)
193 
194             message_summary = {
195                 'message_id': m.message_id,
196                 'text': m.text,
197                 'updated_status': m.updated_status,
198                 'author_username': author_username,
199                 'author_picture_data_url': author_picture_data_url,
200                 'created_on': utils.get_time_in_millisecs(m.created_on)
201             }
202             message_summary_list.append(message_summary)
203 
204         self.render_json({
205             'message_summary_list': message_summary_list
206         })
207
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/core/controllers/learner_dashboard.py b/core/controllers/learner_dashboard.py
--- a/core/controllers/learner_dashboard.py
+++ b/core/controllers/learner_dashboard.py
@@ -175,7 +175,9 @@
                 'description': suggestion_thread.subject,
                 'author_username': authors_settings[0].username,
                 'author_picture_data_url': (
-                    authors_settings[0].profile_picture_data_url)
+                    authors_settings[0].profile_picture_data_url),
+                'created_on': utils.get_time_in_millisecs(
+                    messages[0].created_on)
             }
             message_summary_list.append(suggestion_summary)
             messages.pop(0)

{"golden_diff": "diff --git a/core/controllers/learner_dashboard.py b/core/controllers/learner_dashboard.py\n--- a/core/controllers/learner_dashboard.py\n+++ b/core/controllers/learner_dashboard.py\n@@ -175,7 +175,9 @@\n                 'description': suggestion_thread.subject,\n                 'author_username': authors_settings[0].username,\n                 'author_picture_data_url': (\n-                    authors_settings[0].profile_picture_data_url)\n+                    authors_settings[0].profile_picture_data_url),\n+                'created_on': utils.get_time_in_millisecs(\n+                    messages[0].created_on)\n             }\n             message_summary_list.append(suggestion_summary)\n             messages.pop(0)\n", "issue": "\"Invalid Date\" Shown for Feedback in Learner Dashboard\n**Describe the bug**\r\nWhen opening a feedback thread in the learner dashboard, \"Invalid Date\" is shown in place of the date.\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n 1. Leave feedback on an exploration suggesting a change\r\n 2. Open the learner dashboard\r\n 3. Open the thread for the feedback you just left\r\n\r\n**Observed behavior**\r\n\"Invalid Date\" is shown in place of the timestamp:\r\n\r\n![Screen Shot 2020-02-09 at 15 13 59](https://user-images.githubusercontent.com/19878639/74112170-2bf1fc00-4b4f-11ea-9e21-ccdcd121b1c1.png)\r\n\r\n**Expected behavior**\r\nThe correct timestamp should be shown.\r\n\r\n**Desktop (please complete the following information; delete this section if the issue does not arise on desktop):**\r\n - OS: macOS\r\n - Browser: Firefox\r\n - Version: `release-2.9.0`\r\n\r\n**Additional context**\r\nThe correct timestamp is shown before opening a thread:\r\n\r\n![Screen Shot 2020-02-09 at 15 13 52](https://user-images.githubusercontent.com/19878639/74112150-006f1180-4b4f-11ea-8b07-7c8f7652baeb.png)\n\"Invalid Date\" Shown for Feedback in Learner Dashboard\n**Describe the bug**\r\nWhen opening a feedback thread in the learner dashboard, \"Invalid Date\" is shown in place of the date.\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n 1. Leave feedback on an exploration suggesting a change\r\n 2. Open the learner dashboard\r\n 3. Open the thread for the feedback you just left\r\n\r\n**Observed behavior**\r\n\"Invalid Date\" is shown in place of the timestamp:\r\n\r\n![Screen Shot 2020-02-09 at 15 13 59](https://user-images.githubusercontent.com/19878639/74112170-2bf1fc00-4b4f-11ea-9e21-ccdcd121b1c1.png)\r\n\r\n**Expected behavior**\r\nThe correct timestamp should be shown.\r\n\r\n**Desktop (please complete the following information; delete this section if the issue does not arise on desktop):**\r\n - OS: macOS\r\n - Browser: Firefox\r\n - Version: `release-2.9.0`\r\n\r\n**Additional context**\r\nThe correct timestamp is shown before opening a thread:\r\n\r\n![Screen Shot 2020-02-09 at 15 13 52](https://user-images.githubusercontent.com/19878639/74112150-006f1180-4b4f-11ea-8b07-7c8f7652baeb.png)\n", "before_files": [{"content": "# Copyright 2017 The Oppia Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS-IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Controllers for the learner dashboard.\"\"\"\n\nfrom __future__ import absolute_import  # pylint: disable=import-only-modules\nfrom __future__ import unicode_literals  # pylint: disable=import-only-modules\n\nfrom core.controllers import acl_decorators\nfrom core.controllers import base\nfrom core.domain import exp_fetchers\nfrom core.domain import feedback_services\nfrom core.domain import learner_progress_services\nfrom core.domain import subscription_services\nfrom core.domain import suggestion_services\nfrom core.domain import summary_services\nfrom core.domain import user_services\nimport feconf\nimport python_utils\nimport utils\n\n\nclass LearnerDashboardPage(base.BaseHandler):\n    \"\"\"Page showing the user's learner dashboard.\"\"\"\n\n    @acl_decorators.can_access_learner_dashboard\n    def get(self):\n        \"\"\"Handles GET requests.\"\"\"\n        self.render_template('learner-dashboard-page.mainpage.html')\n\n\nclass LearnerDashboardHandler(base.BaseHandler):\n    \"\"\"Provides data for the user's learner dashboard page.\"\"\"\n\n    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON\n\n    @acl_decorators.can_access_learner_dashboard\n    def get(self):\n        \"\"\"Handles GET requests.\"\"\"\n        (\n            learner_progress, number_of_nonexistent_activities,\n            completed_to_incomplete_collections) = (\n                learner_progress_services.get_activity_progress(self.user_id))\n\n        completed_exp_summary_dicts = (\n            summary_services.get_displayable_exp_summary_dicts(\n                learner_progress.completed_exp_summaries))\n\n        incomplete_exp_summary_dicts = (\n            summary_services.get_displayable_exp_summary_dicts(\n                learner_progress.incomplete_exp_summaries))\n\n        completed_collection_summary_dicts = (\n            learner_progress_services.get_collection_summary_dicts(\n                learner_progress.completed_collection_summaries))\n        incomplete_collection_summary_dicts = (\n            learner_progress_services.get_collection_summary_dicts(\n                learner_progress.incomplete_collection_summaries))\n\n        exploration_playlist_summary_dicts = (\n            summary_services.get_displayable_exp_summary_dicts(\n                learner_progress.exploration_playlist_summaries))\n        collection_playlist_summary_dicts = (\n            learner_progress_services.get_collection_summary_dicts(\n                learner_progress.collection_playlist_summaries))\n\n        full_thread_ids = subscription_services.get_all_threads_subscribed_to(\n            self.user_id)\n        if len(full_thread_ids) > 0:\n            thread_summaries, number_of_unread_threads = (\n                feedback_services.get_thread_summaries(\n                    self.user_id, full_thread_ids))\n        else:\n            thread_summaries, number_of_unread_threads = [], 0\n\n        creators_subscribed_to = (\n            subscription_services.get_all_creators_subscribed_to(self.user_id))\n        creators_settings = user_services.get_users_settings(\n            creators_subscribed_to)\n        subscription_list = []\n\n        for index, creator_settings in enumerate(creators_settings):\n            subscription_summary = {\n                'creator_picture_data_url': (\n                    creator_settings.profile_picture_data_url),\n                'creator_username': creator_settings.username,\n                'creator_impact': (\n                    user_services.get_user_impact_score(\n                        creators_subscribed_to[index]))\n            }\n\n            subscription_list.append(subscription_summary)\n\n        self.values.update({\n            'completed_explorations_list': completed_exp_summary_dicts,\n            'completed_collections_list': completed_collection_summary_dicts,\n            'incomplete_explorations_list': incomplete_exp_summary_dicts,\n            'incomplete_collections_list': incomplete_collection_summary_dicts,\n            'exploration_playlist': exploration_playlist_summary_dicts,\n            'collection_playlist': collection_playlist_summary_dicts,\n            'number_of_nonexistent_activities': (\n                number_of_nonexistent_activities),\n            'completed_to_incomplete_collections': (\n                completed_to_incomplete_collections),\n            'thread_summaries': [s.to_dict() for s in thread_summaries],\n            'number_of_unread_threads': number_of_unread_threads,\n            'subscription_list': subscription_list\n        })\n        self.render_json(self.values)\n\n\nclass LearnerDashboardIdsHandler(base.BaseHandler):\n    \"\"\"Gets the progress of the learner.\n\n    Gets the ids of all explorations and collections completed by the user,\n    the activities currently being pursued, and the activities present in\n    the playlist.\n    \"\"\"\n    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON\n\n    @acl_decorators.can_access_learner_dashboard\n    def get(self):\n        \"\"\"Handles GET requests.\"\"\"\n        learner_dashboard_activities = (\n            learner_progress_services.get_learner_dashboard_activities( # pylint: disable=line-too-long\n                self.user_id))\n\n        self.values.update({\n            'learner_dashboard_activity_ids': (\n                learner_dashboard_activities.to_dict())\n        })\n        self.render_json(self.values)\n\n\nclass LearnerDashboardFeedbackThreadHandler(base.BaseHandler):\n    \"\"\"Gets all the messages in a thread.\"\"\"\n\n    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON\n\n    @acl_decorators.can_access_learner_dashboard\n    def get(self, thread_id):\n        \"\"\"Handles GET requests.\"\"\"\n        messages = feedback_services.get_messages(thread_id)\n        author_ids = [m.author_id for m in messages]\n        authors_settings = user_services.get_users_settings(author_ids)\n\n        message_ids = [m.message_id for m in messages]\n        feedback_services.update_messages_read_by_the_user(\n            self.user_id, thread_id, message_ids)\n\n        message_summary_list = []\n        suggestion = suggestion_services.get_suggestion_by_id(thread_id)\n        suggestion_thread = feedback_services.get_thread(thread_id)\n\n        exploration_id = feedback_services.get_exp_id_from_thread_id(thread_id)\n        if suggestion:\n            exploration = exp_fetchers.get_exploration_by_id(exploration_id)\n            current_content_html = (\n                exploration.states[\n                    suggestion.change.state_name].content.html)\n            suggestion_summary = {\n                'suggestion_html': suggestion.change.new_value['html'],\n                'current_content_html': current_content_html,\n                'description': suggestion_thread.subject,\n                'author_username': authors_settings[0].username,\n                'author_picture_data_url': (\n                    authors_settings[0].profile_picture_data_url)\n            }\n            message_summary_list.append(suggestion_summary)\n            messages.pop(0)\n            authors_settings.pop(0)\n\n        for m, author_settings in python_utils.ZIP(messages, authors_settings):\n\n            if author_settings is None:\n                author_username = None\n                author_picture_data_url = None\n            else:\n                author_username = author_settings.username\n                author_picture_data_url = (\n                    author_settings.profile_picture_data_url)\n\n            message_summary = {\n                'message_id': m.message_id,\n                'text': m.text,\n                'updated_status': m.updated_status,\n                'author_username': author_username,\n                'author_picture_data_url': author_picture_data_url,\n                'created_on': utils.get_time_in_millisecs(m.created_on)\n            }\n            message_summary_list.append(message_summary)\n\n        self.render_json({\n            'message_summary_list': message_summary_list\n        })\n", "path": "core/controllers/learner_dashboard.py"}], "after_files": [{"content": "# Copyright 2017 The Oppia Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS-IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Controllers for the learner dashboard.\"\"\"\n\nfrom __future__ import absolute_import  # pylint: disable=import-only-modules\nfrom __future__ import unicode_literals  # pylint: disable=import-only-modules\n\nfrom core.controllers import acl_decorators\nfrom core.controllers import base\nfrom core.domain import exp_fetchers\nfrom core.domain import feedback_services\nfrom core.domain import learner_progress_services\nfrom core.domain import subscription_services\nfrom core.domain import suggestion_services\nfrom core.domain import summary_services\nfrom core.domain import user_services\nimport feconf\nimport python_utils\nimport utils\n\n\nclass LearnerDashboardPage(base.BaseHandler):\n    \"\"\"Page showing the user's learner dashboard.\"\"\"\n\n    @acl_decorators.can_access_learner_dashboard\n    def get(self):\n        \"\"\"Handles GET requests.\"\"\"\n        self.render_template('learner-dashboard-page.mainpage.html')\n\n\nclass LearnerDashboardHandler(base.BaseHandler):\n    \"\"\"Provides data for the user's learner dashboard page.\"\"\"\n\n    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON\n\n    @acl_decorators.can_access_learner_dashboard\n    def get(self):\n        \"\"\"Handles GET requests.\"\"\"\n        (\n            learner_progress, number_of_nonexistent_activities,\n            completed_to_incomplete_collections) = (\n                learner_progress_services.get_activity_progress(self.user_id))\n\n        completed_exp_summary_dicts = (\n            summary_services.get_displayable_exp_summary_dicts(\n                learner_progress.completed_exp_summaries))\n\n        incomplete_exp_summary_dicts = (\n            summary_services.get_displayable_exp_summary_dicts(\n                learner_progress.incomplete_exp_summaries))\n\n        completed_collection_summary_dicts = (\n            learner_progress_services.get_collection_summary_dicts(\n                learner_progress.completed_collection_summaries))\n        incomplete_collection_summary_dicts = (\n            learner_progress_services.get_collection_summary_dicts(\n                learner_progress.incomplete_collection_summaries))\n\n        exploration_playlist_summary_dicts = (\n            summary_services.get_displayable_exp_summary_dicts(\n                learner_progress.exploration_playlist_summaries))\n        collection_playlist_summary_dicts = (\n            learner_progress_services.get_collection_summary_dicts(\n                learner_progress.collection_playlist_summaries))\n\n        full_thread_ids = subscription_services.get_all_threads_subscribed_to(\n            self.user_id)\n        if len(full_thread_ids) > 0:\n            thread_summaries, number_of_unread_threads = (\n                feedback_services.get_thread_summaries(\n                    self.user_id, full_thread_ids))\n        else:\n            thread_summaries, number_of_unread_threads = [], 0\n\n        creators_subscribed_to = (\n            subscription_services.get_all_creators_subscribed_to(self.user_id))\n        creators_settings = user_services.get_users_settings(\n            creators_subscribed_to)\n        subscription_list = []\n\n        for index, creator_settings in enumerate(creators_settings):\n            subscription_summary = {\n                'creator_picture_data_url': (\n                    creator_settings.profile_picture_data_url),\n                'creator_username': creator_settings.username,\n                'creator_impact': (\n                    user_services.get_user_impact_score(\n                        creators_subscribed_to[index]))\n            }\n\n            subscription_list.append(subscription_summary)\n\n        self.values.update({\n            'completed_explorations_list': completed_exp_summary_dicts,\n            'completed_collections_list': completed_collection_summary_dicts,\n            'incomplete_explorations_list': incomplete_exp_summary_dicts,\n            'incomplete_collections_list': incomplete_collection_summary_dicts,\n            'exploration_playlist': exploration_playlist_summary_dicts,\n            'collection_playlist': collection_playlist_summary_dicts,\n            'number_of_nonexistent_activities': (\n                number_of_nonexistent_activities),\n            'completed_to_incomplete_collections': (\n                completed_to_incomplete_collections),\n            'thread_summaries': [s.to_dict() for s in thread_summaries],\n            'number_of_unread_threads': number_of_unread_threads,\n            'subscription_list': subscription_list\n        })\n        self.render_json(self.values)\n\n\nclass LearnerDashboardIdsHandler(base.BaseHandler):\n    \"\"\"Gets the progress of the learner.\n\n    Gets the ids of all explorations and collections completed by the user,\n    the activities currently being pursued, and the activities present in\n    the playlist.\n    \"\"\"\n    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON\n\n    @acl_decorators.can_access_learner_dashboard\n    def get(self):\n        \"\"\"Handles GET requests.\"\"\"\n        learner_dashboard_activities = (\n            learner_progress_services.get_learner_dashboard_activities( # pylint: disable=line-too-long\n                self.user_id))\n\n        self.values.update({\n            'learner_dashboard_activity_ids': (\n                learner_dashboard_activities.to_dict())\n        })\n        self.render_json(self.values)\n\n\nclass LearnerDashboardFeedbackThreadHandler(base.BaseHandler):\n    \"\"\"Gets all the messages in a thread.\"\"\"\n\n    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON\n\n    @acl_decorators.can_access_learner_dashboard\n    def get(self, thread_id):\n        \"\"\"Handles GET requests.\"\"\"\n        messages = feedback_services.get_messages(thread_id)\n        author_ids = [m.author_id for m in messages]\n        authors_settings = user_services.get_users_settings(author_ids)\n\n        message_ids = [m.message_id for m in messages]\n        feedback_services.update_messages_read_by_the_user(\n            self.user_id, thread_id, message_ids)\n\n        message_summary_list = []\n        suggestion = suggestion_services.get_suggestion_by_id(thread_id)\n        suggestion_thread = feedback_services.get_thread(thread_id)\n\n        exploration_id = feedback_services.get_exp_id_from_thread_id(thread_id)\n        if suggestion:\n            exploration = exp_fetchers.get_exploration_by_id(exploration_id)\n            current_content_html = (\n                exploration.states[\n                    suggestion.change.state_name].content.html)\n            suggestion_summary = {\n                'suggestion_html': suggestion.change.new_value['html'],\n                'current_content_html': current_content_html,\n                'description': suggestion_thread.subject,\n                'author_username': authors_settings[0].username,\n                'author_picture_data_url': (\n                    authors_settings[0].profile_picture_data_url),\n                'created_on': utils.get_time_in_millisecs(\n                    messages[0].created_on)\n            }\n            message_summary_list.append(suggestion_summary)\n            messages.pop(0)\n            authors_settings.pop(0)\n\n        for m, author_settings in python_utils.ZIP(messages, authors_settings):\n\n            if author_settings is None:\n                author_username = None\n                author_picture_data_url = None\n            else:\n                author_username = author_settings.username\n                author_picture_data_url = (\n                    author_settings.profile_picture_data_url)\n\n            message_summary = {\n                'message_id': m.message_id,\n                'text': m.text,\n                'updated_status': m.updated_status,\n                'author_username': author_username,\n                'author_picture_data_url': author_picture_data_url,\n                'created_on': utils.get_time_in_millisecs(m.created_on)\n            }\n            message_summary_list.append(message_summary)\n\n        self.render_json({\n            'message_summary_list': message_summary_list\n        })\n", "path": "core/controllers/learner_dashboard.py"}]}