EDUCATOR-4918: Hide teams listing for team-sets with privacy enabled (#23293)

* pass teamset type existence flags to the teams tab view
This commit is contained in:
Jansen Kantor
2020-03-05 14:40:26 -05:00
committed by GitHub
parent 667d7606ee
commit 919dc5760a
7 changed files with 168 additions and 48 deletions

View File

@@ -4,8 +4,11 @@ define(['jquery', 'backbone', 'teams/js/teams_tab_factory', 'teams/js/views/team
'use strict';
describe('Teams Tab Factory', function() {
var initializeTeamsTabFactory = function() {
TeamsTabFactory(TeamSpecHelpers.createMockContext());
var initializeTeamsTabFactory = function(hasOpenTopic, hasPublicManagedTopic) {
var context = TeamSpecHelpers.createMockContext();
context.hasOpenTopic = hasOpenTopic;
context.hasPublicManagedTopic = hasPublicManagedTopic;
TeamsTabFactory(context);
};
beforeEach(function() {
@@ -18,9 +21,31 @@ define(['jquery', 'backbone', 'teams/js/teams_tab_factory', 'teams/js/views/team
$(document).off('ajaxError', TeamsTabView.prototype.errorHandler);
});
it('can render the "Teams" tab', function() {
initializeTeamsTabFactory();
expect($('.teams-content').text()).toContain('See all teams in your course, organized by topic');
describe('can render the "Teams" tab', function() {
it('when there are no private or open teamsets', function() {
initializeTeamsTabFactory(false, false);
expect($('.teams-content').text()).toContain('See all teams you belong to');
expect($('.teams-content').text()).not.toContain('and all public teams in your course');
expect($('.teams-content').text()).not.toContain('Join an open public team');
});
it('when there are no open teamsets but there are public teamsets', function() {
initializeTeamsTabFactory(false, true);
expect($('.teams-content').text()).toContain('See all teams you belong to');
expect($('.teams-content').text()).toContain('and all public teams in your course');
expect($('.teams-content').text()).not.toContain('Join an open public team');
});
it('when there are open teamsets but no public teamsets', function() {
initializeTeamsTabFactory(true, false);
expect($('.teams-content').text()).toContain('See all teams you belong to');
expect($('.teams-content').text()).toContain('and all public teams in your course');
expect($('.teams-content').text()).toContain('Join an open public team');
});
it('when there are both open and public teamsets', function() {
initializeTeamsTabFactory(true, true);
expect($('.teams-content').text()).toContain('See all teams you belong to');
expect($('.teams-content').text()).toContain('and all public teams in your course');
expect($('.teams-content').text()).toContain('Join an open public team');
});
});
});
}

View File

@@ -232,30 +232,10 @@ define([
describe('Manage Tab', function() {
var manageTabSelector = '.page-content-nav>.nav-item[data-url=manage]';
var noManagedData = TeamSpecHelpers.createMockTopicData(1, 2);
var withManagedData = TeamSpecHelpers.createMockTopicData(1, 2);
var topicsNoManaged, topicsWithManaged;
topicsNoManaged = {
count: 2,
num_pages: 1,
current_page: 1,
start: 0,
results: noManagedData
};
withManagedData[1].type = 'public_managed';
topicsWithManaged = {
count: 2,
num_pages: 1,
current_page: 1,
start: 0,
results: withManagedData
};
it('is not visible to unprivileged users', function() {
var teamsTabView = createTeamsTabView(this, {
userInfo: TeamSpecHelpers.createMockUserInfo({privileged: false}),
topics: topicsNoManaged
hasManagedTopic: true
});
expect(teamsTabView.$(manageTabSelector).length).toBe(0);
});
@@ -263,7 +243,7 @@ define([
it('is not visible when there are no managed topics', function() {
var teamsTabView = createTeamsTabView(this, {
userInfo: TeamSpecHelpers.createMockUserInfo({privileged: true}),
topics: topicsNoManaged
hasManagedTopic: false
});
expect(teamsTabView.$(manageTabSelector).length).toBe(0);
});
@@ -271,12 +251,47 @@ define([
it('is visible to privileged users when there is a managed topic', function() {
var teamsTabView = createTeamsTabView(this, {
userInfo: TeamSpecHelpers.createMockUserInfo({privileged: true}),
topics: topicsWithManaged
hasManagedTopic: true
});
expect(teamsTabView.$(manageTabSelector).length).toBe(1);
});
});
describe('Browse Tab', function() {
var browseTabSelector = '.page-content-nav>.nav-item[data-url=browse]';
it('is not visible if there are no open and no public teamsets', function() {
var teamsTabView = createTeamsTabView(this, {
hasOpenTopic: false,
hasPublicManagedTopic: false
});
expect(teamsTabView.$(browseTabSelector).length).toBe(0);
});
it('is visible if there are open teamsets', function() {
var teamsTabView = createTeamsTabView(this, {
hasOpenTopic: true,
hasPublicManagedTopic: false
});
expect(teamsTabView.$(browseTabSelector).length).toBe(1);
});
it('is visible if there are public teamsets', function() {
var teamsTabView = createTeamsTabView(this, {
hasOpenTopic: false,
hasPublicManagedTopic: true
});
expect(teamsTabView.$(browseTabSelector).length).toBe(1);
});
it('is visible if there are both public and open teamsets', function() {
var teamsTabView = createTeamsTabView(this, {
hasOpenTopic: true,
hasPublicManagedTopic: true
});
expect(teamsTabView.$(browseTabSelector).length).toBe(1);
});
});
describe('Search', function() {
var performSearch = function(reqs, teamsTabView) {
teamsTabView.$('.search-field').val('foo');

View File

@@ -274,6 +274,9 @@ define([
start: 0,
results: createMockTopicData(1, 5)
},
hasOpenTopic: true,
hasPublicManagedTopic: false,
hasManagedTopic: false,
maxTeamSize: 6,
languages: testLanguages,
countries: testCountries,

View File

@@ -143,14 +143,17 @@
});
tabsList = [{
title: gettext('My Team'),
title: gettext('My Teams'),
url: 'my-teams',
view: this.myTeamsView
}, {
title: gettext('Browse'),
url: 'browse',
view: this.topicsView
}];
if (this.shouldSeeBrowseTab()) {
tabsList.push({
title: gettext('Browse'),
url: 'browse',
view: this.topicsView
});
}
if (this.canViewManageTab()) {
tabsList.push({
title: gettext('Manage'),
@@ -161,11 +164,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.'
),
description: this.getTeamsTabViewDescription(),
mainView: new TeamsTabbedView({
tabs: tabsList,
router: this.router
@@ -183,9 +182,10 @@
// Navigate to the default page if there is no history:
// 1. If the user belongs to at least one team, jump to the "My Teams" page
// 2. If not, then jump to the "Browse" page
// 2. If the user will not see a "Browse" page, jump to the "My Teams" page
// 3. Otherwise, jump to the "Browse" page
if (Backbone.history.getFragment() === '') {
if (this.myTeamsCollection.length > 0) {
if (this.myTeamsCollection.length > 0 || !this.shouldSeeBrowseTab()) {
this.router.navigate('my-teams', {trigger: true});
} else {
this.router.navigate('browse', {trigger: true});
@@ -490,7 +490,7 @@
* Returns whether the "Manage" tab should be shown to the user.
*/
canViewManageTab: function() {
return this.canManageTeams() && this.anyInstructorManagedTopics();
return this.canManageTeams() && this.context.hasManagedTopic;
},
/**
@@ -501,13 +501,26 @@
return this.canEditTeam();
},
/**
* Returns whether _any_ of the topics are instructor-managed.
*/
anyInstructorManagedTopics: function() {
return this.topicsCollection.some(
function(topic) { return topic.isInstructorManaged(); }
);
shouldSeeBrowseTab: function() {
return this.context.hasOpenTopic || this.context.hasPublicManagedTopic;
},
getTeamsTabViewDescription: function() {
if (this.context.hasOpenTopic) {
return gettext(
'See all teams you belong to and all public ' +
'teams in your course, organized by topic. ' +
'Join an open public team to collaborate with other learners ' +
'who are interested in the same topic as you are.'
);
} else if (this.context.hasPublicManagedTopic) {
return gettext(
'See all teams you belong to and all public ' +
'teams in your course, organized by topic.'
);
} else {
return gettext('See all teams you belong to.');
}
},
createBreadcrumbs: function(topic, team) {

View File

@@ -43,6 +43,9 @@ from openedx.core.djangolib.js_utils import (
TeamsTabFactory({
courseID: '${six.text_type(course.id) | n, js_escaped_string}',
topics: ${topics | n, dump_js_escaped_json},
hasManagedTopic: ${has_managed_teamset | n, dump_js_escaped_json},
hasPublicManagedTopic: ${has_public_managed_teamset | n, dump_js_escaped_json},
hasOpenTopic: ${has_open_teamset | n, dump_js_escaped_json},
userInfo: ${user_info | n, dump_js_escaped_json},
topicUrl: '${topic_url | n, js_escaped_string}',
topicsUrl: '${topics_url | n, js_escaped_string}',

View File

@@ -39,6 +39,7 @@ from ..search_indexes import CourseTeam, CourseTeamIndexer, course_team_post_sav
from .factories import LAST_ACTIVITY_AT, CourseTeamFactory
@ddt.ddt
class TestDashboard(SharedModuleStoreTestCase):
"""Tests for the Teams dashboard."""
test_password = "test"
@@ -197,6 +198,54 @@ class TestDashboard(SharedModuleStoreTestCase):
response = self.client.get(course_two_teams_url)
self.assertContains(response, '"teams": {"count": 0')
@ddt.unpack
@ddt.data(
(True, False, False),
(False, True, False),
(False, False, True),
(True, True, True),
(False, True, True),
)
def test_teamset_counts(self, has_open, has_private, has_public):
topics = []
if has_open:
topics.append({
"name": "test topic 1",
"id": 1,
"description": "Desc1",
"type": "open"
})
if has_private:
topics.append({
"name": "test topic 2",
"id": 2,
"description": "Desc2",
"type": "private_managed"
})
if has_public:
topics.append({
"name": "test topic 3",
"id": 3,
"description": "Desc3",
"type": "public_managed"
})
course = CourseFactory.create(
teams_configuration=TeamsConfig({"topics": topics})
)
teams_url = reverse('teams_dashboard', args=[course.id])
CourseEnrollmentFactory.create(user=self.user, course_id=course.id)
self.client.login(username=self.user.username, password=self.test_password)
response = self.client.get(teams_url)
expected_has_open = "hasOpenTopic: " + "true" if has_open else "false"
expected_has_public = "hasPublicManagedTopic: " + "true" if has_public else "false"
expected_has_managed = "hasManagedTopic: " + "true" if has_public or has_private else "false"
self.assertContains(response, expected_has_open)
self.assertContains(response, expected_has_public)
self.assertContains(response, expected_has_managed)
class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
"""Base class for Team API test cases."""

View File

@@ -4,6 +4,7 @@ HTTP endpoints for the Teams API.
import logging
from collections import Counter
import six
from django.conf import settings
@@ -31,6 +32,7 @@ from openedx.core.lib.api.authentication import BearerAuthentication
from lms.djangoapps.courseware.courses import get_course_with_access, has_access
from lms.djangoapps.discussion.django_comment_client.utils import has_discussion_privileges
from lms.djangoapps.teams.models import CourseTeam, CourseTeamMembership
from openedx.core.lib.teams_config import TeamsetType
from openedx.core.lib.api.parsers import MergePatchParser
from openedx.core.lib.api.permissions import IsStaffOrReadOnly
from openedx.core.lib.api.view_utils import (
@@ -136,6 +138,10 @@ class TeamsDashboardView(GenericAPIView):
topics = get_alphabetical_topics(course)
organization_protection_status = user_organization_protection_status(request.user, course_key)
# We have some frontend logic that needs to know if we have any open, public, or managed teamsets,
# and it's easier to just figure that out here when we have them all already
teamset_counts_by_type = Counter([topic['type'] for topic in topics])
# Paginate and serialize topic data
# BulkTeamCountPaginatedTopicSerializer will add team counts to the topics in a single
# bulk operation per page.
@@ -180,6 +186,12 @@ class TeamsDashboardView(GenericAPIView):
"staff": bool(has_access(user, 'staff', course_key)),
"teams": user_teams_data
},
"has_open_teamset": bool(teamset_counts_by_type[TeamsetType.open.value]),
"has_public_managed_teamset": bool(teamset_counts_by_type[TeamsetType.public_managed.value]),
"has_managed_teamset": bool(
teamset_counts_by_type[TeamsetType.public_managed.value] +
teamset_counts_by_type[TeamsetType.private_managed.value]
),
"topic_url": reverse(
'topics_detail', kwargs={'topic_id': 'topic_id', 'course_id': str(course_id)}, request=request
),