Commit 8c1bf2cd by Diana Huang

Add events for tracking when teams are edited.

TNL-3190
parent deacfebd
......@@ -99,18 +99,19 @@ def emit_field_changed_events(instance, user, db_table, excluded_fields=None, hi
del instance._changed_fields
def emit_setting_changed_event(user, db_table, setting_name, old_value, new_value):
"""Emits an event for a change in a setting.
def truncate_fields(old_value, new_value):
"""
Truncates old_value and new_value for analytics event emission if necessary.
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.
old_value(obj): the value before the change
new_value(obj): the new value being saved
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
# 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
truncated_values.append("old")
if new_was_truncated:
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(
USER_SETTINGS_CHANGED_EVENT_NAME,
{
"setting": setting_name,
"old": serialized_old_value,
"new": serialized_new_value,
"truncated": truncated_values,
"user_id": user.id,
"table": db_table,
}
truncated_fields
)
......
......@@ -1017,6 +1017,7 @@ class EditTeamTest(TeamFormActions):
Then I should see the Edit Team button
And When I click edit team button
Then I should see the edit team page
And an analytics event should be fired
When I edit all the fields with appropriate data
And I click Update button
Then I should see the page for my team with updated data
......@@ -1030,7 +1031,55 @@ class EditTeamTest(TeamFormActions):
self.verify_and_navigate_to_edit_team_page()
self.fill_create_or_edit_form()
self.create_or_edit_team_page.submit_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.team_page.wait_for_page()
......
......@@ -4,6 +4,7 @@ from datetime import datetime
from uuid import uuid4
import pytz
from datetime import datetime
from model_utils import FieldTracker
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User
......@@ -89,6 +90,11 @@ class CourseTeam(models.Model):
users = models.ManyToManyField(User, db_index=True, related_name='teams', through='CourseTeamMembership')
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
def create(cls, name, course_id, description, topic_id=None, country=None, language=None):
"""Create a complete CourseTeam object.
......
......@@ -743,9 +743,12 @@ class TestDetailTeamAPI(TeamAPITestCase):
@ddt.ddt
class TestUpdateTeamAPI(TeamAPITestCase):
class TestUpdateTeamAPI(EventTestMixin, TeamAPITestCase):
"""Test cases for the team update endpoint."""
def setUp(self): # pylint: disable=arguments-differ
super(TestUpdateTeamAPI, self).setUp('teams.views.tracker')
@ddt.data(
(None, 401),
('student_inactive', 401),
......@@ -757,9 +760,19 @@ class TestUpdateTeamAPI(TeamAPITestCase):
)
@ddt.unpack
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)
if status == 200:
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(
(None, 401),
......@@ -787,8 +800,22 @@ class TestUpdateTeamAPI(TeamAPITestCase):
@ddt.data(('country', 'US'), ('language', 'en'), ('foo', 'bar'))
@ddt.unpack
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')
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):
self.patch_team_detail('no_such_team', 404, user='staff')
......
......@@ -16,6 +16,8 @@ from rest_framework.authentication import (
from rest_framework import status
from rest_framework import permissions
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_countries import countries
from django.utils.translation import ugettext as _
......@@ -40,6 +42,7 @@ from student.models import CourseEnrollment, CourseAccessRole
from student.roles import CourseStaffRole
from django_comment_client.utils import has_discussion_privileges
from teams import is_feature_enabled
from util.model_utils import truncate_fields
from .models import CourseTeam, CourseTeamMembership
from .serializers import (
CourseTeamSerializer,
......@@ -59,6 +62,25 @@ TOPICS_PER_PAGE = 12
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):
"""
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