Team deleted and learner removed events
Now emitting `edx.team.deleted` event where relevant. Includes tests. Also includes tests to cover learner_removed events that are fired as part of the deletion process, as well as learner_removed events fired from the edit membership view. Adding these deleted events has brought about a change in the teams API - the DELETE membership endpoint now takes an optional `admin` query parameter to differentiate between admin removal and self removal for analytics purposes. Also includes some changes needed to get page viewed events working with admin tools changes made in this PR.
This commit is contained in:
@@ -90,7 +90,11 @@ define([
|
||||
verifyTeamMembersView(view);
|
||||
|
||||
deleteTeamMemember(view, true);
|
||||
AjaxHelpers.expectJsonRequest(requests, 'DELETE', '/api/team/v0/team_membership/av,frodo', null);
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests,
|
||||
'DELETE',
|
||||
'/api/team/v0/team_membership/av,frodo?admin=true'
|
||||
);
|
||||
AjaxHelpers.respondWithNoContent(requests);
|
||||
expect(view.teamEvents.trigger).toHaveBeenCalledWith(
|
||||
'teams:update', {
|
||||
@@ -112,7 +116,11 @@ define([
|
||||
verifyTeamMembersView(view);
|
||||
|
||||
deleteTeamMemember(view, true);
|
||||
AjaxHelpers.expectJsonRequest(requests, 'DELETE', '/api/team/v0/team_membership/av,frodo', null);
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests,
|
||||
'DELETE',
|
||||
'/api/team/v0/team_membership/av,frodo?admin=true'
|
||||
);
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
expect(TeamUtils.showMessage).toHaveBeenCalledWith(
|
||||
'An error occurred while removing the member from the team. Try again.',
|
||||
|
||||
@@ -145,7 +145,7 @@ define([
|
||||
}
|
||||
],
|
||||
'fires a page view event for the edit team page': [
|
||||
'topics/' + TeamSpecHelpers.testTopicID + '/' + 'test_team_id/edit-team',
|
||||
'teams/' + TeamSpecHelpers.testTopicID + '/' + 'test_team_id/edit-team',
|
||||
{
|
||||
page_name: 'edit-team',
|
||||
topic_id: TeamSpecHelpers.testTopicID,
|
||||
@@ -154,7 +154,9 @@ define([
|
||||
]
|
||||
}, function (url, expectedEvent) {
|
||||
var requests = AjaxHelpers.requests(this),
|
||||
teamsTabView = createTeamsTabView();
|
||||
teamsTabView = createTeamsTabView({
|
||||
userInfo: TeamSpecHelpers.createMockUserInfo({ staff: true })
|
||||
});
|
||||
teamsTabView.router.navigate(url, {trigger: true});
|
||||
if (requests.length) {
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
function () {
|
||||
$.ajax({
|
||||
type: 'DELETE',
|
||||
url: self.teamMembershipDetailUrl + username
|
||||
url: self.teamMembershipDetailUrl.concat(username, '?admin=true')
|
||||
}).done(function () {
|
||||
self.teamEvents.trigger('teams:update', {
|
||||
action: 'leave',
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
});
|
||||
self.mainView = editViewWithHeader;
|
||||
self.render();
|
||||
TeamAnalytics.emitPageViewed('edit-team', topicID, teamID);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -303,7 +304,7 @@
|
||||
}
|
||||
);
|
||||
self.render();
|
||||
TeamAnalytics.emitPageViewed('edit-team', topicID, teamID);
|
||||
TeamAnalytics.emitPageViewed('edit-team-members', topicID, teamID);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -389,12 +389,8 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
|
||||
|
||||
def delete_membership(self, team_id, username, expected_status=200, **kwargs):
|
||||
"""Deletes an individual membership record. Verifies expected_status."""
|
||||
return self.make_call(
|
||||
reverse('team_membership_detail', args=[team_id, username]),
|
||||
expected_status,
|
||||
'delete',
|
||||
**kwargs
|
||||
)
|
||||
url = reverse('team_membership_detail', args=[team_id, username]) + '?admin=true'
|
||||
return self.make_call(url, expected_status, 'delete', **kwargs)
|
||||
|
||||
def verify_expanded_public_user(self, user):
|
||||
"""Verifies that fields exist on the returned user json indicating that it is expanded."""
|
||||
@@ -803,9 +799,12 @@ class TestDetailTeamAPI(TeamAPITestCase):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestDeleteTeamAPI(TeamAPITestCase):
|
||||
class TestDeleteTeamAPI(EventTestMixin, TeamAPITestCase):
|
||||
"""Test cases for the team delete endpoint."""
|
||||
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super(TestDeleteTeamAPI, self).setUp('teams.views.tracker')
|
||||
|
||||
@ddt.data(
|
||||
(None, 401),
|
||||
('student_inactive', 401),
|
||||
@@ -818,6 +817,19 @@ class TestDeleteTeamAPI(TeamAPITestCase):
|
||||
@ddt.unpack
|
||||
def test_access(self, user, status):
|
||||
self.delete_team(self.solar_team.team_id, status, user=user)
|
||||
if status == 204:
|
||||
self.assert_event_emitted(
|
||||
'edx.team.deleted',
|
||||
team_id=self.solar_team.team_id,
|
||||
course_id=unicode(self.test_course_1.id)
|
||||
)
|
||||
self.assert_event_emitted(
|
||||
'edx.team.learner_removed',
|
||||
team_id=self.solar_team.team_id,
|
||||
course_id=unicode(self.test_course_1.id),
|
||||
remove_method='team_deleted',
|
||||
user_id=self.users['student_enrolled'].id
|
||||
)
|
||||
|
||||
def test_does_not_exist(self):
|
||||
self.delete_team('nonexistent', 404)
|
||||
@@ -825,6 +837,18 @@ class TestDeleteTeamAPI(TeamAPITestCase):
|
||||
def test_memberships_deleted(self):
|
||||
self.assertEqual(CourseTeamMembership.objects.filter(team=self.solar_team).count(), 1)
|
||||
self.delete_team(self.solar_team.team_id, 204, user='staff')
|
||||
self.assert_event_emitted(
|
||||
'edx.team.deleted',
|
||||
team_id=self.solar_team.team_id,
|
||||
course_id=unicode(self.test_course_1.id)
|
||||
)
|
||||
self.assert_event_emitted(
|
||||
'edx.team.learner_removed',
|
||||
team_id=self.solar_team.team_id,
|
||||
course_id=unicode(self.test_course_1.id),
|
||||
remove_method='team_deleted',
|
||||
user_id=self.users['student_enrolled'].id
|
||||
)
|
||||
self.assertEqual(CourseTeamMembership.objects.filter(team=self.solar_team).count(), 0)
|
||||
|
||||
|
||||
@@ -1351,17 +1375,32 @@ class TestDeleteMembershipAPI(EventTestMixin, TeamAPITestCase):
|
||||
)
|
||||
|
||||
if status == 204:
|
||||
remove_method = 'self_removal' if user == 'student_enrolled' else 'removed_by_admin'
|
||||
self.assert_event_emitted(
|
||||
'edx.team.learner_removed',
|
||||
team_id=self.solar_team.team_id,
|
||||
course_id=unicode(self.solar_team.course_id),
|
||||
user_id=self.users['student_enrolled'].id,
|
||||
remove_method=remove_method
|
||||
remove_method='removed_by_admin'
|
||||
)
|
||||
else:
|
||||
self.assert_no_events_were_emitted()
|
||||
|
||||
def test_leave_team(self):
|
||||
"""
|
||||
The key difference between this test and test_access above is that
|
||||
removal via "Edit Membership" and "Leave Team" emit different events
|
||||
despite hitting the same API endpoint, due to the 'admin' query string.
|
||||
"""
|
||||
url = reverse('team_membership_detail', args=[self.solar_team.team_id, self.users['student_enrolled'].username])
|
||||
self.make_call(url, 204, 'delete', user='student_enrolled')
|
||||
self.assert_event_emitted(
|
||||
'edx.team.learner_removed',
|
||||
team_id=self.solar_team.team_id,
|
||||
course_id=unicode(self.solar_team.course_id),
|
||||
user_id=self.users['student_enrolled'].id,
|
||||
remove_method='self_removal'
|
||||
)
|
||||
|
||||
def test_bad_team(self):
|
||||
self.delete_membership('no_such_team', self.users['student_enrolled'].username, 404)
|
||||
|
||||
|
||||
@@ -612,9 +612,23 @@ class TeamsDetailView(ExpandableFieldViewMixin, RetrievePatchAPIView):
|
||||
"""DELETE /api/team/v0/teams/{team_id}"""
|
||||
team = get_object_or_404(CourseTeam, team_id=team_id)
|
||||
self.check_object_permissions(request, team)
|
||||
# Note: list() forces the queryset to be evualuated before delete()
|
||||
memberships = list(CourseTeamMembership.get_memberships(team_id=team_id))
|
||||
|
||||
# Note: also deletes all team memberships associated with this team
|
||||
team.delete()
|
||||
log.info('user %d deleted team %s', request.user.id, team_id)
|
||||
tracker.emit('edx.team.deleted', {
|
||||
'team_id': team_id,
|
||||
'course_id': unicode(team.course_id),
|
||||
})
|
||||
for member in memberships:
|
||||
tracker.emit('edx.team.learner_removed', {
|
||||
'team_id': team_id,
|
||||
'course_id': unicode(team.course_id),
|
||||
'remove_method': 'team_deleted',
|
||||
'user_id': member.user_id
|
||||
})
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@@ -1184,6 +1198,9 @@ class MembershipDetailView(ExpandableFieldViewMixin, GenericAPIView):
|
||||
team = self.get_team(team_id)
|
||||
if has_team_api_access(request.user, team.course_id, access_username=username):
|
||||
membership = self.get_membership(username, team)
|
||||
removal_method = 'self_removal'
|
||||
if 'admin' in request.QUERY_PARAMS:
|
||||
removal_method = 'removed_by_admin'
|
||||
membership.delete()
|
||||
tracker.emit(
|
||||
'edx.team.learner_removed',
|
||||
@@ -1191,7 +1208,7 @@ class MembershipDetailView(ExpandableFieldViewMixin, GenericAPIView):
|
||||
'team_id': team.team_id,
|
||||
'course_id': unicode(team.course_id),
|
||||
'user_id': membership.user.id,
|
||||
'remove_method': 'self_removal' if membership.user == request.user else 'removed_by_admin'
|
||||
'remove_method': removal_method
|
||||
}
|
||||
)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
Reference in New Issue
Block a user