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
from dateutil.parser import parse
import ddt
from flaky import flaky
from nose.plugins.attrib import attr
from uuid import uuid4
from unittest import skip
......@@ -286,6 +285,14 @@ class MyTeamsTest(TeamsTabBase):
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.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):
"""
......@@ -295,7 +302,8 @@ class MyTeamsTest(TeamsTabBase):
And I should see 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(
self.my_teams_page.q(css='.page-content-main').text,
......@@ -313,7 +321,8 @@ class MyTeamsTest(TeamsTabBase):
"""
teams = self.create_teams(self.topic, 1)
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)
......@@ -511,6 +520,28 @@ class BrowseTopicsTest(TeamsTabBase):
self.assertEqual(browse_teams_page.header_name, 'Example Topic')
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')
@ddt.ddt
......@@ -752,6 +783,7 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
Then I should see the search result page
And the search header 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
# used by Bok Choy.
......@@ -766,12 +798,38 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
'topic_id': self.topic['id'],
'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):
search_results_page = self.browse_teams_page.search(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'))
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')
class TeamFormActions(TeamsTabBase):
......@@ -1024,6 +1082,24 @@ class CreateTeamTest(TeamFormActions):
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
class EditTeamTest(TeamFormActions):
......@@ -1224,6 +1300,24 @@ class EditTeamTest(TeamFormActions):
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')
@ddt.ddt
......@@ -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.
self.teams_page.click_all_topics()
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([
'jquery',
'backbone',
'logger',
'common/js/spec_helpers/ajax_helpers',
'common/js/spec_helpers/spec_helpers',
'teams/js/views/teams_tab',
'teams/js/spec_helpers/team_spec_helpers'
], function ($, Backbone, AjaxHelpers, TeamsTabView, TeamSpecHelpers) {
], function ($, Backbone, Logger, AjaxHelpers, SpecHelpers, TeamsTabView, TeamSpecHelpers) {
'use strict';
describe('TeamsTab', function () {
......@@ -34,14 +36,34 @@ define([
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 () {
setFixtures('<div class="teams-content"></div>');
spyOn($.fn, 'focus');
spyOn(Logger, 'log');
});
afterEach(Backbone.history.stop);
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 () {
var teamsTabView = createTeamsTabView();
teamsTabView.router.navigate('no_such_page', {trigger: true});
......@@ -49,12 +71,6 @@ define([
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 () {
var requests = AjaxHelpers.requests(this),
teamsTabView = createTeamsTabView();
......@@ -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 () {
it('allows privileged access to any team', function () {
var teamsTabView = createTeamsTabView({
......@@ -206,7 +280,7 @@ define([
// Navigate back to the teams list
teamsTabView.$('.breadcrumbs a').last().click();
verifyTeamsRequest(requests, {
verifyTeamsRequest(removeTeamEvents(requests), {
order_by: 'last_activity_at',
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 @@
'common/js/components/views/search_field',
'js/components/header/views/header',
'js/components/header/models/header',
'js/components/tabbed/views/tabbed_view',
'teams/js/models/topic',
'teams/js/collections/topic',
'teams/js/models/team',
'teams/js/collections/team',
'teams/js/collections/team_membership',
'teams/js/utils/team_analytics',
'teams/js/views/teams_tabbed_view',
'teams/js/views/topics',
'teams/js/views/team_profile',
'teams/js/views/my_teams',
......@@ -21,9 +22,9 @@
'teams/js/views/team_profile_header_actions',
'teams/js/views/team_utils',
'text!teams/templates/teams_tab.underscore'],
function (Backbone, _, gettext, SearchFieldView, HeaderView, HeaderModel, TabbedView,
TopicModel, TopicCollection, TeamModel, TeamCollection, TeamMembershipCollection,
TopicsView, TeamProfileView, MyTeamsView, TopicTeamsView, TeamEditView,
function (Backbone, _, gettext, SearchFieldView, HeaderView, HeaderModel,
TopicModel, TopicCollection, TeamModel, TeamCollection, TeamMembershipCollection, TeamAnalytics,
TeamsTabbedView, TopicsView, TeamProfileView, MyTeamsView, TopicTeamsView, TeamEditView,
TeamProfileHeaderActionsView, TeamUtils, teamsTemplate) {
var TeamsHeaderModel = HeaderModel.extend({
initialize: function () {
......@@ -118,7 +119,7 @@
this.mainView = this.tabbedView = this.createViewWithHeader({
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."),
mainView: new TabbedView({
mainView: new TeamsTabbedView({
tabs: [{
title: gettext('My Team'),
url: 'my-teams',
......@@ -180,6 +181,7 @@
this.getTeamsView(topicID).done(function (teamsView) {
self.teamsView = self.mainView = teamsView;
self.render();
TeamAnalytics.emitPageViewed('single-topic', topicID, null);
});
},
......@@ -205,6 +207,7 @@
showSortControls: false
});
view.render();
TeamAnalytics.emitPageViewed('search-teams', topicID, null);
});
}
},
......@@ -227,6 +230,7 @@
})
});
view.render();
TeamAnalytics.emitPageViewed('new-team', topicID, null);
});
},
......@@ -254,6 +258,7 @@
});
self.mainView = editViewWithHeader;
self.render();
TeamAnalytics.emitPageViewed('edit-team', topicID, teamID);
});
});
},
......@@ -340,6 +345,7 @@
this.getBrowseTeamView(topicID, teamID).done(function (browseTeamView) {
self.mainView = browseTeamView;
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 @@
},
setActiveTab: function (index) {
var tab, tabEl, view;
if (typeof index === 'string') {
tab = this.urlMap[index];
tabEl = this.$('a[data-url='+index+']');
}
else {
tab = this.tabs[index];
tabEl = this.$('a[data-index='+index+']');
}
view = tab.view;
var tabMeta = this.getTabMeta(index),
tab = tabMeta.tab,
tabEl = tabMeta.element,
view = tab.view;
this.$('a.is-active').removeClass('is-active').attr('aria-selected', 'false');
tabEl.addClass('is-active').attr('aria-selected', 'true');
view.setElement(this.$('.page-content-main')).render();
......@@ -81,6 +75,22 @@
switchTab: function (event) {
event.preventDefault();
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;
......
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