Govern team creation for non-privileged users
TNL-2960 Non-privileged users can only be in one team, so don't allow them to create new teams if they are already members of another.
This commit is contained in:
@@ -8,15 +8,25 @@
|
||||
|
||||
this.course_id = options.course_id;
|
||||
this.username = options.username;
|
||||
this.privileged = options.privileged;
|
||||
this.perPage = options.per_page || 10;
|
||||
this.server_api['expand'] = 'team';
|
||||
this.server_api['course_id'] = function () { return encodeURIComponent(this.course_id); };
|
||||
this.server_api['course_id'] = function () { return encodeURIComponent(options.course_id); };
|
||||
this.server_api['username'] = this.username;
|
||||
delete this.server_api['sort_order']; // Sort order is not specified for the TeamMembership API
|
||||
delete this.server_api['order_by']; // Order by is not specified for the TeamMembership API
|
||||
},
|
||||
|
||||
model: TeamMembershipModel
|
||||
model: TeamMembershipModel,
|
||||
|
||||
canUserCreateTeam: function() {
|
||||
// Note: users can only belong to one team at a time, and
|
||||
// non-privileged users are automatically added to any team
|
||||
// that they create. This means that non-privileged users
|
||||
// are not allowed to create a new team when they already
|
||||
// belong to a different one.
|
||||
return this.privileged || this.length === 0;
|
||||
}
|
||||
});
|
||||
return TeamMembershipCollection;
|
||||
});
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'backbone',
|
||||
'teams/js/views/team_actions'
|
||||
], function ($, Backbone, TeamActionsView) {
|
||||
'use strict';
|
||||
|
||||
describe('TeamActions', function () {
|
||||
var teamActionsView;
|
||||
|
||||
beforeEach(function () {
|
||||
setFixtures('<div class="teams-content"></div>');
|
||||
spyOn(Backbone.history, 'navigate');
|
||||
teamActionsView = new TeamActionsView({
|
||||
el: $('.teams-content'),
|
||||
teamParams: {topicId: 'awesomeness'}
|
||||
}).render();
|
||||
});
|
||||
|
||||
it('can render itself correctly', function () {
|
||||
expect(teamActionsView.$('.title').text()).toBe('Are you having trouble finding a team to join?');
|
||||
expect(teamActionsView.$('.copy').text()).toBe(
|
||||
"Try browsing all teams or searching team descriptions. If you " +
|
||||
"still can't find a team to join, create a new team in this topic."
|
||||
);
|
||||
});
|
||||
|
||||
it('can navigate to correct routes', function () {
|
||||
teamActionsView.$('a.browse-teams').click();
|
||||
expect(Backbone.history.navigate.calls[0].args).toContain('browse');
|
||||
|
||||
teamActionsView.$('a.search-team-descriptions').click();
|
||||
// TODO! Should be updated once team description search feature is available
|
||||
expect(Backbone.history.navigate.calls[1].args).toContain('browse');
|
||||
|
||||
teamActionsView.$('a.create-team').click();
|
||||
expect(Backbone.history.navigate.calls[2].args).toContain('topics/awesomeness/create-team');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,15 +5,23 @@ define(["jquery", "backbone", "teams/js/teams_tab_factory"],
|
||||
describe("Teams Tab Factory", function() {
|
||||
var teamsTab;
|
||||
|
||||
beforeEach(function() {
|
||||
setFixtures('<section class="teams-content"></section>');
|
||||
teamsTab = new TeamsTabFactory({
|
||||
var initializeTeamsTabFactory = function() {
|
||||
TeamsTabFactory({
|
||||
topics: {results: []},
|
||||
topicsUrl: '',
|
||||
teamsUrl: '',
|
||||
maxTeamSize: 9999,
|
||||
courseID: 'edX/DemoX/Demo_Course'
|
||||
courseID: 'edX/DemoX/Demo_Course',
|
||||
userInfo: {
|
||||
username: 'test-user',
|
||||
privileged: false,
|
||||
teamMembershipData: null
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
setFixtures('<section class="teams-content"></section>');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@@ -21,12 +29,14 @@ define(["jquery", "backbone", "teams/js/teams_tab_factory"],
|
||||
});
|
||||
|
||||
it("can load templates", function() {
|
||||
expect($("body").text()).toContain("My Teams");
|
||||
expect($("body").text()).toContain("Showing 0 out of 0 total");
|
||||
initializeTeamsTabFactory();
|
||||
expect($('.teams-content').text()).toContain("My Teams");
|
||||
expect($('.teams-content').text()).toContain("Showing 0 out of 0 total");
|
||||
});
|
||||
|
||||
it("displays a header", function() {
|
||||
expect($("body").html()).toContain("See all teams in your course, organized by topic");
|
||||
initializeTeamsTabFactory();
|
||||
expect($('.teams-content').html()).toContain("See all teams in your course, organized by topic");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -6,36 +6,50 @@ define([
|
||||
], function (Backbone, TeamCollection, TeamMembershipCollection, TeamsView) {
|
||||
'use strict';
|
||||
describe('Teams View', function () {
|
||||
var teamsView, teamCollection, initialTeams,
|
||||
initialTeamMemberships, teamMembershipCollection;
|
||||
var countries = [
|
||||
['', ''],
|
||||
['US', 'United States'],
|
||||
['CA', 'Canada'],
|
||||
['MX', 'Mexico']
|
||||
];
|
||||
var languages = [
|
||||
['', ''],
|
||||
['en', 'English'],
|
||||
['es', 'Spanish'],
|
||||
['fr', 'French']
|
||||
];
|
||||
|
||||
var createTeams = function (startIndex, stopIndex) {
|
||||
return _.map(_.range(startIndex, stopIndex + 1), function (i) {
|
||||
return {
|
||||
name: "team " + i,
|
||||
id: "id " + i,
|
||||
language: languages[i%4][0],
|
||||
country: countries[i%4][0],
|
||||
is_active: true,
|
||||
membership: []
|
||||
};
|
||||
});
|
||||
},
|
||||
countries = [
|
||||
['', ''],
|
||||
['US', 'United States'],
|
||||
['CA', 'Canada'],
|
||||
['MX', 'Mexico']
|
||||
],
|
||||
languages = [
|
||||
['', ''],
|
||||
['en', 'English'],
|
||||
['es', 'Spanish'],
|
||||
['fr', 'French']
|
||||
];
|
||||
var createTeamData = function (startIndex, stopIndex) {
|
||||
return _.map(_.range(startIndex, stopIndex + 1), function (i) {
|
||||
return {
|
||||
name: "team " + i,
|
||||
id: "id " + i,
|
||||
language: languages[i%4][0],
|
||||
country: countries[i%4][0],
|
||||
is_active: true,
|
||||
membership: []
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
var createTeamMemberships = function(startIndex, stopIndex) {
|
||||
var teams = createTeams(startIndex, stopIndex)
|
||||
var createTeams = function(teamData) {
|
||||
return new TeamCollection(
|
||||
{
|
||||
count: 6,
|
||||
num_pages: 2,
|
||||
current_page: 1,
|
||||
start: 0,
|
||||
results: teamData
|
||||
},
|
||||
{
|
||||
course_id: 'my/course/id',
|
||||
parse: true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
var createTeamMembershipsData = function(startIndex, stopIndex) {
|
||||
var teams = createTeamData(startIndex, stopIndex);
|
||||
return _.map(_.range(startIndex, stopIndex + 1), function (i) {
|
||||
return {
|
||||
user: {
|
||||
@@ -47,6 +61,26 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var createTeamMemberships = function(teamMembershipData, options) {
|
||||
return new TeamMembershipCollection(
|
||||
{
|
||||
count: 11,
|
||||
num_pages: 3,
|
||||
current_page: 1,
|
||||
start: 0,
|
||||
results: teamMembershipData
|
||||
},
|
||||
_.extend(_.extend({}, {
|
||||
course_id: 'my/course/id',
|
||||
parse: true,
|
||||
url: 'api/teams/team_memberships',
|
||||
username: 'andya',
|
||||
privileged: false
|
||||
}),
|
||||
options)
|
||||
);
|
||||
};
|
||||
|
||||
var verifyCards = function(view, teams) {
|
||||
var teamCards = view.$('.team-card');
|
||||
_.each(teams, function (team, index) {
|
||||
@@ -55,53 +89,30 @@ define([
|
||||
expect(currentCard.text()).toMatch(_.object(languages)[team.language]);
|
||||
expect(currentCard.text()).toMatch(_.object(countries)[team.country]);
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
setFixtures('<div class="teams-container"></div>');
|
||||
initialTeams = createTeams(1, 5);
|
||||
teamCollection = new TeamCollection(
|
||||
{
|
||||
count: 6,
|
||||
num_pages: 2,
|
||||
current_page: 1,
|
||||
start: 0,
|
||||
results: initialTeams
|
||||
},
|
||||
{
|
||||
course_id: 'my/course/id',
|
||||
parse: true
|
||||
}
|
||||
);
|
||||
|
||||
initialTeamMemberships = createTeamMemberships(1, 5);
|
||||
teamMembershipCollection = new TeamMembershipCollection(
|
||||
{
|
||||
count: 11,
|
||||
num_pages: 3,
|
||||
current_page: 1,
|
||||
start: 0,
|
||||
results: initialTeamMemberships
|
||||
},
|
||||
{
|
||||
course_id: 'my/course/id',
|
||||
parse: true,
|
||||
url: 'api/teams/team_memberships',
|
||||
username: 'andya',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('can render itself with teams collection', function () {
|
||||
teamsView = new TeamsView({
|
||||
var createTeamsView = function(options) {
|
||||
return new TeamsView({
|
||||
el: '.teams-container',
|
||||
collection: teamCollection,
|
||||
collection: options.teams || createTeams(createTeamData(1, 5)),
|
||||
teamMemberships: options.teamMemberships || createTeamMemberships(createTeamMembershipsData(1, 5)),
|
||||
teamParams: {
|
||||
topicID: 'test-topic',
|
||||
countries: countries,
|
||||
languages: languages
|
||||
}
|
||||
}).render();
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
setFixtures('<div class="teams-container"></div>');
|
||||
});
|
||||
|
||||
it('can render itself with team collection', function () {
|
||||
var testTeamData = createTeamData(1, 5),
|
||||
teamsView = createTeamsView({
|
||||
teams: createTeams(testTeamData)
|
||||
});
|
||||
|
||||
expect(teamsView.$('.teams-paging-header').text()).toMatch('Showing 1-5 out of 6 total');
|
||||
|
||||
@@ -109,45 +120,69 @@ define([
|
||||
expect(footerEl.text()).toMatch('1\\s+out of\\s+\/\\s+2');
|
||||
expect(footerEl).not.toHaveClass('hidden');
|
||||
|
||||
verifyCards(teamsView, initialTeams);
|
||||
verifyCards(teamsView, testTeamData);
|
||||
});
|
||||
|
||||
it('can render itself with team memberships collection', function () {
|
||||
teamsView = new TeamsView({
|
||||
el: '.teams-container',
|
||||
collection: teamMembershipCollection,
|
||||
teamParams: {}
|
||||
}).render();
|
||||
it('can render itself with team membership collection', function () {
|
||||
var teamMembershipsData = createTeamMembershipsData(1, 5),
|
||||
teamMemberships = createTeamMemberships(teamMembershipsData),
|
||||
teamsView = createTeamsView({
|
||||
teams: teamMemberships,
|
||||
teamMemberships: teamMemberships
|
||||
});
|
||||
|
||||
expect(teamsView.$('.teams-paging-header').text()).toMatch('Showing 1-5 out of 11 total');
|
||||
var footerEl = teamsView.$('.teams-paging-footer');
|
||||
expect(footerEl.text()).toMatch('1\\s+out of\\s+\/\\s+3');
|
||||
expect(footerEl).not.toHaveClass('hidden');
|
||||
|
||||
verifyCards(teamsView, initialTeamMemberships);
|
||||
verifyCards(teamsView, teamMembershipsData);
|
||||
});
|
||||
|
||||
it ('can render the actions view', function () {
|
||||
teamsView = new TeamsView({
|
||||
el: '.teams-container',
|
||||
collection: teamCollection,
|
||||
teamParams: {},
|
||||
}).render();
|
||||
describe("Team Actions View", function() {
|
||||
it('can render itself correctly', function () {
|
||||
var emptyMembership = createTeamMemberships([]),
|
||||
teamsView = createTeamsView({ teamMemberships: emptyMembership });
|
||||
expect(teamsView.$('.title').text()).toBe('Are you having trouble finding a team to join?');
|
||||
expect(teamsView.$('.copy').text()).toBe(
|
||||
"Try browsing all teams or searching team descriptions. If you " +
|
||||
"still can't find a team to join, create a new team in this topic."
|
||||
);
|
||||
});
|
||||
|
||||
expect(teamsView.$el.text()).not.toContain(
|
||||
'Are you having trouble finding a team to join?'
|
||||
);
|
||||
|
||||
teamsView = new TeamsView({
|
||||
el: '.teams-container',
|
||||
collection: teamCollection,
|
||||
teamParams: {},
|
||||
showActions: true
|
||||
}).render();
|
||||
it('can navigate to correct routes', function () {
|
||||
var emptyMembership = createTeamMemberships([]),
|
||||
teamsView = createTeamsView({ teamMemberships: emptyMembership });
|
||||
spyOn(Backbone.history, 'navigate');
|
||||
teamsView.$('a.browse-teams').click();
|
||||
expect(Backbone.history.navigate.calls[0].args).toContain('browse');
|
||||
|
||||
expect(teamsView.$el.text()).toContain(
|
||||
'Are you having trouble finding a team to join?'
|
||||
);
|
||||
teamsView.$('a.search-teams').click();
|
||||
// TODO! Should be updated once team description search feature is available
|
||||
expect(Backbone.history.navigate.calls[1].args).toContain('browse');
|
||||
|
||||
teamsView.$('a.create-team').click();
|
||||
expect(Backbone.history.navigate.calls[2].args).toContain('topics/test-topic/create-team');
|
||||
});
|
||||
|
||||
it('does not show for a user already in a team', function () {
|
||||
var teamsView = createTeamsView({});
|
||||
expect(teamsView.$el.text()).not.toContain(
|
||||
'Are you having trouble finding a team to join?'
|
||||
);
|
||||
});
|
||||
|
||||
it('shows for a privileged user already in a team', function () {
|
||||
var staffMembership = createTeamMemberships(
|
||||
createTeamMembershipsData(1, 5),
|
||||
{ privileged: true }
|
||||
),
|
||||
teamsView = createTeamsView({ teamMemberships: staffMembership });
|
||||
expect(teamsView.$el.text()).toContain(
|
||||
'Are you having trouble finding a team to join?'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,31 +2,62 @@ define([
|
||||
'jquery',
|
||||
'backbone',
|
||||
'common/js/spec_helpers/ajax_helpers',
|
||||
'teams/js/views/teams_tab',
|
||||
'URI'
|
||||
], function ($, Backbone, AjaxHelpers, TeamsTabView, URI) {
|
||||
'teams/js/views/teams_tab'
|
||||
], function ($, Backbone, AjaxHelpers, TeamsTabView) {
|
||||
'use strict';
|
||||
|
||||
describe('TeamsTab', function () {
|
||||
var teamsTabView,
|
||||
expectContent = function (text) {
|
||||
expect(teamsTabView.$('.page-content-main').text()).toContain(text);
|
||||
},
|
||||
expectHeader = function (text) {
|
||||
expect(teamsTabView.$('.teams-header').text()).toContain(text);
|
||||
},
|
||||
expectError = function (text) {
|
||||
expect(teamsTabView.$('.warning').text()).toContain(text);
|
||||
},
|
||||
expectFocus = function (element) {
|
||||
expect(element.focus).toHaveBeenCalled();
|
||||
};
|
||||
var expectContent = function (teamsTabView, text) {
|
||||
expect(teamsTabView.$('.page-content-main').text()).toContain(text);
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
setFixtures('<div class="teams-content"></div>');
|
||||
teamsTabView = new TeamsTabView({
|
||||
el: $('.teams-content'),
|
||||
topics: {
|
||||
var expectHeader = function (teamsTabView, text) {
|
||||
expect(teamsTabView.$('.teams-header').text()).toContain(text);
|
||||
};
|
||||
|
||||
var expectError = function (teamsTabView, text) {
|
||||
expect(teamsTabView.$('.warning').text()).toContain(text);
|
||||
};
|
||||
|
||||
var expectFocus = function (element) {
|
||||
expect(element.focus).toHaveBeenCalled();
|
||||
};
|
||||
|
||||
var createUserInfo = function(options) {
|
||||
var defaultTeamMembershipData = {
|
||||
count: 1,
|
||||
currentPage: 1,
|
||||
numPages: 1,
|
||||
next: null,
|
||||
previous: null,
|
||||
results: [
|
||||
{
|
||||
user: {
|
||||
username: 'andya',
|
||||
url: 'https://openedx.example.com/api/user/v1/accounts/andya'
|
||||
},
|
||||
team: {
|
||||
description: '',
|
||||
name: 'Discrete Maths',
|
||||
id: 'dm',
|
||||
topic_id: 'algorithms'
|
||||
},
|
||||
date_joined: '2015-04-09T17:31:56Z'
|
||||
}
|
||||
]
|
||||
};
|
||||
return _.extend(
|
||||
{
|
||||
username: 'andya',
|
||||
privileged: false,
|
||||
teamMembershipData: defaultTeamMembershipData
|
||||
},
|
||||
options
|
||||
);
|
||||
};
|
||||
|
||||
var createTeamsTabView = function(options) {
|
||||
var defaultTopics = {
|
||||
count: 1,
|
||||
num_pages: 1,
|
||||
current_page: 1,
|
||||
@@ -38,33 +69,25 @@ define([
|
||||
team_count: 0
|
||||
}]
|
||||
},
|
||||
teamMemberships: {
|
||||
count: 1,
|
||||
currentPage: 1,
|
||||
numPages: 1,
|
||||
next: null,
|
||||
previous: null,
|
||||
results: [
|
||||
{
|
||||
user: {
|
||||
username: 'andya',
|
||||
url: 'https://openedx.example.com/api/user/v1/accounts/andya'
|
||||
teamsTabView = new TeamsTabView(
|
||||
_.extend(
|
||||
{
|
||||
el: $('.teams-content'),
|
||||
topics: defaultTopics,
|
||||
userInfo: createUserInfo(),
|
||||
topicsUrl: 'api/topics/',
|
||||
topicUrl: 'api/topics/topic_id,test/course/id',
|
||||
teamsUrl: 'api/teams/',
|
||||
courseID: 'test/course/id'
|
||||
},
|
||||
team: {
|
||||
description: '',
|
||||
name: 'Discrete Maths',
|
||||
id: 'dm',
|
||||
topic_id: 'algorithms'
|
||||
},
|
||||
date_joined: '2015-04-09T17:31:56Z'
|
||||
},
|
||||
]
|
||||
},
|
||||
topicsUrl: 'api/topics/',
|
||||
topicUrl: 'api/topics/topic_id,test/course/id',
|
||||
teamsUrl: 'api/teams/',
|
||||
courseID: 'test/course/id'
|
||||
}).render();
|
||||
options || {}
|
||||
)
|
||||
).render();
|
||||
return teamsTabView;
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
setFixtures('<div class="teams-content"></div>');
|
||||
Backbone.history.start();
|
||||
spyOn($.fn, 'focus');
|
||||
});
|
||||
@@ -74,48 +97,55 @@ define([
|
||||
});
|
||||
|
||||
it('shows the my teams tab initially', function () {
|
||||
expectHeader('See all teams in your course, organized by topic');
|
||||
expectContent('Showing 1 out of 1 total');
|
||||
expectContent('Discrete Maths');
|
||||
var teamsTabView = createTeamsTabView();
|
||||
expectHeader(teamsTabView, 'See all teams in your course, organized by topic');
|
||||
expectContent(teamsTabView, 'Showing 1 out of 1 total');
|
||||
expectContent(teamsTabView, 'Discrete Maths');
|
||||
});
|
||||
|
||||
describe('Navigation', function () {
|
||||
it('can switch tabs', function () {
|
||||
var teamsTabView = createTeamsTabView();
|
||||
teamsTabView.$('a.nav-item[data-url="browse"]').click();
|
||||
expectContent('test description');
|
||||
expectContent(teamsTabView, 'test description');
|
||||
teamsTabView.$('a.nav-item[data-url="my-teams"]').click();
|
||||
expectContent('Showing 1 out of 1 total');
|
||||
expectContent('Discrete Maths');
|
||||
expectContent(teamsTabView, 'Showing 1 out of 1 total');
|
||||
expectContent(teamsTabView, 'Discrete Maths');
|
||||
});
|
||||
|
||||
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});
|
||||
expectError('The page "no_such_page" could not be found.');
|
||||
expectError(teamsTabView, 'The page "no_such_page" could not be found.');
|
||||
expectFocus(teamsTabView.$('.warning'));
|
||||
});
|
||||
|
||||
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.router.navigate('topics/no_such_topic', {trigger: true});
|
||||
AjaxHelpers.expectRequest(requests, 'GET', 'api/topics/no_such_topic,test/course/id', null);
|
||||
AjaxHelpers.respondWithError(requests, 404);
|
||||
expectError('The topic "no_such_topic" could not be found.');
|
||||
expectError(teamsTabView, 'The topic "no_such_topic" could not be found.');
|
||||
expectFocus(teamsTabView.$('.warning'));
|
||||
});
|
||||
|
||||
it('displays and focuses an error message when trying to navigate to a nonexistent team', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
var requests = AjaxHelpers.requests(this),
|
||||
teamsTabView = createTeamsTabView();
|
||||
teamsTabView.router.navigate('teams/test_topic/no_such_team', {trigger: true});
|
||||
AjaxHelpers.expectRequest(requests, 'GET', 'api/teams/no_such_team', null);
|
||||
AjaxHelpers.respondWithError(requests, 404);
|
||||
expectError('The team "no_such_team" could not be found.');
|
||||
expectError(teamsTabView, 'The team "no_such_team" could not be found.');
|
||||
expectFocus(teamsTabView.$('.warning'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Discussion privileges', function () {
|
||||
it('allows privileged access to any team', function () {
|
||||
teamsTabView.$el.data('privileged', true);
|
||||
var teamsTabView = createTeamsTabView({
|
||||
userInfo: createUserInfo({ privileged: true })
|
||||
});
|
||||
// Note: using `undefined` here to ensure that we
|
||||
// don't even look at the team when the user is
|
||||
// privileged
|
||||
@@ -123,7 +153,12 @@ define([
|
||||
});
|
||||
|
||||
it('allows access to a team which an unprivileged user is a member of', function () {
|
||||
teamsTabView.$el.data('privileged', false).data('username', 'test-user');
|
||||
var teamsTabView = createTeamsTabView({
|
||||
userInfo: createUserInfo({
|
||||
username: 'test-user',
|
||||
privileged: false
|
||||
})
|
||||
});
|
||||
expect(teamsTabView.readOnlyDiscussion({
|
||||
attributes: {
|
||||
membership: [{
|
||||
@@ -136,7 +171,9 @@ define([
|
||||
});
|
||||
|
||||
it('does not allow access if the user is neither privileged nor a team member', function () {
|
||||
teamsTabView.$el.data('privileged', false).data('username', 'test-user');
|
||||
var teamsTabView = createTeamsTabView({
|
||||
userInfo: createUserInfo({ privileged: false })
|
||||
});
|
||||
expect(teamsTabView.readOnlyDiscussion({
|
||||
attributes: { membership: [] }
|
||||
})).toBe(true);
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
define([
|
||||
'underscore', 'common/js/spec_helpers/ajax_helpers'
|
||||
], function (_, AjaxHelpers) {
|
||||
define(['underscore'], function (_) {
|
||||
'use strict';
|
||||
var createMockPostResponse, createMockDiscussionResponse, createAnnotatedContentInfo, createMockThreadResponse,
|
||||
testCourseID = 'course/1',
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
'js/views/fields',
|
||||
'teams/js/models/team',
|
||||
'text!teams/templates/edit-team.underscore'],
|
||||
function (Backbone, _, gettext, FieldViews, TeamModel, edit_team_template) {
|
||||
function (Backbone, _, gettext, FieldViews, TeamModel, editTeamTemplate) {
|
||||
return Backbone.View.extend({
|
||||
|
||||
maxTeamNameLength: 255,
|
||||
@@ -79,7 +79,7 @@
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(_.template(edit_team_template)({primaryButtonTitle: this.primaryButtonTitle}));
|
||||
this.$el.html(_.template(editTeamTemplate)({primaryButtonTitle: this.primaryButtonTitle}));
|
||||
this.set(this.teamNameField, '.team-required-fields');
|
||||
this.set(this.teamDescriptionField, '.team-required-fields');
|
||||
this.set(this.optionalDescriptionField, '.team-optional-fields');
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
;(function (define) {
|
||||
'use strict';
|
||||
define([
|
||||
'gettext',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'text!teams/templates/team-actions.underscore'
|
||||
], function (gettext, _, Backbone, team_actions_template) {
|
||||
return Backbone.View.extend({
|
||||
events: {
|
||||
'click a.browse-teams': 'browseTeams',
|
||||
'click a.search-team-descriptions': 'searchTeamDescriptions',
|
||||
'click a.create-team': 'showCreateTeamForm'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
this.template = _.template(team_actions_template);
|
||||
this.teamParams = options.teamParams;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var message = interpolate_text(
|
||||
_.escape(gettext("Try {browse_span_start}browsing all teams{span_end} or {search_span_start}searching team descriptions{span_end}. If you still can't find a team to join, {create_span_start}create a new team in this topic{span_end}.")),
|
||||
{
|
||||
'browse_span_start': '<a class="browse-teams" href="">',
|
||||
'search_span_start': '<a class="search-team-descriptions" href="">',
|
||||
'create_span_start': '<a class="create-team" href="">',
|
||||
'span_end': '</a>'
|
||||
}
|
||||
);
|
||||
this.$el.html(this.template({message: message}));
|
||||
return this;
|
||||
},
|
||||
|
||||
browseTeams: function (event) {
|
||||
event.preventDefault();
|
||||
Backbone.history.navigate('browse', {trigger: true});
|
||||
},
|
||||
|
||||
searchTeamDescriptions: function (event) {
|
||||
event.preventDefault();
|
||||
// TODO! Will navigate to correct place once required functionality is available
|
||||
Backbone.history.navigate('browse', {trigger: true});
|
||||
},
|
||||
|
||||
showCreateTeamForm: function (event) {
|
||||
event.preventDefault();
|
||||
Backbone.history.navigate('topics/' + this.teamParams.topicId + '/create-team', {trigger: true});
|
||||
}
|
||||
});
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -4,34 +4,45 @@
|
||||
'backbone',
|
||||
'teams/js/views/team_card',
|
||||
'common/js/components/views/paginated_view',
|
||||
'teams/js/views/team_actions'
|
||||
], function (Backbone, TeamCardView, PaginatedView, TeamActionsView) {
|
||||
'text!teams/templates/team-actions.underscore'
|
||||
], function (Backbone, TeamCardView, PaginatedView, teamActionsTemplate) {
|
||||
var TeamsView = PaginatedView.extend({
|
||||
type: 'teams',
|
||||
|
||||
events: {
|
||||
'click a.browse-teams': 'browseTeams',
|
||||
'click a.search-teams': 'searchTeams',
|
||||
'click a.create-team': 'showCreateTeamForm'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
this.topic = options.topic;
|
||||
this.teamMemberships = options.teamMemberships;
|
||||
this.teamParams = options.teamParams;
|
||||
this.itemViewClass = TeamCardView.extend({
|
||||
router: options.router,
|
||||
topic: options.topic,
|
||||
maxTeamSize: options.maxTeamSize,
|
||||
countries: this.selectorOptionsArrayToHashWithBlank(options.teamParams.countries),
|
||||
languages: this.selectorOptionsArrayToHashWithBlank(options.teamParams.languages),
|
||||
languages: this.selectorOptionsArrayToHashWithBlank(options.teamParams.languages)
|
||||
});
|
||||
PaginatedView.prototype.initialize.call(this);
|
||||
this.teamParams = options.teamParams;
|
||||
this.showActions = options.showActions;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
PaginatedView.prototype.render.call(this);
|
||||
|
||||
if (this.showActions === true) {
|
||||
var teamActionsView = new TeamActionsView({
|
||||
teamParams: this.teamParams
|
||||
});
|
||||
this.$el.append(teamActionsView.$el);
|
||||
teamActionsView.render();
|
||||
if (this.teamMemberships.canUserCreateTeam()) {
|
||||
var message = interpolate_text(
|
||||
_.escape(gettext("Try {browse_span_start}browsing all teams{span_end} or {search_span_start}searching team descriptions{span_end}. If you still can't find a team to join, {create_span_start}create a new team in this topic{span_end}.")),
|
||||
{
|
||||
'browse_span_start': '<a class="browse-teams" href="">',
|
||||
'search_span_start': '<a class="search-teams" href="">',
|
||||
'create_span_start': '<a class="create-team" href="">',
|
||||
'span_end': '</a>'
|
||||
}
|
||||
);
|
||||
this.$el.append(_.template(teamActionsTemplate, {message: message}));
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -49,6 +60,22 @@
|
||||
var map = _.object(options);
|
||||
map[""] = "";
|
||||
return map;
|
||||
},
|
||||
|
||||
browseTeams: function (event) {
|
||||
event.preventDefault();
|
||||
Backbone.history.navigate('browse', {trigger: true});
|
||||
},
|
||||
|
||||
searchTeamDescriptions: function (event) {
|
||||
event.preventDefault();
|
||||
// TODO! Will navigate to correct place once required functionality is available
|
||||
Backbone.history.navigate('browse', {trigger: true});
|
||||
},
|
||||
|
||||
showCreateTeamForm: function (event) {
|
||||
event.preventDefault();
|
||||
Backbone.history.navigate('topics/' + this.teamParams.topicID + '/create-team', {trigger: true});
|
||||
}
|
||||
});
|
||||
return TeamsView;
|
||||
|
||||
@@ -41,18 +41,18 @@
|
||||
var router;
|
||||
this.courseID = options.courseID;
|
||||
this.topics = options.topics;
|
||||
this.teamMemberships = options.teamMemberships;
|
||||
this.topicUrl = options.topicUrl;
|
||||
this.teamsUrl = options.teamsUrl;
|
||||
this.teamMembershipsUrl = options.teamMembershipsUrl;
|
||||
this.maxTeamSize = options.maxTeamSize;
|
||||
this.languages = options.languages;
|
||||
this.countries = options.countries;
|
||||
this.username = options.username;
|
||||
this.userInfo = options.userInfo;
|
||||
|
||||
// This slightly tedious approach is necessary
|
||||
// to use regular expressions within Backbone
|
||||
// routes, allowing us to capture which tab
|
||||
// name is being routed to
|
||||
// name is being routed to.
|
||||
router = this.router = new Backbone.Router();
|
||||
_.each([
|
||||
[':default', _.bind(this.routeNotFound, this)],
|
||||
@@ -65,20 +65,21 @@
|
||||
router.route.apply(router, route);
|
||||
});
|
||||
|
||||
this.teamMembershipsCollection = new TeamMembershipCollection(
|
||||
this.teamMemberships,
|
||||
this.teamMemberships = new TeamMembershipCollection(
|
||||
this.userInfo.teamMembershipData,
|
||||
{
|
||||
url: this.teamMembershipsUrl,
|
||||
course_id: this.courseID,
|
||||
username: this.username,
|
||||
parse: true,
|
||||
|
||||
username: this.userInfo.username,
|
||||
privileged: this.userInfo.privileged,
|
||||
parse: true
|
||||
}
|
||||
).bootstrap();
|
||||
|
||||
this.myTeamsView = new TeamsView({
|
||||
router: this.router,
|
||||
collection: this.teamMembershipsCollection,
|
||||
collection: this.teamMemberships,
|
||||
teamMemberships: this.teamMemberships,
|
||||
maxTeamSize: this.maxTeamSize,
|
||||
teamParams: {
|
||||
courseId: this.courseID,
|
||||
@@ -186,14 +187,14 @@
|
||||
url: self.teamsUrl,
|
||||
per_page: 10
|
||||
});
|
||||
this.teamsCollection = collection;
|
||||
self.teamsCollection = collection;
|
||||
collection.goTo(1)
|
||||
.done(function() {
|
||||
var teamsView = new TeamsView({
|
||||
router: router,
|
||||
collection: collection,
|
||||
teamMemberships: self.teamMemberships,
|
||||
maxTeamSize: self.maxTeamSize,
|
||||
showActions: true,
|
||||
teamParams: {
|
||||
courseId: self.courseID,
|
||||
teamsUrl: self.teamsUrl,
|
||||
@@ -412,9 +413,9 @@
|
||||
readOnlyDiscussion: function (team) {
|
||||
var self = this;
|
||||
return !(
|
||||
this.$el.data('privileged') ||
|
||||
self.userInfo.privileged ||
|
||||
_.any(team.attributes.membership, function (membership) {
|
||||
return membership.user.username === self.$el.data('username');
|
||||
return membership.user.username === self.userInfo.username;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
<div class="container">
|
||||
<div class="teams-wrapper">
|
||||
<section class="teams-content" data-username=${json.dumps(username, cls=EscapedEdxJSONEncoder)} data-privileged="${json.dumps(privileged)}">
|
||||
<section class="teams-content">
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,15 +35,14 @@
|
||||
TeamsTabFactory({
|
||||
courseID: '${ unicode(course.id) }',
|
||||
topics: ${ json.dumps(topics, cls=EscapedEdxJSONEncoder) },
|
||||
teamMemberships: ${ json.dumps(team_memberships, cls=EscapedEdxJSONEncoder) },
|
||||
userInfo: ${ json.dumps(user_info, cls=EscapedEdxJSONEncoder) },
|
||||
topicUrl: '${ topic_url }',
|
||||
topicsUrl: '${ topics_url }',
|
||||
teamsUrl: '${ teams_url }',
|
||||
teamMembershipsUrl: '${ team_memberships_url }',
|
||||
maxTeamSize: ${ course.teams_max_size },
|
||||
languages: ${ json.dumps(languages, cls=EscapedEdxJSONEncoder) },
|
||||
countries: ${ json.dumps(countries, cls=EscapedEdxJSONEncoder) },
|
||||
username: '${ username }'
|
||||
countries: ${ json.dumps(countries, cls=EscapedEdxJSONEncoder) }
|
||||
});
|
||||
</%static:require_module>
|
||||
</%block>
|
||||
|
||||
@@ -97,17 +97,19 @@ class TeamsDashboardView(View):
|
||||
context = {
|
||||
"course": course,
|
||||
"topics": topics_serializer.data,
|
||||
"user_info": {
|
||||
"username": user.username,
|
||||
"privileged": has_discussion_privileges(user, course_key),
|
||||
"team_memberships_data": team_memberships_serializer.data,
|
||||
},
|
||||
"topic_url": reverse(
|
||||
'topics_detail', kwargs={'topic_id': 'topic_id', 'course_id': str(course_id)}, request=request
|
||||
),
|
||||
"team_memberships": team_memberships_serializer.data,
|
||||
"topics_url": reverse('topics_list', request=request),
|
||||
"teams_url": reverse('teams_list', request=request),
|
||||
"team_memberships_url": reverse('team_membership_list', request=request),
|
||||
"languages": settings.ALL_LANGUAGES,
|
||||
"countries": list(countries),
|
||||
"username": user.username,
|
||||
"privileged": has_discussion_privileges(user, course_key),
|
||||
"disable_courseware_js": True,
|
||||
}
|
||||
return render_to_response("teams/teams.html", context)
|
||||
|
||||
@@ -790,9 +790,8 @@
|
||||
'lms/include/js/spec/discovery/discovery_factory_spec.js',
|
||||
'lms/include/js/spec/ccx/schedule_spec.js',
|
||||
'lms/include/teams/js/spec/collections/topic_collection_spec.js',
|
||||
'lms/include/teams/js/spec/edit_team_spec.js',
|
||||
'lms/include/teams/js/spec/team_actions_spec.js',
|
||||
'lms/include/teams/js/spec/teams_factory_spec.js',
|
||||
'lms/include/teams/js/spec/teams_tab_factory_spec.js',
|
||||
'lms/include/teams/js/spec/views/edit_team_spec.js',
|
||||
'lms/include/teams/js/spec/views/team_discussion_spec.js',
|
||||
'lms/include/teams/js/spec/views/team_profile_spec.js',
|
||||
'lms/include/teams/js/spec/views/teams_spec.js',
|
||||
|
||||
Reference in New Issue
Block a user