Commit 0b78ffdf by Diana Huang

Merge pull request #9632 from edx/diana/teams-changed-event

Add events for tracking when teams are edited.
parents c45b8a17 8c1bf2cd
...@@ -99,18 +99,19 @@ def emit_field_changed_events(instance, user, db_table, excluded_fields=None, hi ...@@ -99,18 +99,19 @@ def emit_field_changed_events(instance, user, db_table, excluded_fields=None, hi
del instance._changed_fields del instance._changed_fields
def emit_setting_changed_event(user, db_table, setting_name, old_value, new_value): def truncate_fields(old_value, new_value):
"""Emits an event for a change in a setting. """
Truncates old_value and new_value for analytics event emission if necessary.
Args: Args:
user (User): the user that this setting is associated with. old_value(obj): the value before the change
db_table (str): the name of the table that we're modifying. new_value(obj): the new value being saved
setting_name (str): the name of the setting being changed.
old_value (object): the value before the change.
new_value (object): the new value being saved.
Returns: Returns:
None a dictionary with the following fields:
'old': the truncated old value
'new': the truncated new value
'truncated': the list of fields that have been truncated
""" """
# Compute the maximum value length so that two copies can fit into the maximum event size # Compute the maximum value length so that two copies can fit into the maximum event size
# in addition to all the other fields recorded. # in addition to all the other fields recorded.
...@@ -123,16 +124,32 @@ def emit_setting_changed_event(user, db_table, setting_name, old_value, new_valu ...@@ -123,16 +124,32 @@ def emit_setting_changed_event(user, db_table, setting_name, old_value, new_valu
truncated_values.append("old") truncated_values.append("old")
if new_was_truncated: if new_was_truncated:
truncated_values.append("new") truncated_values.append("new")
return {'old': serialized_old_value, 'new': serialized_new_value, 'truncated': truncated_values}
def emit_setting_changed_event(user, db_table, setting_name, old_value, new_value):
"""Emits an event for a change in a setting.
Args:
user (User): the user that this setting is associated with.
db_table (str): the name of the table that we're modifying.
setting_name (str): the name of the setting being changed.
old_value (object): the value before the change.
new_value (object): the new value being saved.
Returns:
None
"""
truncated_fields = truncate_fields(old_value, new_value)
truncated_fields['setting'] = setting_name
truncated_fields['user_id'] = user.id
truncated_fields['table'] = db_table
tracker.emit( tracker.emit(
USER_SETTINGS_CHANGED_EVENT_NAME, USER_SETTINGS_CHANGED_EVENT_NAME,
{ truncated_fields
"setting": setting_name,
"old": serialized_old_value,
"new": serialized_new_value,
"truncated": truncated_values,
"user_id": user.id,
"table": db_table,
}
) )
......
...@@ -1028,6 +1028,7 @@ class EditTeamTest(TeamFormActions): ...@@ -1028,6 +1028,7 @@ class EditTeamTest(TeamFormActions):
Then I should see the Edit Team button Then I should see the Edit Team button
And When I click edit team button And When I click edit team button
Then I should see the edit team page Then I should see the edit team page
And an analytics event should be fired
When I edit all the fields with appropriate data When I edit all the fields with appropriate data
And I click Update button And I click Update button
Then I should see the page for my team with updated data Then I should see the page for my team with updated data
...@@ -1041,6 +1042,54 @@ class EditTeamTest(TeamFormActions): ...@@ -1041,6 +1042,54 @@ class EditTeamTest(TeamFormActions):
self.verify_and_navigate_to_edit_team_page() self.verify_and_navigate_to_edit_team_page()
self.fill_create_or_edit_form() self.fill_create_or_edit_form()
expected_events = [
{
'event_type': 'edx.team.changed',
'event': {
'course_id': self.course_id,
'team_id': self.team['id'],
'field': 'country',
'old': 'AF',
'new': 'PK',
'truncated': [],
}
},
{
'event_type': 'edx.team.changed',
'event': {
'course_id': self.course_id,
'team_id': self.team['id'],
'field': 'name',
'old': self.team['name'],
'new': self.TEAMS_NAME,
'truncated': [],
}
},
{
'event_type': 'edx.team.changed',
'event': {
'course_id': self.course_id,
'team_id': self.team['id'],
'field': 'language',
'old': 'aa',
'new': 'en',
'truncated': [],
}
},
{
'event_type': 'edx.team.changed',
'event': {
'course_id': self.course_id,
'team_id': self.team['id'],
'field': 'description',
'old': self.team['description'],
'new': self.TEAM_DESCRIPTION,
'truncated': [],
}
},
]
with self.assert_events_match_during(event_filter=self.only_team_events, expected_events=expected_events):
self.create_or_edit_team_page.submit_form() self.create_or_edit_team_page.submit_form()
self.team_page.wait_for_page() self.team_page.wait_for_page()
......
...@@ -4,6 +4,7 @@ from datetime import datetime ...@@ -4,6 +4,7 @@ from datetime import datetime
from uuid import uuid4 from uuid import uuid4
import pytz import pytz
from datetime import datetime from datetime import datetime
from model_utils import FieldTracker
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -89,6 +90,11 @@ class CourseTeam(models.Model): ...@@ -89,6 +90,11 @@ class CourseTeam(models.Model):
users = models.ManyToManyField(User, db_index=True, related_name='teams', through='CourseTeamMembership') users = models.ManyToManyField(User, db_index=True, related_name='teams', through='CourseTeamMembership')
team_size = models.IntegerField(default=0, db_index=True) # indexed for ordering team_size = models.IntegerField(default=0, db_index=True) # indexed for ordering
field_tracker = FieldTracker()
# Don't emit changed events when these fields change.
FIELD_BLACKLIST = ['last_activity_at', 'team_size']
@classmethod @classmethod
def create(cls, name, course_id, description, topic_id=None, country=None, language=None): def create(cls, name, course_id, description, topic_id=None, country=None, language=None):
"""Create a complete CourseTeam object. """Create a complete CourseTeam object.
......
...@@ -755,9 +755,12 @@ class TestDetailTeamAPI(TeamAPITestCase): ...@@ -755,9 +755,12 @@ class TestDetailTeamAPI(TeamAPITestCase):
@ddt.ddt @ddt.ddt
class TestUpdateTeamAPI(TeamAPITestCase): class TestUpdateTeamAPI(EventTestMixin, TeamAPITestCase):
"""Test cases for the team update endpoint.""" """Test cases for the team update endpoint."""
def setUp(self): # pylint: disable=arguments-differ
super(TestUpdateTeamAPI, self).setUp('teams.views.tracker')
@ddt.data( @ddt.data(
(None, 401), (None, 401),
('student_inactive', 401), ('student_inactive', 401),
...@@ -769,9 +772,19 @@ class TestUpdateTeamAPI(TeamAPITestCase): ...@@ -769,9 +772,19 @@ class TestUpdateTeamAPI(TeamAPITestCase):
) )
@ddt.unpack @ddt.unpack
def test_access(self, user, status): def test_access(self, user, status):
prev_name = self.solar_team.name
team = self.patch_team_detail(self.solar_team.team_id, status, {'name': 'foo'}, user=user) team = self.patch_team_detail(self.solar_team.team_id, status, {'name': 'foo'}, user=user)
if status == 200: if status == 200:
self.assertEquals(team['name'], 'foo') self.assertEquals(team['name'], 'foo')
self.assert_event_emitted(
'edx.team.changed',
team_id=self.solar_team.team_id,
course_id=unicode(self.solar_team.course_id),
truncated=[],
field='name',
old=prev_name,
new='foo'
)
@ddt.data( @ddt.data(
(None, 401), (None, 401),
...@@ -799,8 +812,22 @@ class TestUpdateTeamAPI(TeamAPITestCase): ...@@ -799,8 +812,22 @@ class TestUpdateTeamAPI(TeamAPITestCase):
@ddt.data(('country', 'US'), ('language', 'en'), ('foo', 'bar')) @ddt.data(('country', 'US'), ('language', 'en'), ('foo', 'bar'))
@ddt.unpack @ddt.unpack
def test_good_requests(self, key, value): def test_good_requests(self, key, value):
if hasattr(self.solar_team, key):
prev_value = getattr(self.solar_team, key)
self.patch_team_detail(self.solar_team.team_id, 200, {key: value}, user='staff') self.patch_team_detail(self.solar_team.team_id, 200, {key: value}, user='staff')
if hasattr(self.solar_team, key):
self.assert_event_emitted(
'edx.team.changed',
team_id=self.solar_team.team_id,
course_id=unicode(self.solar_team.course_id),
truncated=[],
field=key,
old=prev_value,
new=value
)
def test_does_not_exist(self): def test_does_not_exist(self):
self.patch_team_detail('no_such_team', 404, user='staff') self.patch_team_detail('no_such_team', 404, user='staff')
......
...@@ -16,6 +16,8 @@ from rest_framework.authentication import ( ...@@ -16,6 +16,8 @@ from rest_framework.authentication import (
from rest_framework import status from rest_framework import status
from rest_framework import permissions from rest_framework import permissions
from django.db.models import Count from django.db.models import Count
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django_countries import countries from django_countries import countries
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
...@@ -40,6 +42,7 @@ from student.models import CourseEnrollment, CourseAccessRole ...@@ -40,6 +42,7 @@ from student.models import CourseEnrollment, CourseAccessRole
from student.roles import CourseStaffRole from student.roles import CourseStaffRole
from django_comment_client.utils import has_discussion_privileges from django_comment_client.utils import has_discussion_privileges
from teams import is_feature_enabled from teams import is_feature_enabled
from util.model_utils import truncate_fields
from .models import CourseTeam, CourseTeamMembership from .models import CourseTeam, CourseTeamMembership
from .serializers import ( from .serializers import (
CourseTeamSerializer, CourseTeamSerializer,
...@@ -59,6 +62,25 @@ TOPICS_PER_PAGE = 12 ...@@ -59,6 +62,25 @@ TOPICS_PER_PAGE = 12
MAXIMUM_SEARCH_SIZE = 100000 MAXIMUM_SEARCH_SIZE = 100000
@receiver(post_save, sender=CourseTeam)
def team_post_save_callback(sender, instance, **kwargs): # pylint: disable=unused-argument
""" Emits signal after the team is saved. """
changed_fields = instance.field_tracker.changed()
# Don't emit events when we are first creating the team.
if not kwargs['created']:
for field in changed_fields:
if field not in instance.FIELD_BLACKLIST:
truncated_fields = truncate_fields(unicode(changed_fields[field]), unicode(getattr(instance, field)))
truncated_fields['team_id'] = instance.team_id
truncated_fields['course_id'] = unicode(instance.course_id)
truncated_fields['field'] = field
tracker.emit(
'edx.team.changed',
truncated_fields
)
class TeamsDashboardView(View): class TeamsDashboardView(View):
""" """
View methods related to the teams dashboard. View methods related to the teams dashboard.
......
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