Add membership thumbnails to team cards.
TNL-3172
This commit is contained in:
@@ -35,8 +35,8 @@ class UserMembershipSerializer(serializers.ModelSerializer):
|
||||
class Meta(object):
|
||||
"""Defines meta information for the ModelSerializer."""
|
||||
model = CourseTeamMembership
|
||||
fields = ("user", "date_joined")
|
||||
read_only_fields = ("date_joined",)
|
||||
fields = ("user", "date_joined", "last_activity_at")
|
||||
read_only_fields = ("date_joined", "last_activity_at")
|
||||
|
||||
|
||||
class CourseTeamSerializer(serializers.ModelSerializer):
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
this.server_api = _.extend(
|
||||
{
|
||||
expand: 'team',
|
||||
expand: 'team,user',
|
||||
username: this.username,
|
||||
course_id: function () { return encodeURIComponent(self.course_id); }
|
||||
},
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
var TeamMembership = Backbone.Model.extend({
|
||||
defaults: {
|
||||
date_joined: '',
|
||||
last_activity_at: '',
|
||||
team: null,
|
||||
user: null
|
||||
},
|
||||
|
||||
parse: function (response, options) {
|
||||
parse: function (response) {
|
||||
response.team = new TeamModel(response.team);
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ define([
|
||||
requests,
|
||||
TeamSpecHelpers.testContext.teamMembershipsUrl,
|
||||
{
|
||||
expand : 'team',
|
||||
expand : 'team,user',
|
||||
username : TeamSpecHelpers.testContext.userInfo.username,
|
||||
course_id : TeamSpecHelpers.testContext.courseID,
|
||||
page : '1',
|
||||
|
||||
@@ -3,21 +3,24 @@ define(['jquery',
|
||||
'teams/js/views/team_card',
|
||||
'teams/js/models/team'],
|
||||
function ($, _, TeamCardView, Team) {
|
||||
'use strict';
|
||||
|
||||
describe('TeamCardView', function () {
|
||||
var createTeamCardView, view;
|
||||
createTeamCardView = function () {
|
||||
var model = new Team({
|
||||
id: 'test-team',
|
||||
name: 'Test Team',
|
||||
is_active: true,
|
||||
course_id: 'test/course/id',
|
||||
topic_id: 'test-topic',
|
||||
description: 'A team for testing',
|
||||
last_activity_at: "2015-08-21T18:53:01.145Z",
|
||||
country: 'us',
|
||||
language: 'en'
|
||||
}),
|
||||
teamCardClass = TeamCardView.extend({
|
||||
id: 'test-team',
|
||||
name: 'Test Team',
|
||||
is_active: true,
|
||||
course_id: 'test/course/id',
|
||||
topic_id: 'test-topic',
|
||||
description: 'A team for testing',
|
||||
last_activity_at: "2015-08-21T18:53:01.145Z",
|
||||
country: 'us',
|
||||
language: 'en',
|
||||
membership: []
|
||||
}),
|
||||
TeamCardClass = TeamCardView.extend({
|
||||
maxTeamSize: '100',
|
||||
srInfo: {
|
||||
id: 'test-sr-id',
|
||||
@@ -26,7 +29,7 @@ define(['jquery',
|
||||
countries: {us: 'United States of America'},
|
||||
languages: {en: 'English'}
|
||||
});
|
||||
return new teamCardClass({
|
||||
return new TeamCardClass({
|
||||
model: model
|
||||
});
|
||||
};
|
||||
@@ -50,6 +53,101 @@ define(['jquery',
|
||||
it('navigates to the associated team page when its action button is clicked', function () {
|
||||
expect(view.$('.action').attr('href')).toEqual('#teams/test-topic/test-team');
|
||||
});
|
||||
|
||||
describe('Profile Image Thumbnails', function () {
|
||||
/**
|
||||
* Takes an array of objects representing team
|
||||
* members, each having the keys 'username',
|
||||
* 'image_url', and 'last_activity', and sets the
|
||||
* teams membership accordingly and re-renders the
|
||||
* view.
|
||||
*/
|
||||
var setMemberships, expectThumbnailsOrder;
|
||||
|
||||
setMemberships = function (memberships) {
|
||||
view.model.set({
|
||||
membership: _.map(memberships, function (m) {
|
||||
return {
|
||||
user: {username: m.username, profile_image: {image_url_small: m.image_url}},
|
||||
last_activity_at: m.last_activity
|
||||
};
|
||||
})
|
||||
});
|
||||
view.render();
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes an array of objects representing team
|
||||
* members, each having the keys 'username' and
|
||||
* 'image_url', and expects that the image thumbnails
|
||||
* rendered on the team card match, in order, the
|
||||
* members of the provided list.
|
||||
*/
|
||||
expectThumbnailsOrder = function (members) {
|
||||
var thumbnails = view.$('.item-member-thumb img');
|
||||
expect(thumbnails.length).toBe(members.length);
|
||||
thumbnails.each(function (index, imgEl) {
|
||||
expect(thumbnails.eq(index).attr('alt')).toBe(members[index].username);
|
||||
expect(thumbnails.eq(index).attr('src')).toBe(members[index].image_url);
|
||||
});
|
||||
};
|
||||
|
||||
it('displays no thumbnails for an empty team', function () {
|
||||
view.model.set({membership: []});
|
||||
view.render();
|
||||
expect(view.$('.item-member-thumb').length).toBe(0);
|
||||
});
|
||||
|
||||
it('displays thumbnails for a nonempty team', function () {
|
||||
var users = [
|
||||
{
|
||||
username: 'user_1', image_url: 'user_1_image',
|
||||
last_activity: new Date("2010/1/1").toString()
|
||||
}, {
|
||||
username: 'user_2', image_url: 'user_2_image',
|
||||
last_activity: new Date("2011/1/1").toString()
|
||||
}
|
||||
];
|
||||
setMemberships(users);
|
||||
expectThumbnailsOrder([
|
||||
{username: 'user_2', image_url: 'user_2_image'},
|
||||
{username: 'user_1', image_url: 'user_1_image'},
|
||||
]);
|
||||
});
|
||||
|
||||
it('displays thumbnails and an ellipsis for a team with greater than 5 members', function () {
|
||||
var users = [
|
||||
{
|
||||
username: 'user_1', image_url: 'user_1_image',
|
||||
last_activity: new Date("2001/1/1").toString()
|
||||
}, {
|
||||
username: 'user_2', image_url: 'user_2_image',
|
||||
last_activity: new Date("2006/1/1").toString()
|
||||
}, {
|
||||
username: 'user_3', image_url: 'user_3_image',
|
||||
last_activity: new Date("2003/1/1").toString()
|
||||
}, {
|
||||
username: 'user_4', image_url: 'user_4_image',
|
||||
last_activity: new Date("2002/1/1").toString()
|
||||
}, {
|
||||
username: 'user_5', image_url: 'user_5_image',
|
||||
last_activity: new Date("2005/1/1").toString()
|
||||
}, {
|
||||
username: 'user_6', image_url: 'user_6_image',
|
||||
last_activity: new Date("2004/1/1").toString()
|
||||
}
|
||||
];
|
||||
setMemberships(users);
|
||||
expectThumbnailsOrder([
|
||||
{username: 'user_2', image_url: 'user_2_image'},
|
||||
{username: 'user_5', image_url: 'user_5_image'},
|
||||
{username: 'user_6', image_url: 'user_6_image'},
|
||||
{username: 'user_3', image_url: 'user_3_image'},
|
||||
{username: 'user_4', image_url: 'user_4_image'},
|
||||
]);
|
||||
expect(view.$('.item-member-thumb').eq(-1)).toHaveText('and others…');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -7,34 +7,43 @@
|
||||
'jquery.timeago',
|
||||
'js/components/card/views/card',
|
||||
'teams/js/views/team_utils',
|
||||
'text!teams/templates/team-membership-details.underscore',
|
||||
'text!teams/templates/team-country-language.underscore',
|
||||
'text!teams/templates/team-activity.underscore'
|
||||
], function (Backbone, _, gettext, timeago, CardView, TeamUtils, teamCountryLanguageTemplate, teamActivityTemplate) {
|
||||
], function (
|
||||
Backbone,
|
||||
_,
|
||||
gettext,
|
||||
timeago,
|
||||
CardView,
|
||||
TeamUtils,
|
||||
teamMembershipDetailsTemplate,
|
||||
teamCountryLanguageTemplate,
|
||||
teamActivityTemplate
|
||||
) {
|
||||
var TeamMembershipView, TeamCountryLanguageView, TeamActivityView, TeamCardView;
|
||||
|
||||
TeamMembershipView = Backbone.View.extend({
|
||||
tagName: 'div',
|
||||
className: 'team-members',
|
||||
template: _.template(
|
||||
'<span class="member-count"><%= membership_message %></span>' +
|
||||
'<ul class="list-member-thumbs"></ul>'
|
||||
),
|
||||
template: _.template(teamMembershipDetailsTemplate),
|
||||
|
||||
initialize: function (options) {
|
||||
this.maxTeamSize = options.maxTeamSize;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var memberships = this.model.get('membership'),
|
||||
var allMemberships = _(this.model.get('membership'))
|
||||
.sortBy(function (member) {return new Date(member.last_activity_at);}).reverse(),
|
||||
displayableMemberships = allMemberships.slice(0, 5),
|
||||
maxMemberCount = this.maxTeamSize;
|
||||
this.$el.html(this.template({
|
||||
membership_message: TeamUtils.teamCapacityText(memberships.length, maxMemberCount)
|
||||
membership_message: TeamUtils.teamCapacityText(allMemberships.length, maxMemberCount),
|
||||
memberships: displayableMemberships,
|
||||
has_additional_memberships: displayableMemberships.length < allMemberships.length,
|
||||
// Translators: "and others" refers to fact that additional members of a team exist that are not displayed.
|
||||
sr_message: gettext('and others')
|
||||
}));
|
||||
_.each(memberships, function (membership) {
|
||||
this.$('list-member-thumbs').append(
|
||||
'<li class="item-member-thumb"><img alt="' + membership.user.username + '" src=""></img></li>'
|
||||
);
|
||||
}, this);
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<span class="member-count"><%= membership_message %></span>
|
||||
<ul class="list-member-thumbs">
|
||||
<% _.each(memberships, function (membership) { %>
|
||||
<li class="item-member-thumb"><img alt="<%- membership.user.username %>" src="<%- membership.user.profile_image.image_url_small %>"></img></li>
|
||||
<% }) %>
|
||||
<% if (has_additional_memberships) { %>
|
||||
<li class="item-member-thumb"><span class="sr"><%- sr_message %></span>…</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
@@ -97,7 +97,7 @@ class TeamsDashboardView(View):
|
||||
team_memberships_page = Paginator(team_memberships, TEAM_MEMBERSHIPS_PER_PAGE).page(1)
|
||||
team_memberships_serializer = PaginatedMembershipSerializer(
|
||||
instance=team_memberships_page,
|
||||
context={'expand': ('team',)},
|
||||
context={'expand': ('team', 'user'), 'request': request},
|
||||
)
|
||||
|
||||
context = {
|
||||
|
||||
Reference in New Issue
Block a user