Merge pull request #9611 from edx/dan-f/add-team-page-viewed-events
Add page viewed events to teams app.
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
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: ''
|
||||
});
|
||||
|
||||
23
lms/djangoapps/teams/static/teams/js/utils/team_analytics.js
Normal file
23
lms/djangoapps/teams/static/teams/js/utils/team_analytics.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
Reference in New Issue
Block a user