Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow enrollment api admin to see all enrollments #1714

Conversation

0x29a
Copy link
Contributor

@0x29a 0x29a commented Feb 5, 2023

Description

edx-enterprise provides the API to query enrollments to courses at the URL: <lms_base_url>/enterprise/api/v1/enterprise-course-enrollment/. However, the information returned by the API is extremely limited. For a non-staff user it returns only the enrollment data of self, and for a staff user, it returns all the enrollments with very limited data.

Example response with 1 enrollment:

{
    "next": null,
    "previous": null,
    "count": 1,
    "num_pages": 1,
    "current_page": 1,
    "start": 0,
    "results": [
        {
            "enterprise_customer_user": 3,
            "course_id": "course-v1:ENT+ENT101+2023"
        }
    ]
}

This is limiting for 2 reasons:

  1. marking an enterprise user a general staff user would allow them to access not just their catalog data, but also other system wide APIs
  2. the data returns contains just the user's numerical ID and the course_id, this is insufficient for any useful purposes. At the bare minimum it should return the email id of the user for mapping in the enterprise systems.

This PR updates the enterprise course enrollment endpoint to use rules.py, enterprise.can_enroll_learners in particular. Now, only staff or users with enrollment_api_admin role can see all enterprise course enrollments under their enterprise.

Also, now the endpoint returns more fields similarly to the learner-summary API. Example:

{
    "next": null,
    "previous": null,
    "count": 3,
    "num_pages": 1,
    "current_page": 1,
    "start": 0,
    "results": [
        {
            "enterprise_customer_user": 12,
            "course_id": "course-v1:ABC+ABC+ABC",
            "enrollment_date": "2023-02-04T18:44:54.412228Z",
            "enrollment_track": "audit",
            "user_email": "audit@example.com",
            "course_start": "2019-01-01T00:00:00Z",
            "course_end": "2024-06-14T00:00:00Z"
        },
        {
            "enterprise_customer_user": 14,
            "course_id": "course-v1:ABC+ABC+ABC",
            "enrollment_date": "2023-02-05T16:01:28.044111Z",
            "enrollment_track": "audit",
            "user_email": "xyz@example.com",
            "course_start": "2019-01-01T00:00:00Z",
            "course_end": "2024-06-14T00:00:00Z"
        },
        {
            "enterprise_customer_user": 11,
            "course_id": "course-v1:ABC+ABC+ABC",
            "enrollment_date": "2023-02-04T17:52:40.554691Z",
            "enrollment_track": "audit",
            "user_email": "abc@example.com",
            "course_start": "2019-01-01T00:00:00Z",
            "course_end": "2024-06-14T00:00:00Z"
        }
    ]
}

Testing instructions

Before testing, you'll need to install edx-enterprise to your devstack:

  1. cd $DEVSTACK_ROOT/src
  2. git clone -b 0x29a/enterprise-course-enrollments-api-improvements git@github.com:open-craft/edx-enterprise.git
  3. cd ../devstack
  4. make lms-shell
  5. cd /edx/src/edx-enterprise/
  6. pip uninstall edx-enterprise
  7. pip install -e .

Then, you'll need to create:

  • One course.
  • Assuming you already have an admin user, two additional users. Let's say abc@example.com and xyz@example.com.
  • One enterprise customer: <LMS>/admin/enterprise/enterprisecustomer/

Then, you have to link both users to the course here: <LMS>/admin/enterprise/enterprisecustomer/<ENTERPRISE_CUSTOMER_UUID>/manage_learners.

Also, one of the users has to have enrollment_api_admin role assigned. You can do this here: <LMS>/admin/enterprise/enterprisefeatureuserroleassignment/.

Now, test that:

  1. A regular user sees only his enrollment here: <LMS>/enterprise/api/v1/enterprise-course-enrollment/.
  2. An enterprise API admin sees all enrollments here: <LMS>/enterprise/api/v1/enterprise-course-enrollment/.

Checklists

Merge checklist:

  • Any new requirements are in the right place (do not manually modify the requirements/*.txt files)
    • base.in if needed in production but edx-platform doesn't install it
    • test-master.in if edx-platform pins it, with a matching version
    • make upgrade && make requirements have been run to regenerate requirements
  • make static has been run to update webpack bundling if any static content was updated
  • ./manage.py makemigrations has been run
    • Checkout the Database Migration Confluence page for helpful tips on creating migrations.
    • Note: This must be run if you modified any models.
      • It may or may not make a migration depending on exactly what you modified, but it should still be run.
    • This should be run from either a venv with all the lms/edx-enterprise requirements installed or if you checked out edx-enterprise into the src directory used by lms, you can run this command through an lms shell.
      • It would be ./manage.py lms makemigrations in the shell.
  • Version bumped
  • Changelog record added
  • Translations updated (see docs/internationalization.rst but also this isn't blocking for merge atm)

Post merge:

  • Tag pushed and a new version released
    • Note: Assets will be added automatically. You just need to provide a tag (should match your version number) and title and description.
  • After versioned build finishes in GitHub Actions, verify version has been pushed to PyPI
    • Each step in the release build has a condition flag that checks if the rest of the steps are done and if so will deploy to PyPi.
      (so basically once your build finishes, after maybe a minute you should see the new version in PyPi automatically (on refresh))
  • PR created in edx-platform to upgrade dependencies (including edx-enterprise)
    • This must be done after the version is visible in PyPi as make upgrade in edx-platform will look for the latest version in PyPi.
    • Note: the edx-enterprise constraint in edx-platform must also be bumped to the latest version in PyPi.

Private-ref

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Feb 5, 2023
@openedx-webhooks
Copy link

openedx-webhooks commented Feb 5, 2023

Thanks for the pull request, @0x29a! Please note that it may take us up to several weeks or months to complete a review and merge your PR.

Feel free to add as much of the following information to the ticket as you can:

  • supporting documentation
  • Open edX discussion forum threads
  • timeline information ("this must be merged by XX date", and why that is)
  • partner information ("this is a course on edx.org")
  • any other information that can help Product understand the context for the PR

All technical communication about the code itself will be done via the GitHub pull request interface. As a reminder, our process documentation is here.

Please let us know once your PR is ready for our review and all tests are green.

@0x29a 0x29a force-pushed the 0x29a/enterprise-course-enrollments-api-improvements branch 5 times, most recently from 585e511 to fcc3e08 Compare February 7, 2023 17:36
@0x29a 0x29a changed the title [WIP] feat: allow enrollment api admin to see all enrollments feat: allow enrollment api admin to see all enrollments Feb 7, 2023
Copy link
Contributor

@Cup0fCoffee Cup0fCoffee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested the changes manually yet, because I'm struggling with setting up the devstack for the enterprise-catalog to work. The code looks generally good, I just have some suggestions in regards to the new test case.

Comment on lines 85 to 148
@ddt.ddt
@mark.django_db
class TestEnterpriseCourseEnrollmentFilterBackend(APITest):
"""
Test suite for the ``EnterpriseCourseEnrollmentFilterBackend`` filter.
"""

def setUp(self):
super().setUp()
self.other_user= factories.UserFactory()
self.enterprise_customer = factories.EnterpriseCustomerFactory(uuid=FAKE_UUIDS[0])

self.enterprise_customer_user_1 = factories.EnterpriseCustomerUserFactory(
enterprise_customer=self.enterprise_customer,
user_id=self.user.id
)
self.enterprise_customer_user_2 = factories.EnterpriseCustomerUserFactory(
enterprise_customer=self.enterprise_customer,
user_id=self.other_user.id
)
self.course_enrollment_1 = factories.EnterpriseCourseEnrollmentFactory(
enterprise_customer_user=self.enterprise_customer_user_1
)
self.course_enrollment_2 = factories.EnterpriseCourseEnrollmentFactory(
enterprise_customer_user=self.enterprise_customer_user_2
)

self.url = settings.TEST_SERVER + reverse('enterprise-course-enrollment-list')

@ddt.data(
("regular", 1),
("staff", 2),
("enrollment_api_admin", 2),
)
@ddt.unpack
def test_filter_for_list(self, user_role, expected_course_enrollment_count):
"""
Filter objects based off whether the user is a staff, enterprise enrollment api admin, or neither.
"""
if user_role == "staff":
self.user.is_staff = True
self.user.save()
elif user_role == "enrollment_api_admin":
factories.EnterpriseFeatureUserRoleAssignmentFactory(
user=self.user,
role=EnterpriseFeatureRole.objects.get(name=ENTERPRISE_ENROLLMENT_API_ADMIN_ROLE)
)

response = self.client.get(self.url)
assert response.status_code == status.HTTP_200_OK
data = self.load_json(response.content)
assert len(data['results']) == expected_course_enrollment_count
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0x29a, imho, this test should be setup in such a way, that staff user gets more results than enrollment_api_admin, so that it's obvious that enrollment_api_admin can only get a subset of staff's results, since staff can get all enrollments, and enrollment_api_admin can only get the enrollments of the EnterpriseCustomer he is linked too. The way the test setup right now, if we were to modify the filter to return the same result for staff and for enrollment_api_admin, the test would still pass, when it should fail.

Also, since relationship between EnterpriseCustomerUser and User is many-to-one, it might be worth adding tests related to the behavior. I.e. having two enterprise accounts, enroll users under different enterprise customers, linking an admin to both EnterpriseCustomers and then checking that he filter returns results both customers (assuming this is the intended behavior).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved by 3cf711f

Comment on lines 124 to 131
if user_role == "staff":
self.user.is_staff = True
self.user.save()
elif user_role == "enrollment_api_admin":
factories.EnterpriseFeatureUserRoleAssignmentFactory(
user=self.user,
role=EnterpriseFeatureRole.objects.get(name=ENTERPRISE_ENROLLMENT_API_ADMIN_ROLE)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the setup could be extracted from the test, e.g. into a decorator or a function. It would simplify the test it itself, but perhaps it would over-complicate the code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved by 433e06b

@mphilbrick211 mphilbrick211 added the needs test run Author's first PR to this repository, awaiting test authorization from Axim label Feb 8, 2023
@Cup0fCoffee
Copy link
Contributor

👍 Other than some changes necessary for the new test case, everything else is fine.

  • I tested this: I've followed the testing instructions on the local devstack, and verified that the expected result occurs.
  • I read through the code
  • [N/A] I checked for accessibility issues
  • Includes documentation
  • [N/A] I made sure any change in configuration variables is reflected in the corresponding client's configuration-secure repository.

@e0d
Copy link

e0d commented Feb 14, 2023

@mphilbrick211 tests running.

@Cup0fCoffee Cup0fCoffee force-pushed the 0x29a/enterprise-course-enrollments-api-improvements branch from f28747d to 433e06b Compare February 15, 2023 15:40
@e0d
Copy link

e0d commented Feb 16, 2023

@0x29a looks like there are some quality and linting issues.

@e0d e0d removed the needs test run Author's first PR to this repository, awaiting test authorization from Axim label Feb 16, 2023
@Cup0fCoffee Cup0fCoffee force-pushed the 0x29a/enterprise-course-enrollments-api-improvements branch from 433e06b to 2bbff99 Compare February 17, 2023 16:50
@Cup0fCoffee
Copy link
Contributor

@e0d My apologies, the linting issues were introduced by my commits which I added with @0x29a's permission. I've fixed the linting issues and commit message formatting mistakes.

@e0d
Copy link

e0d commented Feb 17, 2023

@Cup0fCoffee no problem. Ran again and there's a coverage difference: https://github.com/openedx/edx-enterprise/pull/1714/checks?check_run_id=11423944585

@Cup0fCoffee
Copy link
Contributor

Cup0fCoffee commented Feb 17, 2023

Ran again and there's a coverage difference: https://github.com/openedx/edx-enterprise/pull/1714/checks?check_run_id=11423944585

@e0d The file that is mentioned was not modified in this PR, and no tests were removed.

I think the coverage diff is coming from this commit (different PR, which already has been merged). I've rebased onto the most recent version of the master branch some time ago to resolve merge conflicts, and the code coverage check is still using the old common commit to check the coverage, so it's flagging this PR as if it introduced the coverage diff.

@Cup0fCoffee Cup0fCoffee force-pushed the 0x29a/enterprise-course-enrollments-api-improvements branch from 2bbff99 to aa912ed Compare March 6, 2023 22:09
@Cup0fCoffee
Copy link
Contributor

@e0d Kind reminder ^

tecoholic added a commit to open-craft/edx-enterprise that referenced this pull request Mar 24, 2023
This enables the enterprise admin to see all the enrollments
of the enterprise and adds the following extra fields to the response:
* enrollment_date
* enrollment_track
* user_email
* course_start
* course_end

Ref: openedx#1714

Co-authored-by: 0x29a <demid@opencraft.com>, Cup0fCoffee <maxim@opencraft.com>
@e0d
Copy link

e0d commented Apr 7, 2023

@Cup0fCoffee sorry this feel off the radar with conference planning. It looks like there are conflicts that need to be resolved. Once that's done, we can adjust the coverage requirements given your PR is not introducing the issue.

@e0d
Copy link

e0d commented Apr 7, 2023

FYI @feanil @felipemontoya input into future plans around a standard enrollment API.

@Cup0fCoffee Cup0fCoffee force-pushed the 0x29a/enterprise-course-enrollments-api-improvements branch 2 times, most recently from 3dce3c4 to fe92bec Compare April 9, 2023 15:07
@Cup0fCoffee
Copy link
Contributor

@e0d While resolving the conflicts I've noticed that our implementation breaks code/tests which were added in between when this PR was created and now. I need some time to adjust our implementation. I'll ping here again once that is done.

@Cup0fCoffee Cup0fCoffee force-pushed the 0x29a/enterprise-course-enrollments-api-improvements branch 2 times, most recently from 29bd233 to 91be371 Compare April 11, 2023 14:31
@mphilbrick211 mphilbrick211 added the waiting on author PR author needs to resolve review requests, answer questions, fix tests, etc. label Nov 9, 2023
@0x29a
Copy link
Contributor Author

0x29a commented Dec 11, 2023

Moving the conversation from #1714 (comment) here to improve readability of my response.

Hi @feanil and @ormsbee, I've done tests that you suggested and some others.

Let me re-state the problem a bit more clearly first:

We have this endpoint: http://localhost:18000/enterprise/api/v1/enterprise-course-enrollment/. It's returning EnterpriseCustomerEnrollment objects and uses this manager to enrich API response with some details, like course and user info. We use sub-queries for this and it's not very efficient for some edge cases. Depending on the user role, this endpoint may return:

  1. Only a user's own enrollments. It seems to be fast in this case:
    image
  2. All EnterpriseCustomerEnrollment objects for an enterprise where the user has enrollment_api_admin role:
    image
    Without taking into account the bug (see this line), looks like not that slow, given the number of enrollments?
    In [12]: CourseOverview.objects.count()
    Out[12]: 5018
    
    In [13]: EnterpriseCustomer.objects.count()
    Out[13]: 103
    
    In [14]: EnterpriseCustomerUser.objects.count()
    Out[14]: 2959
    
    In [15]: EnterpriseCourseEnrollment.objects.count()
    Out[15]: 52314
    It's also important to know that the query is paginated and maximum page_size is 100 (queries take roughly the same amount of time for both 10 and 100 page size).
  3. Finally, staff user gets all enrollments, and this is where the endpoint becomes slow:
    image
    Here we see a huge COUNT query that is generated by the DRF's paginator.

Here are all four queries:

COUNT for staff
  SELECT COUNT(*)
    FROM (
          SELECT (
                  SELECT U0.`mode`
                    FROM `student_courseenrollment` U0
                   WHERE (U0.`course_id` = `enterprise_enterprisecourseenrollment`.`course_id` AND U0.`user_id` = `enterprise_enterprisecustomeruser`.`user_id`)
                   ORDER BY U0.`user_id` ASC, U0.`course_id` ASC
                   LIMIT 1
                 ) AS `enrollment_track`,
                 (
                  SELECT U0.`created`
                    FROM `student_courseenrollment` U0
                   WHERE (U0.`course_id` = `enterprise_enterprisecourseenrollment`.`course_id` AND U0.`user_id` = `enterprise_enterprisecustomeruser`.`user_id`)
                   ORDER BY U0.`user_id` ASC, U0.`course_id` ASC
                   LIMIT 1
                 ) AS `enrollment_date`,
                 (
                  SELECT U0.`email`
                    FROM `auth_user` U0
                   WHERE U0.`id` = `enterprise_enterprisecustomeruser`.`user_id`
                   LIMIT 1
                 ) AS `user_email`,
                 (
                  SELECT U0.`start`
                    FROM `course_overviews_courseoverview` U0
                   WHERE U0.`id` = `enterprise_enterprisecourseenrollment`.`course_id`
                   LIMIT 1
                 ) AS `course_start`,
                 (
                  SELECT U0.`end`
                    FROM `course_overviews_courseoverview` U0
                   WHERE U0.`id` = `enterprise_enterprisecourseenrollment`.`course_id`
                   LIMIT 1
                 ) AS `course_end`
            FROM `enterprise_enterprisecourseenrollment`
           INNER JOIN `enterprise_enterprisecustomeruser`
              ON (`enterprise_enterprisecourseenrollment`.`enterprise_customer_user_id` = `enterprise_enterprisecustomeruser`.`id`)
           WHERE `enterprise_enterprisecustomeruser`.`linked`
         ) subquery
SELECT for staff
  SELECT `enterprise_enterprisecourseenrollment`.`id`,
         `enterprise_enterprisecourseenrollment`.`created`,
         `enterprise_enterprisecourseenrollment`.`modified`,
         `enterprise_enterprisecourseenrollment`.`enterprise_customer_user_id`,
         `enterprise_enterprisecourseenrollment`.`course_id`,
         `enterprise_enterprisecourseenrollment`.`saved_for_later`,
         `enterprise_enterprisecourseenrollment`.`source_id`,
         `enterprise_enterprisecourseenrollment`.`unenrolled`,
         `enterprise_enterprisecourseenrollment`.`unenrolled_at`,
         (
          SELECT U0.`mode`
            FROM `student_courseenrollment` U0
           WHERE (U0.`course_id` = `enterprise_enterprisecourseenrollment`.`course_id` AND U0.`user_id` = `enterprise_enterprisecustomeruser`.`user_id`)
           ORDER BY U0.`user_id` ASC, U0.`course_id` ASC
           LIMIT 1
         ) AS `enrollment_track`,
         (
          SELECT U0.`created`
            FROM `student_courseenrollment` U0
           WHERE (U0.`course_id` = `enterprise_enterprisecourseenrollment`.`course_id` AND U0.`user_id` = `enterprise_enterprisecustomeruser`.`user_id`)
           ORDER BY U0.`user_id` ASC, U0.`course_id` ASC
           LIMIT 1
         ) AS `enrollment_date`,
         (
          SELECT U0.`email`
            FROM `auth_user` U0
           WHERE U0.`id` = `enterprise_enterprisecustomeruser`.`user_id`
           LIMIT 1
         ) AS `user_email`,
         (
          SELECT U0.`start`
            FROM `course_overviews_courseoverview` U0
           WHERE U0.`id` = `enterprise_enterprisecourseenrollment`.`course_id`
           LIMIT 1
         ) AS `course_start`,
         (
          SELECT U0.`end`
            FROM `course_overviews_courseoverview` U0
           WHERE U0.`id` = `enterprise_enterprisecourseenrollment`.`course_id`
           LIMIT 1
         ) AS `course_end`,
         `enterprise_enterprisecustomeruser`.`id`,
         `enterprise_enterprisecustomeruser`.`created`,
         `enterprise_enterprisecustomeruser`.`modified`,
         `enterprise_enterprisecustomeruser`.`enterprise_customer_id`,
         `enterprise_enterprisecustomeruser`.`user_id`,
         `enterprise_enterprisecustomeruser`.`active`,
         `enterprise_enterprisecustomeruser`.`linked`,
         `enterprise_enterprisecustomeruser`.`is_relinkable`,
         `enterprise_enterprisecustomeruser`.`invite_key_id`,
         `enterprise_enterprisecustomeruser`.`should_inactivate_other_customers`
    FROM `enterprise_enterprisecourseenrollment`
   INNER JOIN `enterprise_enterprisecustomeruser`
      ON (`enterprise_enterprisecourseenrollment`.`enterprise_customer_user_id` = `enterprise_enterprisecustomeruser`.`id`)
   WHERE `enterprise_enterprisecustomeruser`.`linked`
   ORDER BY `enterprise_enterprisecourseenrollment`.`created` ASC
   LIMIT 100
COUNT for enrollment_api_admin (with WHERE clause)
  SELECT COUNT(*)
    FROM (
          SELECT (
                  SELECT U0.`mode`
                    FROM `student_courseenrollment` U0
                   WHERE (U0.`course_id` = `enterprise_enterprisecourseenrollment`.`course_id` AND U0.`user_id` = `enterprise_enterprisecustomeruser`.`user_id`)
                   ORDER BY U0.`user_id` ASC, U0.`course_id` ASC
                   LIMIT 1
                 ) AS `enrollment_track`,
                 (
                  SELECT U0.`created`
                    FROM `student_courseenrollment` U0
                   WHERE (U0.`course_id` = `enterprise_enterprisecourseenrollment`.`course_id` AND U0.`user_id` = `enterprise_enterprisecustomeruser`.`user_id`)
                   ORDER BY U0.`user_id` ASC, U0.`course_id` ASC
                   LIMIT 1
                 ) AS `enrollment_date`,
                 (
                  SELECT U0.`email`
                    FROM `auth_user` U0
                   WHERE U0.`id` = `enterprise_enterprisecustomeruser`.`user_id`
                   LIMIT 1
                 ) AS `user_email`,
                 (
                  SELECT U0.`start`
                    FROM `course_overviews_courseoverview` U0
                   WHERE U0.`id` = `enterprise_enterprisecourseenrollment`.`course_id`
                   LIMIT 1
                 ) AS `course_start`,
                 (
                  SELECT U0.`end`
                    FROM `course_overviews_courseoverview` U0
                   WHERE U0.`id` = `enterprise_enterprisecourseenrollment`.`course_id`
                   LIMIT 1
                 ) AS `course_end`
            FROM `enterprise_enterprisecourseenrollment`
           INNER JOIN `enterprise_enterprisecustomeruser`
              ON (`enterprise_enterprisecourseenrollment`.`enterprise_customer_user_id` = `enterprise_enterprisecustomeruser`.`id`)
           WHERE (`enterprise_enterprisecustomeruser`.`linked` AND `enterprise_enterprisecustomeruser`.`enterprise_customer_id` IN (SELECT U0.`uuid` FROM `enterprise_enterprisecustomer` U0 INNER JOIN `enterprise_enterprisecustomeruser` U1 ON (U0.`uuid` = U1.`enterprise_customer_id`) WHERE U1.`user_id` = 17))
         ) subquery
SELECT for enrollment_api_admin (with WHERE clause)
  SELECT `enterprise_enterprisecourseenrollment`.`id`,
         `enterprise_enterprisecourseenrollment`.`created`,
         `enterprise_enterprisecourseenrollment`.`modified`,
         `enterprise_enterprisecourseenrollment`.`enterprise_customer_user_id`,
         `enterprise_enterprisecourseenrollment`.`course_id`,
         `enterprise_enterprisecourseenrollment`.`saved_for_later`,
         `enterprise_enterprisecourseenrollment`.`source_id`,
         `enterprise_enterprisecourseenrollment`.`unenrolled`,
         `enterprise_enterprisecourseenrollment`.`unenrolled_at`,
         (
          SELECT U0.`mode`
            FROM `student_courseenrollment` U0
           WHERE (U0.`course_id` = `enterprise_enterprisecourseenrollment`.`course_id` AND U0.`user_id` = `enterprise_enterprisecustomeruser`.`user_id`)
           ORDER BY U0.`user_id` ASC, U0.`course_id` ASC
           LIMIT 1
         ) AS `enrollment_track`,
         (
          SELECT U0.`created`
            FROM `student_courseenrollment` U0
           WHERE (U0.`course_id` = `enterprise_enterprisecourseenrollment`.`course_id` AND U0.`user_id` = `enterprise_enterprisecustomeruser`.`user_id`)
           ORDER BY U0.`user_id` ASC, U0.`course_id` ASC
           LIMIT 1
         ) AS `enrollment_date`,
         (
          SELECT U0.`email`
            FROM `auth_user` U0
           WHERE U0.`id` = `enterprise_enterprisecustomeruser`.`user_id`
           LIMIT 1
         ) AS `user_email`,
         (
          SELECT U0.`start`
            FROM `course_overviews_courseoverview` U0
           WHERE U0.`id` = `enterprise_enterprisecourseenrollment`.`course_id`
           LIMIT 1
         ) AS `course_start`,
         (
          SELECT U0.`end`
            FROM `course_overviews_courseoverview` U0
           WHERE U0.`id` = `enterprise_enterprisecourseenrollment`.`course_id`
           LIMIT 1
         ) AS `course_end`,
         `enterprise_enterprisecustomeruser`.`id`,
         `enterprise_enterprisecustomeruser`.`created`,
         `enterprise_enterprisecustomeruser`.`modified`,
         `enterprise_enterprisecustomeruser`.`enterprise_customer_id`,
         `enterprise_enterprisecustomeruser`.`user_id`,
         `enterprise_enterprisecustomeruser`.`active`,
         `enterprise_enterprisecustomeruser`.`linked`,
         `enterprise_enterprisecustomeruser`.`is_relinkable`,
         `enterprise_enterprisecustomeruser`.`invite_key_id`,
         `enterprise_enterprisecustomeruser`.`should_inactivate_other_customers`
    FROM `enterprise_enterprisecourseenrollment`
   INNER JOIN `enterprise_enterprisecustomeruser`
      ON (`enterprise_enterprisecourseenrollment`.`enterprise_customer_user_id` = `enterprise_enterprisecustomeruser`.`id`)
   WHERE (`enterprise_enterprisecustomeruser`.`linked` AND `enterprise_enterprisecustomeruser`.`enterprise_customer_id` IN (SELECT U0.`uuid` FROM `enterprise_enterprisecustomer` U0 INNER JOIN `enterprise_enterprisecustomeruser` U1 ON (U0.`uuid` = U1.`enterprise_customer_id`) WHERE U1.`user_id` = 17))
   ORDER BY `enterprise_enterprisecourseenrollment`.`created` ASC
   LIMIT 100

Explains for each without any additional indexes (this time with diagrams):

COUNT for staff

image

{
"query_block": {
  "select_id": 1,
  "cost_info": {
    "query_cost": "5385.06"
  },
  "table": {
    "table_name": "subquery",
    "access_type": "ALL",
    "rows_examined_per_scan": 47845,
    "rows_produced_per_join": 47845,
    "filtered": "100.00",
    "cost_info": {
      "read_cost": "600.56",
      "eval_cost": "4784.50",
      "prefix_cost": "5385.06",
      "data_read_per_join": "50M"
    },
    "used_columns": [
      "enrollment_track",
      "enrollment_date",
      "user_email",
      "course_start",
      "course_end"
    ],
    "materialized_from_subquery": {
      "using_temporary_table": true,
      "dependent": false,
      "cacheable": true,
      "query_block": {
        "select_id": 2,
        "cost_info": {
          "query_cost": "6777.55"
        },
        "nested_loop": [
          {
            "table": {
              "table_name": "enterprise_enterprisecustomeruser",
              "access_type": "ALL",
              "possible_keys": [
                "PRIMARY"
              ],
              "rows_examined_per_scan": 2959,
              "rows_produced_per_join": 2663,
              "filtered": "90.00",
              "cost_info": {
                "read_cost": "34.09",
                "eval_cost": "266.31",
                "prefix_cost": "300.40",
                "data_read_per_join": "582K"
              },
              "used_columns": [
                "id",
                "user_id",
                "linked"
              ],
              "attached_condition": "(0 <> `edxapp`.`enterprise_enterprisecustomeruser`.`linked`)"
            }
          },
          {
            "table": {
              "table_name": "enterprise_enterprisecourseenrollment",
              "access_type": "ref",
              "possible_keys": [
                "enterprise_enterprisecou_enterprise_customer_user_71fe301a_uniq"
              ],
              "key": "enterprise_enterprisecou_enterprise_customer_user_71fe301a_uniq",
              "used_key_parts": [
                "enterprise_customer_user_id"
              ],
              "key_length": "4",
              "ref": [
                "edxapp.enterprise_enterprisecustomeruser.id"
              ],
              "rows_examined_per_scan": 17,
              "rows_produced_per_join": 47845,
              "filtered": "100.00",
              "using_index": true,
              "cost_info": {
                "read_cost": "1692.64",
                "eval_cost": "4784.51",
                "prefix_cost": "6777.55",
                "data_read_per_join": "36M"
              },
              "used_columns": [
                "course_id",
                "enterprise_customer_user_id"
              ]
            }
          }
        ],
        "select_list_subqueries": [
          {
            "dependent": true,
            "cacheable": false,
            "query_block": {
              "select_id": 7,
              "cost_info": {
                "query_cost": "0.35"
              },
              "table": {
                "table_name": "U0",
                "access_type": "eq_ref",
                "possible_keys": [
                  "PRIMARY"
                ],
                "key": "PRIMARY",
                "used_key_parts": [
                  "id"
                ],
                "key_length": "767",
                "ref": [
                  "edxapp.enterprise_enterprisecourseenrollment.course_id"
                ],
                "rows_examined_per_scan": 1,
                "rows_produced_per_join": 1,
                "filtered": "100.00",
                "cost_info": {
                  "read_cost": "0.25",
                  "eval_cost": "0.10",
                  "prefix_cost": "0.35",
                  "data_read_per_join": "3K"
                },
                "used_columns": [
                  "id",
                  "end"
                ]
              }
            }
          },
          {
            "dependent": true,
            "cacheable": false,
            "query_block": {
              "select_id": 6,
              "cost_info": {
                "query_cost": "0.35"
              },
              "table": {
                "table_name": "U0",
                "access_type": "eq_ref",
                "possible_keys": [
                  "PRIMARY"
                ],
                "key": "PRIMARY",
                "used_key_parts": [
                  "id"
                ],
                "key_length": "767",
                "ref": [
                  "edxapp.enterprise_enterprisecourseenrollment.course_id"
                ],
                "rows_examined_per_scan": 1,
                "rows_produced_per_join": 1,
                "filtered": "100.00",
                "cost_info": {
                  "read_cost": "0.25",
                  "eval_cost": "0.10",
                  "prefix_cost": "0.35",
                  "data_read_per_join": "3K"
                },
                "used_columns": [
                  "id",
                  "start"
                ]
              }
            }
          },
          {
            "dependent": true,
            "cacheable": false,
            "query_block": {
              "select_id": 5,
              "cost_info": {
                "query_cost": "0.35"
              },
              "table": {
                "table_name": "U0",
                "access_type": "eq_ref",
                "possible_keys": [
                  "PRIMARY"
                ],
                "key": "PRIMARY",
                "used_key_parts": [
                  "id"
                ],
                "key_length": "4",
                "ref": [
                  "edxapp.enterprise_enterprisecustomeruser.user_id"
                ],
                "rows_examined_per_scan": 1,
                "rows_produced_per_join": 1,
                "filtered": "100.00",
                "cost_info": {
                  "read_cost": "0.25",
                  "eval_cost": "0.10",
                  "prefix_cost": "0.35",
                  "data_read_per_join": "2K"
                },
                "used_columns": [
                  "id",
                  "email"
                ],
                "attached_condition": "(`edxapp`.`U0`.`id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)"
              }
            }
          },
          {
            "dependent": true,
            "cacheable": false,
            "query_block": {
              "select_id": 4,
              "cost_info": {
                "query_cost": "0.35"
              },
              "ordering_operation": {
                "using_filesort": false,
                "table": {
                  "table_name": "U0",
                  "access_type": "eq_ref",
                  "possible_keys": [
                    "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                    "student_courseenrollment_user_id_4263a8e2",
                    "student_cou_user_id_b19dcd_idx",
                    "student_courseenrollment_course_id_a6f93be8"
                  ],
                  "key": "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                  "used_key_parts": [
                    "user_id",
                    "course_id"
                  ],
                  "key_length": "771",
                  "ref": [
                    "edxapp.enterprise_enterprisecustomeruser.user_id",
                    "edxapp.enterprise_enterprisecourseenrollment.course_id"
                  ],
                  "rows_examined_per_scan": 1,
                  "rows_produced_per_join": 1,
                  "filtered": "100.00",
                  "index_condition": "(`edxapp`.`U0`.`user_id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)",
                  "cost_info": {
                    "read_cost": "0.25",
                    "eval_cost": "0.10",
                    "prefix_cost": "0.35",
                    "data_read_per_join": "1K"
                  },
                  "used_columns": [
                    "id",
                    "course_id",
                    "created",
                    "user_id"
                  ]
                }
              }
            }
          },
          {
            "dependent": true,
            "cacheable": false,
            "query_block": {
              "select_id": 3,
              "cost_info": {
                "query_cost": "0.35"
              },
              "ordering_operation": {
                "using_filesort": false,
                "table": {
                  "table_name": "U0",
                  "access_type": "eq_ref",
                  "possible_keys": [
                    "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                    "student_courseenrollment_user_id_4263a8e2",
                    "student_cou_user_id_b19dcd_idx",
                    "student_courseenrollment_course_id_a6f93be8"
                  ],
                  "key": "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                  "used_key_parts": [
                    "user_id",
                    "course_id"
                  ],
                  "key_length": "771",
                  "ref": [
                    "edxapp.enterprise_enterprisecustomeruser.user_id",
                    "edxapp.enterprise_enterprisecourseenrollment.course_id"
                  ],
                  "rows_examined_per_scan": 1,
                  "rows_produced_per_join": 1,
                  "filtered": "100.00",
                  "index_condition": "(`edxapp`.`U0`.`user_id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)",
                  "cost_info": {
                    "read_cost": "0.25",
                    "eval_cost": "0.10",
                    "prefix_cost": "0.35",
                    "data_read_per_join": "1K"
                  },
                  "used_columns": [
                    "id",
                    "course_id",
                    "mode",
                    "user_id"
                  ]
                }
              }
            }
          }
        ]
      }
    }
  }
}
}
SELECT for staff

image

{
"query_block": {
  "select_id": 1,
  "cost_info": {
    "query_cost": "23590.40"
  },
  "ordering_operation": {
    "using_filesort": true,
    "nested_loop": [
      {
        "table": {
          "table_name": "enterprise_enterprisecourseenrollment",
          "access_type": "ALL",
          "possible_keys": [
            "enterprise_enterprisecou_enterprise_customer_user_71fe301a_uniq"
          ],
          "rows_examined_per_scan": 52227,
          "rows_produced_per_join": 52227,
          "filtered": "100.00",
          "cost_info": {
            "read_cost": "88.25",
            "eval_cost": "5222.70",
            "prefix_cost": "5310.95",
            "data_read_per_join": "40M"
          },
          "used_columns": [
            "id",
            "created",
            "modified",
            "course_id",
            "enterprise_customer_user_id",
            "source_id",
            "saved_for_later",
            "unenrolled",
            "unenrolled_at"
          ]
        }
      },
      {
        "table": {
          "table_name": "enterprise_enterprisecustomeruser",
          "access_type": "eq_ref",
          "possible_keys": [
            "PRIMARY"
          ],
          "key": "PRIMARY",
          "used_key_parts": [
            "id"
          ],
          "key_length": "4",
          "ref": [
            "edxapp.enterprise_enterprisecourseenrollment.enterprise_customer_user_id"
          ],
          "rows_examined_per_scan": 1,
          "rows_produced_per_join": 47004,
          "filtered": "90.00",
          "cost_info": {
            "read_cost": "13056.75",
            "eval_cost": "4700.43",
            "prefix_cost": "23590.40",
            "data_read_per_join": "10M"
          },
          "used_columns": [
            "id",
            "created",
            "modified",
            "user_id",
            "active",
            "linked",
            "enterprise_customer_id",
            "invite_key_id",
            "should_inactivate_other_customers",
            "is_relinkable"
          ],
          "attached_condition": "(0 <> `edxapp`.`enterprise_enterprisecustomeruser`.`linked`)"
        }
      }
    ],
    "select_list_subqueries": [
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 6,
          "cost_info": {
            "query_cost": "0.35"
          },
          "table": {
            "table_name": "U0",
            "access_type": "eq_ref",
            "possible_keys": [
              "PRIMARY"
            ],
            "key": "PRIMARY",
            "used_key_parts": [
              "id"
            ],
            "key_length": "767",
            "ref": [
              "edxapp.enterprise_enterprisecourseenrollment.course_id"
            ],
            "rows_examined_per_scan": 1,
            "rows_produced_per_join": 1,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "0.25",
              "eval_cost": "0.10",
              "prefix_cost": "0.35",
              "data_read_per_join": "3K"
            },
            "used_columns": [
              "id",
              "end"
            ]
          }
        }
      },
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 5,
          "cost_info": {
            "query_cost": "0.35"
          },
          "table": {
            "table_name": "U0",
            "access_type": "eq_ref",
            "possible_keys": [
              "PRIMARY"
            ],
            "key": "PRIMARY",
            "used_key_parts": [
              "id"
            ],
            "key_length": "767",
            "ref": [
              "edxapp.enterprise_enterprisecourseenrollment.course_id"
            ],
            "rows_examined_per_scan": 1,
            "rows_produced_per_join": 1,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "0.25",
              "eval_cost": "0.10",
              "prefix_cost": "0.35",
              "data_read_per_join": "3K"
            },
            "used_columns": [
              "id",
              "start"
            ]
          }
        }
      },
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 4,
          "cost_info": {
            "query_cost": "0.35"
          },
          "table": {
            "table_name": "U0",
            "access_type": "eq_ref",
            "possible_keys": [
              "PRIMARY"
            ],
            "key": "PRIMARY",
            "used_key_parts": [
              "id"
            ],
            "key_length": "4",
            "ref": [
              "edxapp.enterprise_enterprisecustomeruser.user_id"
            ],
            "rows_examined_per_scan": 1,
            "rows_produced_per_join": 1,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "0.25",
              "eval_cost": "0.10",
              "prefix_cost": "0.35",
              "data_read_per_join": "2K"
            },
            "used_columns": [
              "id",
              "email"
            ],
            "attached_condition": "(`edxapp`.`U0`.`id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)"
          }
        }
      },
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 3,
          "cost_info": {
            "query_cost": "0.35"
          },
          "ordering_operation": {
            "using_filesort": false,
            "table": {
              "table_name": "U0",
              "access_type": "eq_ref",
              "possible_keys": [
                "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                "student_courseenrollment_user_id_4263a8e2",
                "student_cou_user_id_b19dcd_idx",
                "student_courseenrollment_course_id_a6f93be8"
              ],
              "key": "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
              "used_key_parts": [
                "user_id",
                "course_id"
              ],
              "key_length": "771",
              "ref": [
                "edxapp.enterprise_enterprisecustomeruser.user_id",
                "edxapp.enterprise_enterprisecourseenrollment.course_id"
              ],
              "rows_examined_per_scan": 1,
              "rows_produced_per_join": 1,
              "filtered": "100.00",
              "index_condition": "(`edxapp`.`U0`.`user_id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)",
              "cost_info": {
                "read_cost": "0.25",
                "eval_cost": "0.10",
                "prefix_cost": "0.35",
                "data_read_per_join": "1K"
              },
              "used_columns": [
                "id",
                "course_id",
                "created",
                "user_id"
              ]
            }
          }
        }
      },
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 2,
          "cost_info": {
            "query_cost": "0.35"
          },
          "ordering_operation": {
            "using_filesort": false,
            "table": {
              "table_name": "U0",
              "access_type": "eq_ref",
              "possible_keys": [
                "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                "student_courseenrollment_user_id_4263a8e2",
                "student_cou_user_id_b19dcd_idx",
                "student_courseenrollment_course_id_a6f93be8"
              ],
              "key": "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
              "used_key_parts": [
                "user_id",
                "course_id"
              ],
              "key_length": "771",
              "ref": [
                "edxapp.enterprise_enterprisecustomeruser.user_id",
                "edxapp.enterprise_enterprisecourseenrollment.course_id"
              ],
              "rows_examined_per_scan": 1,
              "rows_produced_per_join": 1,
              "filtered": "100.00",
              "index_condition": "(`edxapp`.`U0`.`user_id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)",
              "cost_info": {
                "read_cost": "0.25",
                "eval_cost": "0.10",
                "prefix_cost": "0.35",
                "data_read_per_join": "1K"
              },
              "used_columns": [
                "id",
                "course_id",
                "mode",
                "user_id"
              ]
            }
          }
        }
      }
    ]
  }
}
}
COUNT for enrollment_api_admin

image

{
"query_block": {
  "select_id": 1,
  "cost_info": {
    "query_cost": "108.02"
  },
  "table": {
    "table_name": "subquery",
    "access_type": "ALL",
    "rows_examined_per_scan": 938,
    "rows_produced_per_join": 938,
    "filtered": "100.00",
    "cost_info": {
      "read_cost": "14.22",
      "eval_cost": "93.80",
      "prefix_cost": "108.03",
      "data_read_per_join": "1003K"
    },
    "used_columns": [
      "enrollment_track",
      "enrollment_date",
      "user_email",
      "course_start",
      "course_end"
    ],
    "materialized_from_subquery": {
      "using_temporary_table": true,
      "dependent": false,
      "cacheable": true,
      "query_block": {
        "select_id": 2,
        "cost_info": {
          "query_cost": "148.71"
        },
        "nested_loop": [
          {
            "table": {
              "table_name": "U1",
              "access_type": "ref",
              "possible_keys": [
                "enterprise_enterprisecus_enterprise_customer_id_u_ffddc29f_uniq",
                "enterprise_enterprisecustomeruser_user_id_aa8d772f"
              ],
              "key": "enterprise_enterprisecustomeruser_user_id_aa8d772f",
              "used_key_parts": [
                "user_id"
              ],
              "key_length": "4",
              "ref": [
                "const"
              ],
              "rows_examined_per_scan": 2,
              "rows_produced_per_join": 2,
              "filtered": "100.00",
              "cost_info": {
                "read_cost": "0.50",
                "eval_cost": "0.20",
                "prefix_cost": "0.70",
                "data_read_per_join": "448"
              },
              "used_columns": [
                "user_id",
                "enterprise_customer_id"
              ]
            }
          },
          {
            "table": {
              "table_name": "U0",
              "access_type": "eq_ref",
              "possible_keys": [
                "PRIMARY"
              ],
              "key": "PRIMARY",
              "used_key_parts": [
                "uuid"
              ],
              "key_length": "96",
              "ref": [
                "edxapp.U1.enterprise_customer_id"
              ],
              "rows_examined_per_scan": 1,
              "rows_produced_per_join": 2,
              "filtered": "100.00",
              "using_index": true,
              "cost_info": {
                "read_cost": "0.50",
                "eval_cost": "0.20",
                "prefix_cost": "1.40",
                "data_read_per_join": "7K"
              },
              "used_columns": [
                "uuid"
              ]
            }
          },
          {
            "table": {
              "table_name": "enterprise_enterprisecustomeruser",
              "access_type": "ref",
              "possible_keys": [
                "PRIMARY",
                "enterprise_enterprisecus_enterprise_customer_id_u_ffddc29f_uniq"
              ],
              "key": "enterprise_enterprisecus_enterprise_customer_id_u_ffddc29f_uniq",
              "used_key_parts": [
                "enterprise_customer_id"
              ],
              "key_length": "96",
              "ref": [
                "edxapp.U1.enterprise_customer_id"
              ],
              "rows_examined_per_scan": 29,
              "rows_produced_per_join": 52,
              "filtered": "90.00",
              "cost_info": {
                "read_cost": "14.50",
                "eval_cost": "5.22",
                "prefix_cost": "21.71",
                "data_read_per_join": "11K"
              },
              "used_columns": [
                "id",
                "user_id",
                "linked",
                "enterprise_customer_id"
              ],
              "attached_condition": "(0 <> `edxapp`.`enterprise_enterprisecustomeruser`.`linked`)"
            }
          },
          {
            "table": {
              "table_name": "enterprise_enterprisecourseenrollment",
              "access_type": "ref",
              "possible_keys": [
                "enterprise_enterprisecou_enterprise_customer_user_71fe301a_uniq"
              ],
              "key": "enterprise_enterprisecou_enterprise_customer_user_71fe301a_uniq",
              "used_key_parts": [
                "enterprise_customer_user_id"
              ],
              "key_length": "4",
              "ref": [
                "edxapp.enterprise_enterprisecustomeruser.id"
              ],
              "rows_examined_per_scan": 17,
              "rows_produced_per_join": 938,
              "filtered": "100.00",
              "using_index": true,
              "cost_info": {
                "read_cost": "33.19",
                "eval_cost": "93.81",
                "prefix_cost": "148.71",
                "data_read_per_join": "740K"
              },
              "used_columns": [
                "course_id",
                "enterprise_customer_user_id"
              ]
            }
          }
        ],
        "select_list_subqueries": [
          {
            "dependent": true,
            "cacheable": false,
            "query_block": {
              "select_id": 7,
              "cost_info": {
                "query_cost": "0.35"
              },
              "table": {
                "table_name": "U0",
                "access_type": "eq_ref",
                "possible_keys": [
                  "PRIMARY"
                ],
                "key": "PRIMARY",
                "used_key_parts": [
                  "id"
                ],
                "key_length": "767",
                "ref": [
                  "edxapp.enterprise_enterprisecourseenrollment.course_id"
                ],
                "rows_examined_per_scan": 1,
                "rows_produced_per_join": 1,
                "filtered": "100.00",
                "cost_info": {
                  "read_cost": "0.25",
                  "eval_cost": "0.10",
                  "prefix_cost": "0.35",
                  "data_read_per_join": "3K"
                },
                "used_columns": [
                  "id",
                  "end"
                ]
              }
            }
          },
          {
            "dependent": true,
            "cacheable": false,
            "query_block": {
              "select_id": 6,
              "cost_info": {
                "query_cost": "0.35"
              },
              "table": {
                "table_name": "U0",
                "access_type": "eq_ref",
                "possible_keys": [
                  "PRIMARY"
                ],
                "key": "PRIMARY",
                "used_key_parts": [
                  "id"
                ],
                "key_length": "767",
                "ref": [
                  "edxapp.enterprise_enterprisecourseenrollment.course_id"
                ],
                "rows_examined_per_scan": 1,
                "rows_produced_per_join": 1,
                "filtered": "100.00",
                "cost_info": {
                  "read_cost": "0.25",
                  "eval_cost": "0.10",
                  "prefix_cost": "0.35",
                  "data_read_per_join": "3K"
                },
                "used_columns": [
                  "id",
                  "start"
                ]
              }
            }
          },
          {
            "dependent": true,
            "cacheable": false,
            "query_block": {
              "select_id": 5,
              "cost_info": {
                "query_cost": "0.35"
              },
              "table": {
                "table_name": "U0",
                "access_type": "eq_ref",
                "possible_keys": [
                  "PRIMARY"
                ],
                "key": "PRIMARY",
                "used_key_parts": [
                  "id"
                ],
                "key_length": "4",
                "ref": [
                  "edxapp.enterprise_enterprisecustomeruser.user_id"
                ],
                "rows_examined_per_scan": 1,
                "rows_produced_per_join": 1,
                "filtered": "100.00",
                "cost_info": {
                  "read_cost": "0.25",
                  "eval_cost": "0.10",
                  "prefix_cost": "0.35",
                  "data_read_per_join": "2K"
                },
                "used_columns": [
                  "id",
                  "email"
                ],
                "attached_condition": "(`edxapp`.`U0`.`id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)"
              }
            }
          },
          {
            "dependent": true,
            "cacheable": false,
            "query_block": {
              "select_id": 4,
              "cost_info": {
                "query_cost": "0.35"
              },
              "ordering_operation": {
                "using_filesort": false,
                "table": {
                  "table_name": "U0",
                  "access_type": "eq_ref",
                  "possible_keys": [
                    "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                    "student_courseenrollment_user_id_4263a8e2",
                    "student_cou_user_id_b19dcd_idx",
                    "student_courseenrollment_course_id_a6f93be8"
                  ],
                  "key": "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                  "used_key_parts": [
                    "user_id",
                    "course_id"
                  ],
                  "key_length": "771",
                  "ref": [
                    "edxapp.enterprise_enterprisecustomeruser.user_id",
                    "edxapp.enterprise_enterprisecourseenrollment.course_id"
                  ],
                  "rows_examined_per_scan": 1,
                  "rows_produced_per_join": 1,
                  "filtered": "100.00",
                  "index_condition": "(`edxapp`.`U0`.`user_id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)",
                  "cost_info": {
                    "read_cost": "0.25",
                    "eval_cost": "0.10",
                    "prefix_cost": "0.35",
                    "data_read_per_join": "1K"
                  },
                  "used_columns": [
                    "id",
                    "course_id",
                    "created",
                    "user_id"
                  ]
                }
              }
            }
          },
          {
            "dependent": true,
            "cacheable": false,
            "query_block": {
              "select_id": 3,
              "cost_info": {
                "query_cost": "0.35"
              },
              "ordering_operation": {
                "using_filesort": false,
                "table": {
                  "table_name": "U0",
                  "access_type": "eq_ref",
                  "possible_keys": [
                    "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                    "student_courseenrollment_user_id_4263a8e2",
                    "student_cou_user_id_b19dcd_idx",
                    "student_courseenrollment_course_id_a6f93be8"
                  ],
                  "key": "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                  "used_key_parts": [
                    "user_id",
                    "course_id"
                  ],
                  "key_length": "771",
                  "ref": [
                    "edxapp.enterprise_enterprisecustomeruser.user_id",
                    "edxapp.enterprise_enterprisecourseenrollment.course_id"
                  ],
                  "rows_examined_per_scan": 1,
                  "rows_produced_per_join": 1,
                  "filtered": "100.00",
                  "index_condition": "(`edxapp`.`U0`.`user_id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)",
                  "cost_info": {
                    "read_cost": "0.25",
                    "eval_cost": "0.10",
                    "prefix_cost": "0.35",
                    "data_read_per_join": "1K"
                  },
                  "used_columns": [
                    "id",
                    "course_id",
                    "mode",
                    "user_id"
                  ]
                }
              }
            }
          }
        ]
      }
    }
  }
}
}
SELECT for enrollment_api_admin

image

{
"query_block": {
  "select_id": 1,
  "cost_info": {
    "query_cost": "1288.19"
  },
  "ordering_operation": {
    "using_temporary_table": true,
    "using_filesort": true,
    "cost_info": {
      "sort_cost": "938.14"
    },
    "nested_loop": [
      {
        "table": {
          "table_name": "U1",
          "access_type": "ref",
          "possible_keys": [
            "enterprise_enterprisecus_enterprise_customer_id_u_ffddc29f_uniq",
            "enterprise_enterprisecustomeruser_user_id_aa8d772f"
          ],
          "key": "enterprise_enterprisecustomeruser_user_id_aa8d772f",
          "used_key_parts": [
            "user_id"
          ],
          "key_length": "4",
          "ref": [
            "const"
          ],
          "rows_examined_per_scan": 2,
          "rows_produced_per_join": 2,
          "filtered": "100.00",
          "cost_info": {
            "read_cost": "0.50",
            "eval_cost": "0.20",
            "prefix_cost": "0.70",
            "data_read_per_join": "448"
          },
          "used_columns": [
            "id",
            "user_id",
            "enterprise_customer_id"
          ]
        }
      },
      {
        "table": {
          "table_name": "U0",
          "access_type": "eq_ref",
          "possible_keys": [
            "PRIMARY"
          ],
          "key": "PRIMARY",
          "used_key_parts": [
            "uuid"
          ],
          "key_length": "96",
          "ref": [
            "edxapp.U1.enterprise_customer_id"
          ],
          "rows_examined_per_scan": 1,
          "rows_produced_per_join": 2,
          "filtered": "100.00",
          "using_index": true,
          "cost_info": {
            "read_cost": "0.50",
            "eval_cost": "0.20",
            "prefix_cost": "1.40",
            "data_read_per_join": "7K"
          },
          "used_columns": [
            "uuid"
          ]
        }
      },
      {
        "table": {
          "table_name": "enterprise_enterprisecustomeruser",
          "access_type": "ref",
          "possible_keys": [
            "PRIMARY",
            "enterprise_enterprisecus_enterprise_customer_id_u_ffddc29f_uniq"
          ],
          "key": "enterprise_enterprisecus_enterprise_customer_id_u_ffddc29f_uniq",
          "used_key_parts": [
            "enterprise_customer_id"
          ],
          "key_length": "96",
          "ref": [
            "edxapp.U1.enterprise_customer_id"
          ],
          "rows_examined_per_scan": 29,
          "rows_produced_per_join": 52,
          "filtered": "90.00",
          "cost_info": {
            "read_cost": "14.50",
            "eval_cost": "5.22",
            "prefix_cost": "21.71",
            "data_read_per_join": "11K"
          },
          "used_columns": [
            "id",
            "created",
            "modified",
            "user_id",
            "active",
            "linked",
            "enterprise_customer_id",
            "invite_key_id",
            "should_inactivate_other_customers",
            "is_relinkable"
          ],
          "attached_condition": "(0 <> `edxapp`.`enterprise_enterprisecustomeruser`.`linked`)"
        }
      },
      {
        "table": {
          "table_name": "enterprise_enterprisecourseenrollment",
          "access_type": "ref",
          "possible_keys": [
            "enterprise_enterprisecou_enterprise_customer_user_71fe301a_uniq"
          ],
          "key": "enterprise_enterprisecou_enterprise_customer_user_71fe301a_uniq",
          "used_key_parts": [
            "enterprise_customer_user_id"
          ],
          "key_length": "4",
          "ref": [
            "edxapp.enterprise_enterprisecustomeruser.id"
          ],
          "rows_examined_per_scan": 17,
          "rows_produced_per_join": 938,
          "filtered": "100.00",
          "cost_info": {
            "read_cost": "234.53",
            "eval_cost": "93.81",
            "prefix_cost": "350.06",
            "data_read_per_join": "740K"
          },
          "used_columns": [
            "id",
            "created",
            "modified",
            "course_id",
            "enterprise_customer_user_id",
            "source_id",
            "saved_for_later",
            "unenrolled",
            "unenrolled_at"
          ]
        }
      }
    ],
    "select_list_subqueries": [
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 6,
          "cost_info": {
            "query_cost": "0.35"
          },
          "table": {
            "table_name": "U0",
            "access_type": "eq_ref",
            "possible_keys": [
              "PRIMARY"
            ],
            "key": "PRIMARY",
            "used_key_parts": [
              "id"
            ],
            "key_length": "767",
            "ref": [
              "edxapp.enterprise_enterprisecourseenrollment.course_id"
            ],
            "rows_examined_per_scan": 1,
            "rows_produced_per_join": 1,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "0.25",
              "eval_cost": "0.10",
              "prefix_cost": "0.35",
              "data_read_per_join": "3K"
            },
            "used_columns": [
              "id",
              "end"
            ]
          }
        }
      },
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 5,
          "cost_info": {
            "query_cost": "0.35"
          },
          "table": {
            "table_name": "U0",
            "access_type": "eq_ref",
            "possible_keys": [
              "PRIMARY"
            ],
            "key": "PRIMARY",
            "used_key_parts": [
              "id"
            ],
            "key_length": "767",
            "ref": [
              "edxapp.enterprise_enterprisecourseenrollment.course_id"
            ],
            "rows_examined_per_scan": 1,
            "rows_produced_per_join": 1,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "0.25",
              "eval_cost": "0.10",
              "prefix_cost": "0.35",
              "data_read_per_join": "3K"
            },
            "used_columns": [
              "id",
              "start"
            ]
          }
        }
      },
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 4,
          "cost_info": {
            "query_cost": "0.35"
          },
          "table": {
            "table_name": "U0",
            "access_type": "eq_ref",
            "possible_keys": [
              "PRIMARY"
            ],
            "key": "PRIMARY",
            "used_key_parts": [
              "id"
            ],
            "key_length": "4",
            "ref": [
              "edxapp.enterprise_enterprisecustomeruser.user_id"
            ],
            "rows_examined_per_scan": 1,
            "rows_produced_per_join": 1,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "0.25",
              "eval_cost": "0.10",
              "prefix_cost": "0.35",
              "data_read_per_join": "2K"
            },
            "used_columns": [
              "id",
              "email"
            ],
            "attached_condition": "(`edxapp`.`U0`.`id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)"
          }
        }
      },
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 3,
          "cost_info": {
            "query_cost": "0.35"
          },
          "ordering_operation": {
            "using_filesort": false,
            "table": {
              "table_name": "U0",
              "access_type": "eq_ref",
              "possible_keys": [
                "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                "student_courseenrollment_user_id_4263a8e2",
                "student_cou_user_id_b19dcd_idx",
                "student_courseenrollment_course_id_a6f93be8"
              ],
              "key": "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
              "used_key_parts": [
                "user_id",
                "course_id"
              ],
              "key_length": "771",
              "ref": [
                "edxapp.enterprise_enterprisecustomeruser.user_id",
                "edxapp.enterprise_enterprisecourseenrollment.course_id"
              ],
              "rows_examined_per_scan": 1,
              "rows_produced_per_join": 1,
              "filtered": "100.00",
              "index_condition": "(`edxapp`.`U0`.`user_id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)",
              "cost_info": {
                "read_cost": "0.25",
                "eval_cost": "0.10",
                "prefix_cost": "0.35",
                "data_read_per_join": "1K"
              },
              "used_columns": [
                "id",
                "course_id",
                "created",
                "user_id"
              ]
            }
          }
        }
      },
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 2,
          "cost_info": {
            "query_cost": "0.35"
          },
          "ordering_operation": {
            "using_filesort": false,
            "table": {
              "table_name": "U0",
              "access_type": "eq_ref",
              "possible_keys": [
                "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                "student_courseenrollment_user_id_4263a8e2",
                "student_cou_user_id_b19dcd_idx",
                "student_courseenrollment_course_id_a6f93be8"
              ],
              "key": "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
              "used_key_parts": [
                "user_id",
                "course_id"
              ],
              "key_length": "771",
              "ref": [
                "edxapp.enterprise_enterprisecustomeruser.user_id",
                "edxapp.enterprise_enterprisecourseenrollment.course_id"
              ],
              "rows_examined_per_scan": 1,
              "rows_produced_per_join": 1,
              "filtered": "100.00",
              "index_condition": "(`edxapp`.`U0`.`user_id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)",
              "cost_info": {
                "read_cost": "0.25",
                "eval_cost": "0.10",
                "prefix_cost": "0.35",
                "data_read_per_join": "1K"
              },
              "used_columns": [
                "id",
                "course_id",
                "mode",
                "user_id"
              ]
            }
          }
        }
      }
    ]
  }
}
}

As @ormsbee suggested, I tried indexes on different columns, but improvements can be seen only for SELECT query for staff (without the WHERE clause) when there is an index on enterprise_enterprisecourseenrollment(created):

Before

image

image

After

image

image

Given all this, I can propose this plan:

  1. Find a way to use the default model manager for paginator's COUNT query.
  2. Add a new index on enterprise_enterprisecourseenrollment(created).
  3. Fix the bug with enterprise customer users string representation.
  4. Use read_replica_or_default (in progress).

If I'm not missing anything, this should make us safe from performance issues or high DB load.

What do you think?

@mphilbrick211 mphilbrick211 removed the waiting on author PR author needs to resolve review requests, answer questions, fix tests, etc. label Dec 11, 2023
@ormsbee
Copy link

ormsbee commented Dec 14, 2023

@0x29a: Is sorting that table by created functionally equivalent to sorting it by its primary key? If so, would changing its default ordering to the primary key get us the lift without the migration?

@0x29a
Copy link
Contributor Author

0x29a commented Dec 15, 2023

@ormsbee, ah, right, I forgot to address this comment. Indeed, sorting by created and id is equivalent. And yes, it gets us the same speed up.

Results.

image

image

{
"query_block": {
  "select_id": 1,
  "cost_info": {
    "query_cost": "23590.40"
  },
  "ordering_operation": {
    "using_filesort": false,
    "nested_loop": [
      {
        "table": {
          "table_name": "enterprise_enterprisecourseenrollment",
          "access_type": "index",
          "possible_keys": [
            "enterprise_enterprisecou_enterprise_customer_user_71fe301a_uniq"
          ],
          "key": "PRIMARY",
          "used_key_parts": [
            "id"
          ],
          "key_length": "4",
          "rows_examined_per_scan": 111,
          "rows_produced_per_join": 52227,
          "filtered": "100.00",
          "cost_info": {
            "read_cost": "88.25",
            "eval_cost": "5222.70",
            "prefix_cost": "5310.95",
            "data_read_per_join": "40M"
          },
          "used_columns": [
            "id",
            "created",
            "modified",
            "course_id",
            "enterprise_customer_user_id",
            "source_id",
            "saved_for_later",
            "unenrolled",
            "unenrolled_at"
          ]
        }
      },
      {
        "table": {
          "table_name": "enterprise_enterprisecustomeruser",
          "access_type": "eq_ref",
          "possible_keys": [
            "PRIMARY"
          ],
          "key": "PRIMARY",
          "used_key_parts": [
            "id"
          ],
          "key_length": "4",
          "ref": [
            "edxapp.enterprise_enterprisecourseenrollment.enterprise_customer_user_id"
          ],
          "rows_examined_per_scan": 1,
          "rows_produced_per_join": 47004,
          "filtered": "90.00",
          "cost_info": {
            "read_cost": "13056.75",
            "eval_cost": "4700.43",
            "prefix_cost": "23590.40",
            "data_read_per_join": "10M"
          },
          "used_columns": [
            "id",
            "created",
            "modified",
            "user_id",
            "active",
            "linked",
            "enterprise_customer_id",
            "invite_key_id",
            "should_inactivate_other_customers",
            "is_relinkable"
          ],
          "attached_condition": "(0 <> `edxapp`.`enterprise_enterprisecustomeruser`.`linked`)"
        }
      }
    ],
    "select_list_subqueries": [
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 6,
          "cost_info": {
            "query_cost": "0.35"
          },
          "table": {
            "table_name": "U0",
            "access_type": "eq_ref",
            "possible_keys": [
              "PRIMARY"
            ],
            "key": "PRIMARY",
            "used_key_parts": [
              "id"
            ],
            "key_length": "767",
            "ref": [
              "edxapp.enterprise_enterprisecourseenrollment.course_id"
            ],
            "rows_examined_per_scan": 1,
            "rows_produced_per_join": 1,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "0.25",
              "eval_cost": "0.10",
              "prefix_cost": "0.35",
              "data_read_per_join": "3K"
            },
            "used_columns": [
              "id",
              "end"
            ]
          }
        }
      },
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 5,
          "cost_info": {
            "query_cost": "0.35"
          },
          "table": {
            "table_name": "U0",
            "access_type": "eq_ref",
            "possible_keys": [
              "PRIMARY"
            ],
            "key": "PRIMARY",
            "used_key_parts": [
              "id"
            ],
            "key_length": "767",
            "ref": [
              "edxapp.enterprise_enterprisecourseenrollment.course_id"
            ],
            "rows_examined_per_scan": 1,
            "rows_produced_per_join": 1,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "0.25",
              "eval_cost": "0.10",
              "prefix_cost": "0.35",
              "data_read_per_join": "3K"
            },
            "used_columns": [
              "id",
              "start"
            ]
          }
        }
      },
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 4,
          "cost_info": {
            "query_cost": "0.35"
          },
          "table": {
            "table_name": "U0",
            "access_type": "eq_ref",
            "possible_keys": [
              "PRIMARY"
            ],
            "key": "PRIMARY",
            "used_key_parts": [
              "id"
            ],
            "key_length": "4",
            "ref": [
              "edxapp.enterprise_enterprisecustomeruser.user_id"
            ],
            "rows_examined_per_scan": 1,
            "rows_produced_per_join": 1,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "0.25",
              "eval_cost": "0.10",
              "prefix_cost": "0.35",
              "data_read_per_join": "2K"
            },
            "used_columns": [
              "id",
              "email"
            ],
            "attached_condition": "(`edxapp`.`U0`.`id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)"
          }
        }
      },
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 3,
          "cost_info": {
            "query_cost": "0.35"
          },
          "ordering_operation": {
            "using_filesort": false,
            "table": {
              "table_name": "U0",
              "access_type": "eq_ref",
              "possible_keys": [
                "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                "student_courseenrollment_user_id_4263a8e2",
                "student_cou_user_id_b19dcd_idx",
                "student_courseenrollment_course_id_a6f93be8"
              ],
              "key": "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
              "used_key_parts": [
                "user_id",
                "course_id"
              ],
              "key_length": "771",
              "ref": [
                "edxapp.enterprise_enterprisecustomeruser.user_id",
                "edxapp.enterprise_enterprisecourseenrollment.course_id"
              ],
              "rows_examined_per_scan": 1,
              "rows_produced_per_join": 1,
              "filtered": "100.00",
              "index_condition": "(`edxapp`.`U0`.`user_id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)",
              "cost_info": {
                "read_cost": "0.25",
                "eval_cost": "0.10",
                "prefix_cost": "0.35",
                "data_read_per_join": "1K"
              },
              "used_columns": [
                "id",
                "course_id",
                "created",
                "user_id"
              ]
            }
          }
        }
      },
      {
        "dependent": true,
        "cacheable": false,
        "query_block": {
          "select_id": 2,
          "cost_info": {
            "query_cost": "0.35"
          },
          "ordering_operation": {
            "using_filesort": false,
            "table": {
              "table_name": "U0",
              "access_type": "eq_ref",
              "possible_keys": [
                "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
                "student_courseenrollment_user_id_4263a8e2",
                "student_cou_user_id_b19dcd_idx",
                "student_courseenrollment_course_id_a6f93be8"
              ],
              "key": "student_courseenrollment_user_id_course_id_5d34a47f_uniq",
              "used_key_parts": [
                "user_id",
                "course_id"
              ],
              "key_length": "771",
              "ref": [
                "edxapp.enterprise_enterprisecustomeruser.user_id",
                "edxapp.enterprise_enterprisecourseenrollment.course_id"
              ],
              "rows_examined_per_scan": 1,
              "rows_produced_per_join": 1,
              "filtered": "100.00",
              "index_condition": "(`edxapp`.`U0`.`user_id` = `edxapp`.`enterprise_enterprisecustomeruser`.`user_id`)",
              "cost_info": {
                "read_cost": "0.25",
                "eval_cost": "0.10",
                "prefix_cost": "0.35",
                "data_read_per_join": "1K"
              },
              "used_columns": [
                "id",
                "course_id",
                "mode",
                "user_id"
              ]
            }
          }
        }
      }
    ]
  }
}
}

@0x29a 0x29a force-pushed the 0x29a/enterprise-course-enrollments-api-improvements branch 7 times, most recently from 39af57c to 6422e62 Compare December 30, 2023 09:00
@0x29a
Copy link
Contributor Author

0x29a commented Jan 2, 2024

@feanil, I went ahead and added the suggested improvements, see these commits:

  • 06646f4 — ordering by id instead of created.
  • 36a9679 — fix for the costly count query.
  • 6422e62 — using read replica for GET queries.

I didn't fix the issue with enrollment users string representation, as it's beyond the scope of this PR and we currently don't have budget for this.

Also I rebased the branch and moved the changelog item to the "Unreleased" section, just to avoid fixing conflicts again.

cc @ormsbee

Copy link

@ormsbee ormsbee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small request to add a comment, but you can feel free to squash/merge after that. Please ping me before this is deployed so that I can monitor it go out. Thank you.


class Meta:
unique_together = (('enterprise_customer_user', 'course_id',),)
app_label = 'enterprise'
ordering = ['created']
ordering = ['id']
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment to the effect that the originally intended ordering is by created, but there was never an index added for it, and that we're ordering by id because we believe it's equivalent and doesn't require an expensive migration that might require downtime.

At some point, it's possible that created will become editable, a bug will be filed, and someone is going to look at this and think, "oh, easy fix, I'll just make this created instead of id" without realizing the potential consequences.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, @ormsbee, thank you for taking time to review this at last.

@0x29a 0x29a force-pushed the 0x29a/enterprise-course-enrollments-api-improvements branch from 6422e62 to aa3f73b Compare January 4, 2024 10:54
@0x29a
Copy link
Contributor Author

0x29a commented Jan 4, 2024

I'm happy to have this approved, @ormsbee. Thank you so much! @e0d, are we good to merge this?

@feanil
Copy link
Contributor

feanil commented Jan 9, 2024

@0x29a it looks like there is a conflict on the branch, can you rebase and then I can merge this. Can you also please, bump the version for this so we can do a release of this code.

@0x29a 0x29a force-pushed the 0x29a/enterprise-course-enrollments-api-improvements branch 3 times, most recently from 3c45f1a to cd01e5c Compare January 11, 2024 04:57
@0x29a
Copy link
Contributor Author

0x29a commented Jan 11, 2024

it looks like there is a conflict on the branch, can you rebase and then I can merge this. Can you also please, bump the version for this so we can do a release of this code

@feanil, done.

@feanil
Copy link
Contributor

feanil commented Jan 11, 2024

@0x29a sorry, but based on the content of the PR and semver, we should bump the minor version not the patch version, you're adding a new feature but it's backwords compatible. Sorry I was not clear about that before.

- Allows Enrollment API Admin to see all enrollments.
- Makes the endpoint return more fields, such as: enrollment_track,
  enrollment_date, user_email, course_start and course_end.
- Changes EnterpriseCourseEnrollment's default ordering from 'created'
  to 'id', which equivalent, but faster in some cases (due to the
  existing indes on 'id').

Co-authored-by: Maxim Beder <maxim@opencraft.com>
@0x29a 0x29a force-pushed the 0x29a/enterprise-course-enrollments-api-improvements branch from cd01e5c to 237e4a1 Compare January 16, 2024 12:35
@feanil feanil merged commit f6e6ea0 into openedx:master Jan 16, 2024
9 checks passed
@openedx-webhooks
Copy link

@0x29a 🎉 Your pull request was merged! Please take a moment to answer a two question survey so we can improve your experience in the future.

@0x29a 0x29a deleted the 0x29a/enterprise-course-enrollments-api-improvements branch January 16, 2024 13:26
@0x29a
Copy link
Contributor Author

0x29a commented Jan 16, 2024

Thanks for merging this, @feanil!

@feanil
Copy link
Contributor

feanil commented Jan 17, 2024

No problem! Thanks for your patience, it took a while but I'm glad we got it landed, improving our APIs is really useful but it can be hard to assess he performance impact so I appreciate you taking all the extra time and effort needed to land this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
open-source-contribution PR author is not from Axim or 2U
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

9 participants