Commit d1d44e7b by Daniel Friedman

Merge pull request #9611 from edx/dan-f/add-team-page-viewed-events

Add page viewed events to teams app.
parents 83e6da06 3b957a7e
...@@ -7,7 +7,6 @@ import time ...@@ -7,7 +7,6 @@ import time
from dateutil.parser import parse from dateutil.parser import parse
import ddt import ddt
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 unittest import skip from unittest import skip
...@@ -286,6 +285,14 @@ class MyTeamsTest(TeamsTabBase): ...@@ -286,6 +285,14 @@ class MyTeamsTest(TeamsTabBase):
self.topic = {u"name": u"Example Topic", u"id": "example_topic", u"description": "Description"} self.topic = {u"name": u"Example Topic", u"id": "example_topic", u"description": "Description"}
self.set_team_configuration({'course_id': self.course_id, 'max_team_size': 10, 'topics': [self.topic]}) self.set_team_configuration({'course_id': self.course_id, 'max_team_size': 10, 'topics': [self.topic]})
self.my_teams_page = MyTeamsPage(self.browser, self.course_id) self.my_teams_page = MyTeamsPage(self.browser, self.course_id)
self.page_viewed_event = {
'event_type': 'edx.team.page_viewed',
'event': {
'page_name': 'my-teams',
'topic_id': None,
'team_id': None
}
}
def test_not_member_of_any_teams(self): def test_not_member_of_any_teams(self):
""" """
...@@ -295,7 +302,8 @@ class MyTeamsTest(TeamsTabBase): ...@@ -295,7 +302,8 @@ class MyTeamsTest(TeamsTabBase):
And I should see no teams And I should see no teams
And I should see a message that I belong to no teams. And I should see a message that I belong to no teams.
""" """
self.my_teams_page.visit() with self.assert_events_match_during(self.only_team_events, expected_events=[self.page_viewed_event]):
self.my_teams_page.visit()
self.assertEqual(len(self.my_teams_page.team_cards), 0, msg='Expected to see no team cards') self.assertEqual(len(self.my_teams_page.team_cards), 0, msg='Expected to see no team cards')
self.assertEqual( self.assertEqual(
self.my_teams_page.q(css='.page-content-main').text, self.my_teams_page.q(css='.page-content-main').text,
...@@ -313,7 +321,8 @@ class MyTeamsTest(TeamsTabBase): ...@@ -313,7 +321,8 @@ class MyTeamsTest(TeamsTabBase):
""" """
teams = self.create_teams(self.topic, 1) teams = self.create_teams(self.topic, 1)
self.create_membership(self.user_info['username'], teams[0]['id']) self.create_membership(self.user_info['username'], teams[0]['id'])
self.my_teams_page.visit() with self.assert_events_match_during(self.only_team_events, expected_events=[self.page_viewed_event]):
self.my_teams_page.visit()
self.verify_teams(self.my_teams_page, teams) self.verify_teams(self.my_teams_page, teams)
...@@ -511,6 +520,28 @@ class BrowseTopicsTest(TeamsTabBase): ...@@ -511,6 +520,28 @@ class BrowseTopicsTest(TeamsTabBase):
self.assertEqual(browse_teams_page.header_name, 'Example Topic') self.assertEqual(browse_teams_page.header_name, 'Example Topic')
self.assertEqual(browse_teams_page.header_description, 'Description') self.assertEqual(browse_teams_page.header_description, 'Description')
def test_page_viewed_event(self):
"""
Scenario: Visiting the browse topics page should fire a page viewed event.
Given I am enrolled in a course with a team configuration and a topic
When I visit the browse topics page
Then my browser should post a page viewed event
"""
topic = {u"name": u"Example Topic", u"id": u"example_topic", u"description": "Description"}
self.set_team_configuration(
{u"max_team_size": 1, u"topics": [topic]}
)
events = [{
'event_type': 'edx.team.page_viewed',
'event': {
'page_name': 'browse',
'topic_id': None,
'team_id': None
}
}]
with self.assert_events_match_during(self.only_team_events, expected_events=events):
self.topics_page.visit()
@attr('shard_5') @attr('shard_5')
@ddt.ddt @ddt.ddt
...@@ -752,6 +783,7 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase): ...@@ -752,6 +783,7 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
Then I should see the search result page Then I should see the search result page
And the search header should be shown And the search header should be shown
And 0 results should be shown And 0 results should be shown
And my browser should fire a page viewed event for the search page
""" """
# Note: all searches will return 0 results with the mock search server # Note: all searches will return 0 results with the mock search server
# used by Bok Choy. # used by Bok Choy.
...@@ -766,12 +798,38 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase): ...@@ -766,12 +798,38 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
'topic_id': self.topic['id'], 'topic_id': self.topic['id'],
'number_of_results': 0 'number_of_results': 0
} }
}, {
'event_type': 'edx.team.page_viewed',
'event': {
'page_name': 'search-teams',
'topic_id': self.topic['id'],
'team_id': None
}
}] }]
with self.assert_events_match_during(self.only_team_events, expected_events=events): with self.assert_events_match_during(self.only_team_events, expected_events=events):
search_results_page = self.browse_teams_page.search(search_text) search_results_page = self.browse_teams_page.search(search_text)
self.verify_search_header(search_results_page, search_text) self.verify_search_header(search_results_page, search_text)
self.assertTrue(search_results_page.get_pagination_header_text().startswith('Showing 0 out of 0 total')) self.assertTrue(search_results_page.get_pagination_header_text().startswith('Showing 0 out of 0 total'))
def test_page_viewed_event(self):
"""
Scenario: Visiting the browse page should fire a page viewed event.
Given I am enrolled in a course with a team configuration and a topic
When I visit the Teams page
Then my browser should post a page viewed event for the teams page
"""
self.create_teams(self.topic, 5)
events = [{
'event_type': 'edx.team.page_viewed',
'event': {
'page_name': 'single-topic',
'topic_id': self.topic['id'],
'team_id': None
}
}]
with self.assert_events_match_during(self.only_team_events, expected_events=events):
self.browse_teams_page.visit()
@attr('shard_5') @attr('shard_5')
class TeamFormActions(TeamsTabBase): class TeamFormActions(TeamsTabBase):
...@@ -1024,6 +1082,24 @@ class CreateTeamTest(TeamFormActions): ...@@ -1024,6 +1082,24 @@ class CreateTeamTest(TeamFormActions):
self.verify_my_team_count(0) self.verify_my_team_count(0)
def test_page_viewed_event(self):
"""
Scenario: Visiting the create team page should fire a page viewed event.
Given I am enrolled in a course with a team configuration and a topic
When I visit the create team page
Then my browser should post a page viewed event
"""
events = [{
'event_type': 'edx.team.page_viewed',
'event': {
'page_name': 'new-team',
'topic_id': self.topic['id'],
'team_id': None
}
}]
with self.assert_events_match_during(self.only_team_events, expected_events=events):
self.verify_and_navigate_to_create_team_page()
@ddt.ddt @ddt.ddt
class EditTeamTest(TeamFormActions): class EditTeamTest(TeamFormActions):
...@@ -1224,6 +1300,24 @@ class EditTeamTest(TeamFormActions): ...@@ -1224,6 +1300,24 @@ class EditTeamTest(TeamFormActions):
language='English' language='English'
) )
def test_page_viewed_event(self):
"""
Scenario: Visiting the edit team page should fire a page viewed event.
Given I am enrolled in a course with a team configuration and a topic
When I visit the edit team page
Then my browser should post a page viewed event
"""
events = [{
'event_type': 'edx.team.page_viewed',
'event': {
'page_name': 'edit-team',
'topic_id': self.topic['id'],
'team_id': self.team['id']
}
}]
with self.assert_events_match_during(self.only_team_events, expected_events=events):
self.verify_and_navigate_to_edit_team_page()
@attr('shard_5') @attr('shard_5')
@ddt.ddt @ddt.ddt
...@@ -1554,3 +1648,22 @@ class TeamPageTest(TeamsTabBase): ...@@ -1554,3 +1648,22 @@ class TeamPageTest(TeamsTabBase):
# Verify that if one switches to "My Team" without reloading the page, the old team no longer shows. # Verify that if one switches to "My Team" without reloading the page, the old team no longer shows.
self.teams_page.click_all_topics() self.teams_page.click_all_topics()
self.verify_my_team_count(0) self.verify_my_team_count(0)
def test_page_viewed_event(self):
"""
Scenario: Visiting the team profile page should fire a page viewed event.
Given I am enrolled in a course with a team configuration and a topic
When I visit the team profile page
Then my browser should post a page viewed event
"""
self._set_team_configuration_and_membership()
events = [{
'event_type': 'edx.team.page_viewed',
'event': {
'page_name': 'single-team',
'topic_id': self.topic['id'],
'team_id': self.teams[0]['id']
}
}]
with self.assert_events_match_during(self.only_team_events, expected_events=events):
self.team_page.visit()
define([ define([
'jquery', 'jquery',
'backbone', 'backbone',
'logger',
'common/js/spec_helpers/ajax_helpers', 'common/js/spec_helpers/ajax_helpers',
'common/js/spec_helpers/spec_helpers',
'teams/js/views/teams_tab', 'teams/js/views/teams_tab',
'teams/js/spec_helpers/team_spec_helpers' 'teams/js/spec_helpers/team_spec_helpers'
], function ($, Backbone, AjaxHelpers, TeamsTabView, TeamSpecHelpers) { ], function ($, Backbone, Logger, AjaxHelpers, SpecHelpers, TeamsTabView, TeamSpecHelpers) {
'use strict'; 'use strict';
describe('TeamsTab', function () { describe('TeamsTab', function () {
...@@ -34,14 +36,34 @@ define([ ...@@ -34,14 +36,34 @@ define([
return teamsTabView; return teamsTabView;
}; };
/**
* Filters out all team events from a list of requests.
*/
var removeTeamEvents = function (requests) {
return requests.filter(function (request) {
if (request.requestBody && request.requestBody.startsWith('event_type=edx.team')) {
return false;
} else {
return true;
}
});
};
beforeEach(function () { beforeEach(function () {
setFixtures('<div class="teams-content"></div>'); setFixtures('<div class="teams-content"></div>');
spyOn($.fn, 'focus'); spyOn($.fn, 'focus');
spyOn(Logger, 'log');
}); });
afterEach(Backbone.history.stop); afterEach(Backbone.history.stop);
describe('Navigation', function () { describe('Navigation', function () {
it('does not interfere with anchor links to #content', function () {
var teamsTabView = createTeamsTabView();
teamsTabView.router.navigate('#content', {trigger: true});
expect(teamsTabView.$('.warning')).toHaveClass('is-hidden');
});
it('displays and focuses an error message when trying to navigate to a nonexistent page', function () { it('displays and focuses an error message when trying to navigate to a nonexistent page', function () {
var teamsTabView = createTeamsTabView(); var teamsTabView = createTeamsTabView();
teamsTabView.router.navigate('no_such_page', {trigger: true}); teamsTabView.router.navigate('no_such_page', {trigger: true});
...@@ -49,12 +71,6 @@ define([ ...@@ -49,12 +71,6 @@ define([
expectFocus(teamsTabView.$('.warning')); expectFocus(teamsTabView.$('.warning'));
}); });
it('does not interfere with anchor links to #content', function () {
var teamsTabView = createTeamsTabView();
teamsTabView.router.navigate('#content', {trigger: true});
expect(teamsTabView.$('.warning')).toHaveClass('is-hidden');
});
it('displays and focuses an error message when trying to navigate to a nonexistent topic', function () { it('displays and focuses an error message when trying to navigate to a nonexistent topic', function () {
var requests = AjaxHelpers.requests(this), var requests = AjaxHelpers.requests(this),
teamsTabView = createTeamsTabView(); teamsTabView = createTeamsTabView();
...@@ -94,6 +110,64 @@ define([ ...@@ -94,6 +110,64 @@ define([
}); });
}); });
describe('Analytics Events', function () {
SpecHelpers.withData({
'fires a page view event for the topic page': [
'topics/' + TeamSpecHelpers.testTopicID,
{
page_name: 'single-topic',
topic_id: TeamSpecHelpers.testTopicID,
team_id: null
}
],
'fires a page view event for the team page': [
'teams/' + TeamSpecHelpers.testTopicID + '/test_team_id',
{
page_name: 'single-team',
topic_id: TeamSpecHelpers.testTopicID,
team_id: 'test_team_id'
}
],
'fires a page view event for the search team page': [
'topics/' + TeamSpecHelpers.testTopicID + '/search',
{
page_name: 'search-teams',
topic_id: TeamSpecHelpers.testTopicID,
team_id: null
}
],
'fires a page view event for the new team page': [
'topics/' + TeamSpecHelpers.testTopicID + '/create-team',
{
page_name: 'new-team',
topic_id: TeamSpecHelpers.testTopicID,
team_id: null
}
],
'fires a page view event for the edit team page': [
'topics/' + TeamSpecHelpers.testTopicID + '/' + 'test_team_id/edit-team',
{
page_name: 'edit-team',
topic_id: TeamSpecHelpers.testTopicID,
team_id: 'test_team_id'
}
]
}, function (url, expectedEvent) {
if (url.indexOf('search') !== -1
|| url.indexOf('create-team') !== -1
|| url.indexOf('edit-team') !== -1) {
debugger;
}
var requests = AjaxHelpers.requests(this),
teamsTabView = createTeamsTabView();
teamsTabView.router.navigate(url, {trigger: true});
if (requests.length) {
AjaxHelpers.respondWithJson(requests, {});
}
expect(Logger.log).toHaveBeenCalledWith('edx.team.page_viewed', expectedEvent);
});
});
describe('Discussion privileges', function () { describe('Discussion privileges', function () {
it('allows privileged access to any team', function () { it('allows privileged access to any team', function () {
var teamsTabView = createTeamsTabView({ var teamsTabView = createTeamsTabView({
...@@ -206,7 +280,7 @@ define([ ...@@ -206,7 +280,7 @@ define([
// Navigate back to the teams list // Navigate back to the teams list
teamsTabView.$('.breadcrumbs a').last().click(); teamsTabView.$('.breadcrumbs a').last().click();
verifyTeamsRequest(requests, { verifyTeamsRequest(removeTeamEvents(requests), {
order_by: 'last_activity_at', order_by: 'last_activity_at',
text_search: '' text_search: ''
}); });
......
/**
* Utility methods for emitting teams events. See the event spec:
* https://openedx.atlassian.net/wiki/display/AN/Teams+Feature+Event+Design
*/
;(function (define) {
'use strict';
define([
'logger'
], function (Logger) {
var TeamAnalytics = {
emitPageViewed: function (page_name, topic_id, team_id) {
Logger.log('edx.team.page_viewed', {
page_name: page_name,
topic_id: topic_id,
team_id: team_id
});
}
};
return TeamAnalytics;
});
}).call(this, define || RequireJS.define);
...@@ -7,12 +7,13 @@ ...@@ -7,12 +7,13 @@
'common/js/components/views/search_field', 'common/js/components/views/search_field',
'js/components/header/views/header', 'js/components/header/views/header',
'js/components/header/models/header', 'js/components/header/models/header',
'js/components/tabbed/views/tabbed_view',
'teams/js/models/topic', 'teams/js/models/topic',
'teams/js/collections/topic', 'teams/js/collections/topic',
'teams/js/models/team', 'teams/js/models/team',
'teams/js/collections/team', 'teams/js/collections/team',
'teams/js/collections/team_membership', 'teams/js/collections/team_membership',
'teams/js/utils/team_analytics',
'teams/js/views/teams_tabbed_view',
'teams/js/views/topics', 'teams/js/views/topics',
'teams/js/views/team_profile', 'teams/js/views/team_profile',
'teams/js/views/my_teams', 'teams/js/views/my_teams',
...@@ -21,9 +22,9 @@ ...@@ -21,9 +22,9 @@
'teams/js/views/team_profile_header_actions', 'teams/js/views/team_profile_header_actions',
'teams/js/views/team_utils', 'teams/js/views/team_utils',
'text!teams/templates/teams_tab.underscore'], 'text!teams/templates/teams_tab.underscore'],
function (Backbone, _, gettext, SearchFieldView, HeaderView, HeaderModel, TabbedView, function (Backbone, _, gettext, SearchFieldView, HeaderView, HeaderModel,
TopicModel, TopicCollection, TeamModel, TeamCollection, TeamMembershipCollection, TopicModel, TopicCollection, TeamModel, TeamCollection, TeamMembershipCollection, TeamAnalytics,
TopicsView, TeamProfileView, MyTeamsView, TopicTeamsView, TeamEditView, TeamsTabbedView, TopicsView, TeamProfileView, MyTeamsView, TopicTeamsView, TeamEditView,
TeamProfileHeaderActionsView, TeamUtils, teamsTemplate) { TeamProfileHeaderActionsView, TeamUtils, teamsTemplate) {
var TeamsHeaderModel = HeaderModel.extend({ var TeamsHeaderModel = HeaderModel.extend({
initialize: function () { initialize: function () {
...@@ -118,7 +119,7 @@ ...@@ -118,7 +119,7 @@
this.mainView = this.tabbedView = this.createViewWithHeader({ this.mainView = this.tabbedView = this.createViewWithHeader({
title: gettext("Teams"), title: gettext("Teams"),
description: gettext("See all teams in your course, organized by topic. Join a team to collaborate with other learners who are interested in the same topic as you are."), description: gettext("See all teams in your course, organized by topic. Join a team to collaborate with other learners who are interested in the same topic as you are."),
mainView: new TabbedView({ mainView: new TeamsTabbedView({
tabs: [{ tabs: [{
title: gettext('My Team'), title: gettext('My Team'),
url: 'my-teams', url: 'my-teams',
...@@ -180,6 +181,7 @@ ...@@ -180,6 +181,7 @@
this.getTeamsView(topicID).done(function (teamsView) { this.getTeamsView(topicID).done(function (teamsView) {
self.teamsView = self.mainView = teamsView; self.teamsView = self.mainView = teamsView;
self.render(); self.render();
TeamAnalytics.emitPageViewed('single-topic', topicID, null);
}); });
}, },
...@@ -205,6 +207,7 @@ ...@@ -205,6 +207,7 @@
showSortControls: false showSortControls: false
}); });
view.render(); view.render();
TeamAnalytics.emitPageViewed('search-teams', topicID, null);
}); });
} }
}, },
...@@ -227,6 +230,7 @@ ...@@ -227,6 +230,7 @@
}) })
}); });
view.render(); view.render();
TeamAnalytics.emitPageViewed('new-team', topicID, null);
}); });
}, },
...@@ -254,6 +258,7 @@ ...@@ -254,6 +258,7 @@
}); });
self.mainView = editViewWithHeader; self.mainView = editViewWithHeader;
self.render(); self.render();
TeamAnalytics.emitPageViewed('edit-team', topicID, teamID);
}); });
}); });
}, },
...@@ -340,6 +345,7 @@ ...@@ -340,6 +345,7 @@
this.getBrowseTeamView(topicID, teamID).done(function (browseTeamView) { this.getBrowseTeamView(topicID, teamID).done(function (browseTeamView) {
self.mainView = browseTeamView; self.mainView = browseTeamView;
self.render(); self.render();
TeamAnalytics.emitPageViewed('single-team', topicID, teamID);
}); });
}, },
......
/**
* A custom TabbedView for Teams.
*/
;(function (define) {
'use strict';
define([
'js/components/tabbed/views/tabbed_view',
'teams/js/utils/team_analytics'
], function (TabbedView, TeamAnalytics) {
var TeamsTabbedView = TabbedView.extend({
/**
* Overrides TabbedView.prototype.setActiveTab in order to
* log page viewed events.
*/
setActiveTab: function (index) {
TabbedView.prototype.setActiveTab.call(this, index);
TeamAnalytics.emitPageViewed(this.getTabMeta(index).tab.url, null, null);
}
});
return TeamsTabbedView;
});
}).call(this, define || RequireJS.define);
...@@ -59,16 +59,10 @@ ...@@ -59,16 +59,10 @@
}, },
setActiveTab: function (index) { setActiveTab: function (index) {
var tab, tabEl, view; var tabMeta = this.getTabMeta(index),
if (typeof index === 'string') { tab = tabMeta.tab,
tab = this.urlMap[index]; tabEl = tabMeta.element,
tabEl = this.$('a[data-url='+index+']'); view = tab.view;
}
else {
tab = this.tabs[index];
tabEl = this.$('a[data-index='+index+']');
}
view = tab.view;
this.$('a.is-active').removeClass('is-active').attr('aria-selected', 'false'); this.$('a.is-active').removeClass('is-active').attr('aria-selected', 'false');
tabEl.addClass('is-active').attr('aria-selected', 'true'); tabEl.addClass('is-active').attr('aria-selected', 'true');
view.setElement(this.$('.page-content-main')).render(); view.setElement(this.$('.page-content-main')).render();
...@@ -81,6 +75,22 @@ ...@@ -81,6 +75,22 @@
switchTab: function (event) { switchTab: function (event) {
event.preventDefault(); event.preventDefault();
this.setActiveTab($(event.currentTarget).data('index')); this.setActiveTab($(event.currentTarget).data('index'));
},
/**
* Get the tab by name or index. Returns an object
* encapsulating the tab object and its element.
*/
getTabMeta: function (tabNameOrIndex) {
var tab, element;
if (typeof tabNameOrIndex === 'string') {
tab = this.urlMap[tabNameOrIndex];
element = this.$('a[data-url='+tabNameOrIndex+']');
} else {
tab = this.tabs[tabNameOrIndex];
element = this.$('a[data-index='+tabNameOrIndex+']');
}
return {'tab': tab, 'element': element};
} }
}); });
return TabbedView; return TabbedView;
......
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