Repository: ucfopen/canvasapi (language: python)

Issue:
Course Analytics summary data returns a json, should return a PaginatedList
# Describe the bug

The methods to return analytics data only return a json value, but Canvas returns this as a paginated list for some of these methods.

# To Reproduce

Steps to reproduce the behavior:

Call methods like `course.get_course_level_student_summary_data()` on a course with many users. This method returns json so it's not possible to get the next set of data, only the first.

# Expected behavior

Expected that these methods return a PaginatedList so you can loop to the next record.

# Environment information

<!--(please complete the following information)-->

- Python version (`python --version`) 3.8.5
- CanvasAPI version (`pip show canvasapi`) 2.0.0

# Additional context

Review the [other analytics](https://canvas.instructure.com/doc/api/analytics.html) (and possibly other) methods like
```
get_course_level_participation_data
get_course_level_assignment_data
get_user_in_a_course_level_assignment_data
get_user_in_a_course_level_messaging_data
```

to see if these also need a Paginatedlist. 


Discussion / hints:

Fix patch:
diff --git a/AUTHORS.md b/AUTHORS.md
index 4b8eafad..eaf346e9 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -26,6 +26,7 @@ Patches and Suggestions
 - Bruce Spang [@brucespang](https://github.com/brucespang)
 - Catherine Abbruzzese [@cat0698](https://github.com/cat0698))
 - Cameron Cuff [@ctcuff](https://github.com/ctcuff)
+- Craig Thompson [@craigdsthompson](https://github.com/craigdsthompson)
 - Dalton Durst [@UniversalSuperBox](https://github.com/UniversalSuperBox)
 - Damian Sweeney [@damianfs](https://github.com/damianfs)
 - Daniel Brinkman [@DanBrink91](https://github.com/DanBrink91)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2fca249b..3798213e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@
 ### Breaking Changes
 
 - Update `QuizSubmission.get_submission_events` to return a `PaginatedList`. (Thanks, [@stevenbell](https://github.com/stevenbell))
+- Update `Course.get_course_level_student_summary_data` to returns a `PaginatedList` of `CourseStudentSummary` items instead of a dictionary. (Thanks, [@craigdsthompson](https://github.com/craigdsthompson))
 - Remove unnecessary `id` parameter from `delete`, `reorder_question_group`, and `update` methods in `QuizGroup` class. (Thanks, [@kailukaitisBrendan](https://github.com/kailukaitisBrendan))
 
 ## [2.2.0] - 2021-03-25
diff --git a/canvasapi/course.py b/canvasapi/course.py
index 11842f9e..26d71b51 100644
--- a/canvasapi/course.py
+++ b/canvasapi/course.py
@@ -1052,17 +1052,19 @@ def get_course_level_student_summary_data(self, **kwargs):
         :calls: `GET /api/v1/courses/:course_id/analytics/student_summaries \
         <https://canvas.instructure.com/doc/api/analytics.html#method.analytics_api.course_student_summaries>`_
 
-        :rtype: dict
+        :rtype: :class:`canvasapi.paginated_list.PaginatedList` of
+            :class:`canvasapi.course.CourseStudentSummary`
         """
 
-        response = self._requester.request(
+        return PaginatedList(
+            CourseStudentSummary,
+            self._requester,
             "GET",
             "courses/{}/analytics/student_summaries".format(self.id),
+            {"course_id": self.id},
             _kwargs=combine_kwargs(**kwargs),
         )
 
-        return response.json()
-
     def get_custom_columns(self, **kwargs):
         """
         List of all the custom gradebook columns for a course.
@@ -2687,6 +2689,11 @@ def remove(self, **kwargs):
         return CourseNickname(self._requester, response.json())
 
 
+class CourseStudentSummary(CanvasObject):
+    def __str__(self):
+        return "Course Student Summary {}".format(self.id)
+
+
 class LatePolicy(CanvasObject):
     def __str__(self):
         return "Late Policy {}".format(self.id)
diff --git a/docs/course-ref.rst b/docs/course-ref.rst
index 3d43f672..02e77cf3 100644
--- a/docs/course-ref.rst
+++ b/docs/course-ref.rst
@@ -12,6 +12,13 @@ CourseNickname
 .. autoclass:: canvasapi.course.CourseNickname
     :members:
 
+====================
+CourseStudentSummary
+====================
+
+.. autoclass:: canvasapi.course.CourseStudentSummary
+    :members:
+
 ==========
 LatePolicy
 ==========


Test patch:
diff --git a/tests/test_course.py b/tests/test_course.py
index 915d8f5a..284e24e9 100644
--- a/tests/test_course.py
+++ b/tests/test_course.py
@@ -1027,8 +1027,7 @@ def test_get_course_level_student_summary_data(self, m):
         register_uris({"course": ["get_course_level_student_summary_data"]}, m)
 
         response = self.course.get_course_level_student_summary_data()
-
-        self.assertIsInstance(response, list)
+        self.assertIsInstance(response, PaginatedList)
 
     # get_user_in_a_course_level_participation_data()
     def test_get_user_in_a_course_level_participation_data(self, m):
@@ -1813,6 +1812,27 @@ def test_remove(self, m):
         self.assertTrue(hasattr(deleted_nick, "nickname"))
 
 
+@requests_mock.Mocker()
+class TestCourseStudentSummary(unittest.TestCase):
+    def setUp(self):
+        self.canvas = Canvas(settings.BASE_URL, settings.API_KEY)
+
+        with requests_mock.Mocker() as m:
+            register_uris(
+                {"course": ["get_by_id", "get_course_level_student_summary_data"]}, m
+            )
+
+            self.course = self.canvas.get_course(1)
+            self.course_student_summary = (
+                self.course.get_course_level_student_summary_data()[0]
+            )
+
+    # __str__()
+    def test__str__(self, m):
+        string = str(self.course_student_summary)
+        self.assertIsInstance(string, str)
+
+
 @requests_mock.Mocker()
 class TestLatePolicy(unittest.TestCase):
     def setUp(self):