-
Notifications
You must be signed in to change notification settings - Fork 47
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
feat: allow enrollment api admin to see all enrollments #1714
Conversation
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:
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. |
585e511
to
fcc3e08
Compare
There was a problem hiding this 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.
@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 |
There was a problem hiding this comment.
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 EnterpriseCustomer
s and then checking that he filter returns results both customers (assuming this is the intended behavior).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved by 3cf711f
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) | ||
) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved by 433e06b
👍 Other than some changes necessary for the new test case, everything else is fine.
|
@mphilbrick211 tests running. |
f28747d
to
433e06b
Compare
@0x29a looks like there are some quality and linting issues. |
433e06b
to
2bbff99
Compare
@Cup0fCoffee no problem. 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 |
2bbff99
to
aa912ed
Compare
@e0d Kind reminder ^ |
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 <[email protected]>, Cup0fCoffee <[email protected]>
@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. |
FYI @feanil @felipemontoya input into future plans around a standard enrollment API. |
3dce3c4
to
fe92bec
Compare
@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. |
29bd233
to
91be371
Compare
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
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{
"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{
"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{
"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{
"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 Given all this, I can propose this plan:
If I'm not missing anything, this should make us safe from performance issues or high DB load. What do you think? |
@0x29a: Is sorting that table by |
@ormsbee, ah, right, I forgot to address this comment. Indeed, sorting by Results.{
"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"
]
}
}
}
}
]
}
}
} |
39af57c
to
6422e62
Compare
@feanil, I went ahead and added the suggested improvements, see these commits:
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 |
There was a problem hiding this 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'] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
6422e62
to
aa3f73b
Compare
@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. |
3c45f1a
to
cd01e5c
Compare
@feanil, done. |
@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 <[email protected]>
cd01e5c
to
237e4a1
Compare
@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. |
Thanks for merging this, @feanil! |
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. |
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:
This is limiting for 2 reasons:
This PR updates the enterprise course enrollment endpoint to use rules.py,
enterprise.can_enroll_learners
in particular. Now, only staff or users withenrollment_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:
Testing instructions
Before testing, you'll need to install
edx-enterprise
to your devstack:cd $DEVSTACK_ROOT/src
git clone -b 0x29a/enterprise-course-enrollments-api-improvements [email protected]:open-craft/edx-enterprise.git
cd ../devstack
make lms-shell
cd /edx/src/edx-enterprise/
pip uninstall edx-enterprise
pip install -e .
Then, you'll need to create:
[email protected]
and[email protected]
.<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:
<LMS>/enterprise/api/v1/enterprise-course-enrollment/
.<LMS>/enterprise/api/v1/enterprise-course-enrollment/
.Checklists
Merge checklist:
requirements/*.txt
files)base.in
if needed in production but edx-platform doesn't install ittest-master.in
if edx-platform pins it, with a matching versionmake upgrade && make requirements
have been run to regenerate requirementsmake static
has been run to update webpack bundling if any static content was updated./manage.py makemigrations
has been run./manage.py lms makemigrations
in the shell.Post merge:
(so basically once your build finishes, after maybe a minute you should see the new version in PyPi automatically (on refresh))
make upgrade
in edx-platform will look for the latest version in PyPi.Private-ref