diff --git a/lms/djangoapps/teams/static/teams/js/spec/teams_tab_factory_spec.js b/lms/djangoapps/teams/static/teams/js/spec/teams_tab_factory_spec.js index eeac3b6385..ee245a3c8c 100644 --- a/lms/djangoapps/teams/static/teams/js/spec/teams_tab_factory_spec.js +++ b/lms/djangoapps/teams/static/teams/js/spec/teams_tab_factory_spec.js @@ -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'); + }); }); }); } diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/teams_tab_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/teams_tab_spec.js index 0935f149b2..8866c2ef55 100644 --- a/lms/djangoapps/teams/static/teams/js/spec/views/teams_tab_spec.js +++ b/lms/djangoapps/teams/static/teams/js/spec/views/teams_tab_spec.js @@ -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'); diff --git a/lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js b/lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js index efcfefc8fb..2762b85623 100644 --- a/lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js +++ b/lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js @@ -274,6 +274,9 @@ define([ start: 0, results: createMockTopicData(1, 5) }, + hasOpenTopic: true, + hasPublicManagedTopic: false, + hasManagedTopic: false, maxTeamSize: 6, languages: testLanguages, countries: testCountries, diff --git a/lms/djangoapps/teams/static/teams/js/views/teams_tab.js b/lms/djangoapps/teams/static/teams/js/views/teams_tab.js index c8b775258a..a1a8154924 100644 --- a/lms/djangoapps/teams/static/teams/js/views/teams_tab.js +++ b/lms/djangoapps/teams/static/teams/js/views/teams_tab.js @@ -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) { diff --git a/lms/djangoapps/teams/templates/teams/teams.html b/lms/djangoapps/teams/templates/teams/teams.html index d24b9d7ffb..67d8eccd0b 100644 --- a/lms/djangoapps/teams/templates/teams/teams.html +++ b/lms/djangoapps/teams/templates/teams/teams.html @@ -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}', diff --git a/lms/djangoapps/teams/tests/test_views.py b/lms/djangoapps/teams/tests/test_views.py index bdf2c1c7fd..e4c12c761d 100644 --- a/lms/djangoapps/teams/tests/test_views.py +++ b/lms/djangoapps/teams/tests/test_views.py @@ -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.""" diff --git a/lms/djangoapps/teams/views.py b/lms/djangoapps/teams/views.py index 14470b95a6..4bc734c6c2 100644 --- a/lms/djangoapps/teams/views.py +++ b/lms/djangoapps/teams/views.py @@ -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 ),