Commit deacfebd by Diana Huang

Merge pull request #9607 from edx/diana/teams-event-tracking

Add teams eventing and tests.
parents d567e5e3 a743f4a6
...@@ -11,7 +11,7 @@ from flaky import flaky ...@@ -11,7 +11,7 @@ from flaky import flaky
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from uuid import uuid4 from uuid import uuid4
from ..helpers import UniqueCourseTest from ..helpers import UniqueCourseTest, EventsTestMixin
from ...fixtures import LMS_BASE_URL from ...fixtures import LMS_BASE_URL
from ...fixtures.course import CourseFixture from ...fixtures.course import CourseFixture
from ...fixtures.discussion import ( from ...fixtures.discussion import (
...@@ -28,7 +28,7 @@ from ...pages.lms.teams import TeamsPage, MyTeamsPage, BrowseTopicsPage, BrowseT ...@@ -28,7 +28,7 @@ from ...pages.lms.teams import TeamsPage, MyTeamsPage, BrowseTopicsPage, BrowseT
TOPICS_PER_PAGE = 12 TOPICS_PER_PAGE = 12
class TeamsTabBase(UniqueCourseTest): class TeamsTabBase(EventsTestMixin, UniqueCourseTest):
"""Base class for Teams Tab tests""" """Base class for Teams Tab tests"""
def setUp(self): def setUp(self):
super(TeamsTabBase, self).setUp() super(TeamsTabBase, self).setUp()
...@@ -123,6 +123,10 @@ class TeamsTabBase(UniqueCourseTest): ...@@ -123,6 +123,10 @@ class TeamsTabBase(UniqueCourseTest):
# We are doing these operations on this top-level page object to avoid reloading the page. # We are doing these operations on this top-level page object to avoid reloading the page.
self.teams_page.verify_my_team_count(expected_number_of_teams) self.teams_page.verify_my_team_count(expected_number_of_teams)
def only_team_events(self, event):
"""Filter out all non-team events."""
return event['event_type'].startswith('edx.team.')
@ddt.ddt @ddt.ddt
@attr('shard_5') @attr('shard_5')
...@@ -899,7 +903,8 @@ class CreateTeamTest(TeamFormActions): ...@@ -899,7 +903,8 @@ class CreateTeamTest(TeamFormActions):
Then I should see the Create Team header and form Then I should see the Create Team header and form
When I fill all the fields present with appropriate data When I fill all the fields present with appropriate data
And I click Create button And I click Create button
Then I should see the page for my team Then I expect analytics events to be emitted
And I should see the page for my team
And I should see the message that says "You are member of this team" And I should see the message that says "You are member of this team"
And the new team should be added to the list of teams within the topic And the new team should be added to the list of teams within the topic
And the number of teams should be updated on the topic card And the number of teams should be updated on the topic card
...@@ -911,7 +916,24 @@ class CreateTeamTest(TeamFormActions): ...@@ -911,7 +916,24 @@ class CreateTeamTest(TeamFormActions):
self.verify_and_navigate_to_create_team_page() self.verify_and_navigate_to_create_team_page()
self.fill_create_or_edit_form() self.fill_create_or_edit_form()
self.create_or_edit_team_page.submit_form()
expected_events = [
{
'event_type': 'edx.team.created',
'event': {
'course_id': self.course_id,
}
},
{
'event_type': 'edx.team.learner_added',
'event': {
'course_id': self.course_id,
'add_method': 'added_on_create',
}
}
]
with self.assert_events_match_during(event_filter=self.only_team_events, expected_events=expected_events):
self.create_or_edit_team_page.submit_form()
# Verify that the page is shown for the new team # Verify that the page is shown for the new team
team_page = TeamPage(self.browser, self.course_id) team_page = TeamPage(self.browser, self.course_id)
...@@ -1330,6 +1352,7 @@ class TeamPageTest(TeamsTabBase): ...@@ -1330,6 +1352,7 @@ class TeamPageTest(TeamsTabBase):
And I should not see New Post button And I should not see New Post button
When I click on Join Team button When I click on Join Team button
Then there should be no Join Team button and no message Then there should be no Join Team button and no message
And an analytics event should be emitted
And I should see the updated information under Team Details And I should see the updated information under Team Details
And I should see New Post button And I should see New Post button
And if I switch to "My Team", the team I have joined is displayed And if I switch to "My Team", the team I have joined is displayed
...@@ -1337,7 +1360,17 @@ class TeamPageTest(TeamsTabBase): ...@@ -1337,7 +1360,17 @@ class TeamPageTest(TeamsTabBase):
self._set_team_configuration_and_membership(create_membership=False) self._set_team_configuration_and_membership(create_membership=False)
self.team_page.visit() self.team_page.visit()
self.assertTrue(self.team_page.join_team_button_present) self.assertTrue(self.team_page.join_team_button_present)
self.team_page.click_join_team_button() expected_events = [
{
'event_type': 'edx.team.learner_added',
'event': {
'course_id': self.course_id,
'add_method': 'joined_from_team_view'
}
}
]
with self.assert_events_match_during(event_filter=self.only_team_events, expected_events=expected_events):
self.team_page.click_join_team_button()
self.assertFalse(self.team_page.join_team_button_present) self.assertFalse(self.team_page.join_team_button_present)
self.assertFalse(self.team_page.join_team_message_present) self.assertFalse(self.team_page.join_team_message_present)
self.assert_team_details(num_members=1, is_member=True) self.assert_team_details(num_members=1, is_member=True)
...@@ -1397,6 +1430,7 @@ class TeamPageTest(TeamsTabBase): ...@@ -1397,6 +1430,7 @@ class TeamPageTest(TeamsTabBase):
Then I should see Leave Team link Then I should see Leave Team link
When I click on Leave Team link When I click on Leave Team link
Then user should be removed from team Then user should be removed from team
And an analytics event should be emitted
And I should see Join Team button And I should see Join Team button
And I should not see New Post button And I should not see New Post button
And if I switch to "My Team", the team I have left is not displayed And if I switch to "My Team", the team I have left is not displayed
...@@ -1405,7 +1439,17 @@ class TeamPageTest(TeamsTabBase): ...@@ -1405,7 +1439,17 @@ class TeamPageTest(TeamsTabBase):
self.team_page.visit() self.team_page.visit()
self.assertFalse(self.team_page.join_team_button_present) self.assertFalse(self.team_page.join_team_button_present)
self.assert_team_details(num_members=1) self.assert_team_details(num_members=1)
self.team_page.click_leave_team_link() expected_events = [
{
'event_type': 'edx.team.learner_removed',
'event': {
'course_id': self.course_id,
'remove_method': 'self_removal'
}
}
]
with self.assert_events_match_during(event_filter=self.only_team_events, expected_events=expected_events):
self.team_page.click_leave_team_link()
self.assert_team_details(num_members=0, is_member=False) self.assert_team_details(num_members=0, is_member=False)
self.assertTrue(self.team_page.join_team_button_present) self.assertTrue(self.team_page.join_team_button_present)
......
...@@ -14,6 +14,7 @@ from rest_framework.test import APITestCase, APIClient ...@@ -14,6 +14,7 @@ from rest_framework.test import APITestCase, APIClient
from courseware.tests.factories import StaffFactory from courseware.tests.factories import StaffFactory
from common.test.utils import skip_signal from common.test.utils import skip_signal
from util.testing import EventTestMixin
from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentFactory from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentFactory
from student.models import CourseEnrollment from student.models import CourseEnrollment
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
...@@ -529,9 +530,12 @@ class TestListTeamsAPI(TeamAPITestCase): ...@@ -529,9 +530,12 @@ class TestListTeamsAPI(TeamAPITestCase):
@ddt.ddt @ddt.ddt
class TestCreateTeamAPI(TeamAPITestCase): class TestCreateTeamAPI(EventTestMixin, TeamAPITestCase):
"""Test cases for the team creation endpoint.""" """Test cases for the team creation endpoint."""
def setUp(self): # pylint: disable=arguments-differ
super(TestCreateTeamAPI, self).setUp('teams.views.tracker')
@ddt.data( @ddt.data(
(None, 401), (None, 401),
('student_inactive', 401), ('student_inactive', 401),
...@@ -549,11 +553,15 @@ class TestCreateTeamAPI(TeamAPITestCase): ...@@ -549,11 +553,15 @@ class TestCreateTeamAPI(TeamAPITestCase):
teams = self.get_teams_list(user=user) teams = self.get_teams_list(user=user)
self.assertIn("New Team", [team['name'] for team in teams['results']]) self.assertIn("New Team", [team['name'] for team in teams['results']])
def _expected_team_id(self, team, expected_prefix):
""" Return the team id that we'd expect given this team data and this prefix. """
return expected_prefix + '-' + team['discussion_topic_id']
def verify_expected_team_id(self, team, expected_prefix): def verify_expected_team_id(self, team, expected_prefix):
""" Verifies that the team id starts with the specified prefix and ends with the discussion_topic_id """ """ Verifies that the team id starts with the specified prefix and ends with the discussion_topic_id """
self.assertIn('id', team) self.assertIn('id', team)
self.assertIn('discussion_topic_id', team) self.assertIn('discussion_topic_id', team)
self.assertEqual(team['id'], expected_prefix + '-' + team['discussion_topic_id']) self.assertEqual(team['id'], self._expected_team_id(team, expected_prefix))
def test_naming(self): def test_naming(self):
new_teams = [ new_teams = [
...@@ -640,6 +648,19 @@ class TestCreateTeamAPI(TeamAPITestCase): ...@@ -640,6 +648,19 @@ class TestCreateTeamAPI(TeamAPITestCase):
self.verify_expected_team_id(team, 'fully-specified-team') self.verify_expected_team_id(team, 'fully-specified-team')
del team['id'] del team['id']
self.assert_event_emitted(
'edx.team.created',
team_id=self._expected_team_id(team, 'fully-specified-team'),
course_id=unicode(self.test_course_1.id),
)
self.assert_event_emitted(
'edx.team.learner_added',
team_id=self._expected_team_id(team, 'fully-specified-team'),
course_id=unicode(self.test_course_1.id),
user_id=self.users[creator].id,
add_method='added_on_create'
)
# Remove date_created and discussion_topic_id because they change between test runs # Remove date_created and discussion_topic_id because they change between test runs
del team['date_created'] del team['date_created']
del team['discussion_topic_id'] del team['discussion_topic_id']
...@@ -1026,9 +1047,12 @@ class TestListMembershipAPI(TeamAPITestCase): ...@@ -1026,9 +1047,12 @@ class TestListMembershipAPI(TeamAPITestCase):
@ddt.ddt @ddt.ddt
class TestCreateMembershipAPI(TeamAPITestCase): class TestCreateMembershipAPI(EventTestMixin, TeamAPITestCase):
"""Test cases for the membership creation endpoint.""" """Test cases for the membership creation endpoint."""
def setUp(self): # pylint: disable=arguments-differ
super(TestCreateMembershipAPI, self).setUp('teams.views.tracker')
@ddt.data( @ddt.data(
(None, 401), (None, 401),
('student_inactive', 401), ('student_inactive', 401),
...@@ -1053,6 +1077,18 @@ class TestCreateMembershipAPI(TeamAPITestCase): ...@@ -1053,6 +1077,18 @@ class TestCreateMembershipAPI(TeamAPITestCase):
memberships = self.get_membership_list(200, {'team_id': self.solar_team.team_id}) memberships = self.get_membership_list(200, {'team_id': self.solar_team.team_id})
self.assertEqual(memberships['count'], 2) self.assertEqual(memberships['count'], 2)
add_method = 'joined_from_team_view' if user == 'student_enrolled_not_on_team' else 'added_by_another_user'
self.assert_event_emitted(
'edx.team.learner_added',
team_id=self.solar_team.team_id,
user_id=self.users['student_enrolled_not_on_team'].id,
course_id=unicode(self.solar_team.course_id),
add_method=add_method
)
else:
self.assert_no_events_were_emitted()
def test_no_username(self): def test_no_username(self):
response = self.post_create_membership(400, {'team_id': self.solar_team.team_id}) response = self.post_create_membership(400, {'team_id': self.solar_team.team_id})
self.assertIn('username', json.loads(response.content)['field_errors']) self.assertIn('username', json.loads(response.content)['field_errors'])
...@@ -1176,9 +1212,12 @@ class TestDetailMembershipAPI(TeamAPITestCase): ...@@ -1176,9 +1212,12 @@ class TestDetailMembershipAPI(TeamAPITestCase):
@ddt.ddt @ddt.ddt
class TestDeleteMembershipAPI(TeamAPITestCase): class TestDeleteMembershipAPI(EventTestMixin, TeamAPITestCase):
"""Test cases for the membership deletion endpoint.""" """Test cases for the membership deletion endpoint."""
def setUp(self): # pylint: disable=arguments-differ
super(TestDeleteMembershipAPI, self).setUp('teams.views.tracker')
@ddt.data( @ddt.data(
(None, 401), (None, 401),
('student_inactive', 401), ('student_inactive', 401),
...@@ -1198,6 +1237,18 @@ class TestDeleteMembershipAPI(TeamAPITestCase): ...@@ -1198,6 +1237,18 @@ class TestDeleteMembershipAPI(TeamAPITestCase):
user=user user=user
) )
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
)
else:
self.assert_no_events_were_emitted()
def test_bad_team(self): def test_bad_team(self):
self.delete_membership('no_such_team', self.users['student_enrolled'].username, 404) self.delete_membership('no_such_team', self.users['student_enrolled'].username, 404)
......
...@@ -34,6 +34,7 @@ from xmodule.modulestore.django import modulestore ...@@ -34,6 +34,7 @@ from xmodule.modulestore.django import modulestore
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from eventtracking import tracker
from courseware.courses import get_course_with_access, has_access from courseware.courses import get_course_with_access, has_access
from student.models import CourseEnrollment, CourseAccessRole from student.models import CourseEnrollment, CourseAccessRole
from student.roles import CourseStaffRole from student.roles import CourseStaffRole
...@@ -417,9 +418,22 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): ...@@ -417,9 +418,22 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
}, status=status.HTTP_400_BAD_REQUEST) }, status=status.HTTP_400_BAD_REQUEST)
else: else:
team = serializer.save() team = serializer.save()
tracker.emit('edx.team.created', {
'team_id': team.team_id,
'course_id': unicode(course_id)
})
if not team_administrator: if not team_administrator:
# Add the creating user to the team. # Add the creating user to the team.
team.add_user(request.user) team.add_user(request.user)
tracker.emit(
'edx.team.learner_added',
{
'team_id': team.team_id,
'user_id': request.user.id,
'course_id': unicode(team.course_id),
'add_method': 'added_on_create'
}
)
return Response(CourseTeamSerializer(team).data) return Response(CourseTeamSerializer(team).data)
def get_page(self): def get_page(self):
...@@ -974,6 +988,15 @@ class MembershipListView(ExpandableFieldViewMixin, GenericAPIView): ...@@ -974,6 +988,15 @@ class MembershipListView(ExpandableFieldViewMixin, GenericAPIView):
try: try:
membership = team.add_user(user) membership = team.add_user(user)
tracker.emit(
'edx.team.learner_added',
{
'team_id': team.team_id,
'user_id': user.id,
'course_id': unicode(team.course_id),
'add_method': 'joined_from_team_view' if user == request.user else 'added_by_another_user'
}
)
except AlreadyOnTeamInCourse: except AlreadyOnTeamInCourse:
return Response( return Response(
build_api_error( build_api_error(
...@@ -1100,6 +1123,15 @@ class MembershipDetailView(ExpandableFieldViewMixin, GenericAPIView): ...@@ -1100,6 +1123,15 @@ class MembershipDetailView(ExpandableFieldViewMixin, GenericAPIView):
if has_team_api_access(request.user, team.course_id, access_username=username): if has_team_api_access(request.user, team.course_id, access_username=username):
membership = self.get_membership(username, team) membership = self.get_membership(username, team)
membership.delete() membership.delete()
tracker.emit(
'edx.team.learner_removed',
{
'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'
}
)
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
else: else:
return Response(status=status.HTTP_404_NOT_FOUND) return Response(status=status.HTTP_404_NOT_FOUND)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment