diff --git a/common/static/common/js/components/collections/paging_collection.js b/common/static/common/js/components/collections/paging_collection.js
index 4ac4bf93b3..4bf4ff3066 100644
--- a/common/static/common/js/components/collections/paging_collection.js
+++ b/common/static/common/js/components/collections/paging_collection.js
@@ -30,6 +30,8 @@
isZeroIndexed: false,
perPage: 10,
+ isStale: false,
+
sortField: '',
sortDirection: 'descending',
sortableFields: {},
@@ -37,6 +39,8 @@
filterField: '',
filterableFields: {},
+ searchString: null,
+
paginator_core: {
type: 'GET',
dataType: 'json',
@@ -51,9 +55,10 @@
},
server_api: {
- 'page': function () { return this.currentPage; },
- 'page_size': function () { return this.perPage; },
- 'sort_order': function () { return this.sortField; }
+ page: function () { return this.currentPage; },
+ page_size: function () { return this.perPage; },
+ text_search: function () { return this.searchString ? this.searchString : ''; },
+ sort_order: function () { return this.sortField; }
},
parse: function (response) {
@@ -61,7 +66,11 @@
this.currentPage = response.current_page;
this.totalPages = response.num_pages;
this.start = response.start;
- this.sortField = response.sort_order;
+
+ // Note: sort_order is not returned when performing a search
+ if (response.sort_order) {
+ this.sortField = response.sort_order;
+ }
return response.results;
},
@@ -84,6 +93,7 @@
self = this;
return this.goTo(page - (this.isZeroIndexed ? 1 : 0), {reset: true}).then(
function () {
+ self.isStale = false;
self.trigger('page_changed');
},
function () {
@@ -92,6 +102,24 @@
);
},
+
+ /**
+ * Refreshes the collection if it has been marked as stale.
+ * @returns {promise} Returns a promise representing the refresh.
+ */
+ refresh: function() {
+ var deferred = $.Deferred();
+ if (this.isStale) {
+ this.setPage(1)
+ .done(function() {
+ deferred.resolve();
+ });
+ } else {
+ deferred.resolve();
+ }
+ return deferred.promise();
+ },
+
/**
* Returns true if the collection has a next page, false otherwise.
*/
@@ -183,7 +211,7 @@
}
}
this.sortField = fieldName;
- this.setPage(1);
+ this.isStale = true;
},
/**
@@ -193,7 +221,7 @@
*/
setSortDirection: function (direction) {
this.sortDirection = direction;
- this.setPage(1);
+ this.isStale = true;
},
/**
@@ -203,7 +231,19 @@
*/
setFilterField: function (fieldName) {
this.filterField = fieldName;
- this.setPage(1);
+ this.isStale = true;
+ },
+
+ /**
+ * Sets the string to use for a text search. If no string is specified then
+ * the search is cleared.
+ * @param searchString A string to search on, or null if no search is to be applied.
+ */
+ setSearchString: function(searchString) {
+ if (searchString !== this.searchString) {
+ this.searchString = searchString;
+ this.isStale = true;
+ }
}
}, {
SortDirection: {
diff --git a/common/static/common/js/components/views/paging_header.js b/common/static/common/js/components/views/paging_header.js
index 8cd01bb9a1..c49af8bc4b 100644
--- a/common/static/common/js/components/views/paging_header.js
+++ b/common/static/common/js/components/views/paging_header.js
@@ -43,10 +43,16 @@
return this;
},
+ /**
+ * Updates the collection's sort order, and fetches an updated set of
+ * results.
+ * @returns {*} A promise for the collection being updated
+ */
sortCollection: function () {
var selected = this.$('#paging-header-select option:selected');
this.sortOrder = selected.attr('value');
this.collection.setSortField(this.sortOrder);
+ return this.collection.refresh();
}
});
return PagingHeader;
diff --git a/common/static/common/js/components/views/search_field.js b/common/static/common/js/components/views/search_field.js
new file mode 100644
index 0000000000..7599edee35
--- /dev/null
+++ b/common/static/common/js/components/views/search_field.js
@@ -0,0 +1,71 @@
+/**
+ * A search field that works in concert with a paginated collection. When the user
+ * performs a search, the collection's search string will be updated and then the
+ * collection will be refreshed to show the first page of results.
+ */
+;(function (define) {
+ 'use strict';
+
+ define(['backbone', 'jquery', 'underscore', 'text!common/templates/components/search-field.underscore'],
+ function (Backbone, $, _, searchFieldTemplate) {
+ return Backbone.View.extend({
+
+ events: {
+ 'submit .search-form': 'performSearch',
+ 'blur .search-form': 'onFocusOut',
+ 'keyup .search-field': 'refreshState',
+ 'click .action-clear': 'clearSearch'
+ },
+
+ initialize: function(options) {
+ this.type = options.type;
+ this.label = options.label;
+ },
+
+ refreshState: function() {
+ var searchField = this.$('.search-field'),
+ clearButton = this.$('.action-clear'),
+ searchString = $.trim(searchField.val());
+ if (searchString) {
+ clearButton.removeClass('is-hidden');
+ } else {
+ clearButton.addClass('is-hidden');
+ }
+ },
+
+ render: function() {
+ this.$el.html(_.template(searchFieldTemplate, {
+ type: this.type,
+ searchString: this.collection.searchString,
+ searchLabel: this.label
+ }));
+ this.refreshState();
+ return this;
+ },
+
+ onFocusOut: function(event) {
+ // If the focus is going anywhere but the clear search
+ // button then treat it as a request to search.
+ if (!$(event.relatedTarget).hasClass('action-clear')) {
+ this.performSearch(event);
+ }
+ },
+
+ performSearch: function(event) {
+ var searchField = this.$('.search-field'),
+ searchString = $.trim(searchField.val());
+ event.preventDefault();
+ this.collection.setSearchString(searchString);
+ return this.collection.refresh();
+ },
+
+ clearSearch: function(event) {
+ event.preventDefault();
+ this.$('.search-field').val('');
+ this.collection.setSearchString('');
+ this.refreshState();
+ return this.collection.refresh();
+ }
+ });
+ });
+}).call(this, define || RequireJS.define);
diff --git a/common/static/common/js/spec/components/paging_collection_spec.js b/common/static/common/js/spec/components/paging_collection_spec.js
index 89062d24d2..0d5f668e97 100644
--- a/common/static/common/js/spec/components/paging_collection_spec.js
+++ b/common/static/common/js/spec/components/paging_collection_spec.js
@@ -10,11 +10,11 @@ define(['jquery',
'use strict';
describe('PagingCollection', function () {
- var collection, requests, server, assertQueryParams;
- server = {
+ var collection;
+ var server = {
isZeroIndexed: false,
count: 43,
- respond: function () {
+ respond: function (requests) {
var params = (new URI(requests[requests.length - 1].url)).query(true),
page = parseInt(params['page'], 10),
page_size = parseInt(params['page_size'], 10),
@@ -35,7 +35,7 @@ define(['jquery',
}
}
};
- assertQueryParams = function (params) {
+ var assertQueryParams = function (requests, params) {
var urlParams = (new URI(requests[requests.length - 1].url)).query(true);
_.each(params, function (value, key) {
expect(urlParams[key]).toBe(value);
@@ -45,7 +45,6 @@ define(['jquery',
beforeEach(function () {
collection = new PagingCollection();
collection.perPage = 10;
- requests = AjaxHelpers.requests(this);
server.isZeroIndexed = false;
server.count = 43;
});
@@ -69,10 +68,11 @@ define(['jquery',
});
it('can set the sort field', function () {
+ var requests = AjaxHelpers.requests(this);
collection.registerSortableField('test_field', 'Test Field');
collection.setSortField('test_field', false);
- expect(requests.length).toBe(1);
- assertQueryParams({'sort_order': 'test_field'});
+ collection.refresh();
+ assertQueryParams(requests, {'sort_order': 'test_field'});
expect(collection.sortField).toBe('test_field');
expect(collection.sortDisplayName()).toBe('Test Field');
});
@@ -80,7 +80,7 @@ define(['jquery',
it('can set the filter field', function () {
collection.registerFilterableField('test_field', 'Test Field');
collection.setFilterField('test_field');
- expect(requests.length).toBe(1);
+ collection.refresh();
// The default implementation does not send any query params for filtering
expect(collection.filterField).toBe('test_field');
expect(collection.filterDisplayName()).toBe('Test Field');
@@ -88,11 +88,9 @@ define(['jquery',
it('can set the sort direction', function () {
collection.setSortDirection(PagingCollection.SortDirection.ASCENDING);
- expect(requests.length).toBe(1);
// The default implementation does not send any query params for sort direction
expect(collection.sortDirection).toBe(PagingCollection.SortDirection.ASCENDING);
collection.setSortDirection(PagingCollection.SortDirection.DESCENDING);
- expect(requests.length).toBe(2);
expect(collection.sortDirection).toBe(PagingCollection.SortDirection.DESCENDING);
});
@@ -113,11 +111,12 @@ define(['jquery',
'queries with page, page_size, and sort_order parameters when zero indexed': [true, 2],
'queries with page, page_size, and sort_order parameters when one indexed': [false, 3],
}, function (isZeroIndexed, page) {
+ var requests = AjaxHelpers.requests(this);
collection.isZeroIndexed = isZeroIndexed;
collection.perPage = 5;
collection.sortField = 'test_field';
collection.setPage(3);
- assertQueryParams({'page': page.toString(), 'page_size': '5', 'sort_order': 'test_field'});
+ assertQueryParams(requests, {'page': page.toString(), 'page_size': '5', 'sort_order': 'test_field'});
});
SpecHelpers.withConfiguration({
@@ -129,27 +128,30 @@ define(['jquery',
}, function () {
describe('setPage', function() {
it('triggers a reset event when the page changes successfully', function () {
- var resetTriggered = false;
+ var requests = AjaxHelpers.requests(this),
+ resetTriggered = false;
collection.on('reset', function () { resetTriggered = true; });
collection.setPage(3);
- server.respond();
+ server.respond(requests);
expect(resetTriggered).toBe(true);
});
it('triggers an error event when the requested page is out of range', function () {
- var errorTriggered = false;
+ var requests = AjaxHelpers.requests(this),
+ errorTriggered = false;
collection.on('error', function () { errorTriggered = true; });
collection.setPage(17);
- server.respond();
+ server.respond(requests);
expect(errorTriggered).toBe(true);
});
it('triggers an error event if the server responds with a 500', function () {
- var errorTriggered = false;
+ var requests = AjaxHelpers.requests(this),
+ errorTriggered = false;
collection.on('error', function () { errorTriggered = true; });
collection.setPage(2);
expect(collection.getPage()).toBe(2);
- server.respond();
+ server.respond(requests);
collection.setPage(3);
AjaxHelpers.respondWithError(requests, 500, {}, requests.length - 1);
expect(errorTriggered).toBe(true);
@@ -159,11 +161,12 @@ define(['jquery',
describe('getPage', function () {
it('returns the correct page', function () {
+ var requests = AjaxHelpers.requests(this);
collection.setPage(1);
- server.respond();
+ server.respond(requests);
expect(collection.getPage()).toBe(1);
collection.setPage(3);
- server.respond();
+ server.respond(requests);
expect(collection.getPage()).toBe(3);
});
});
@@ -177,9 +180,10 @@ define(['jquery',
'returns false on the last page': [5, 43, false]
},
function (page, count, result) {
+ var requests = AjaxHelpers.requests(this);
server.count = count;
collection.setPage(page);
- server.respond();
+ server.respond(requests);
expect(collection.hasNextPage()).toBe(result);
}
);
@@ -194,9 +198,10 @@ define(['jquery',
'returns false on the first page': [1, 43, false]
},
function (page, count, result) {
+ var requests = AjaxHelpers.requests(this);
server.count = count;
collection.setPage(page);
- server.respond();
+ server.respond(requests);
expect(collection.hasPreviousPage()).toBe(result);
}
);
@@ -209,13 +214,14 @@ define(['jquery',
'silently fails on the last page': [5, 43, 5]
},
function (page, count, newPage) {
+ var requests = AjaxHelpers.requests(this);
server.count = count;
collection.setPage(page);
- server.respond();
+ server.respond(requests);
expect(collection.getPage()).toBe(page);
collection.nextPage();
if (requests.length > 1) {
- server.respond();
+ server.respond(requests);
}
expect(collection.getPage()).toBe(newPage);
}
@@ -229,13 +235,14 @@ define(['jquery',
'silently fails on the first page': [1, 43, 1]
},
function (page, count, newPage) {
+ var requests = AjaxHelpers.requests(this);
server.count = count;
collection.setPage(page);
- server.respond();
+ server.respond(requests);
expect(collection.getPage()).toBe(page);
collection.previousPage();
if (requests.length > 1) {
- server.respond();
+ server.respond(requests);
}
expect(collection.getPage()).toBe(newPage);
}
diff --git a/common/static/common/js/spec/components/search_field_spec.js b/common/static/common/js/spec/components/search_field_spec.js
new file mode 100644
index 0000000000..3464ef1036
--- /dev/null
+++ b/common/static/common/js/spec/components/search_field_spec.js
@@ -0,0 +1,105 @@
+define([
+ 'underscore',
+ 'common/js/components/views/search_field',
+ 'common/js/components/collections/paging_collection',
+ 'common/js/spec_helpers/ajax_helpers'
+], function (_, SearchFieldView, PagingCollection, AjaxHelpers) {
+ 'use strict';
+ describe('SearchFieldView', function () {
+ var searchFieldView,
+ mockUrl = '/api/mock_collection';
+
+ var newCollection = function (size, perPage) {
+ var pageSize = 5,
+ results = _.map(_.range(size), function (i) { return {foo: i}; });
+ var collection = new PagingCollection(
+ [],
+ {
+ url: mockUrl,
+ count: results.length,
+ num_pages: results.length / pageSize,
+ current_page: 1,
+ start: 0,
+ results: _.first(results, perPage)
+ },
+ {parse: true}
+ );
+ collection.start = 0;
+ collection.totalCount = results.length;
+ return collection;
+ };
+
+ var createSearchFieldView = function (options) {
+ options = _.extend(
+ {
+ type: 'test',
+ collection: newCollection(5, 4),
+ el: $('.test-search')
+ },
+ options || {}
+ );
+ return new SearchFieldView(options);
+ };
+
+ beforeEach(function() {
+ setFixtures('');
+ });
+
+ it('correctly displays itself', function () {
+ searchFieldView = createSearchFieldView().render();
+ expect(searchFieldView.$('.search-field').val(), '');
+ expect(searchFieldView.$('.action-clear')).toHaveClass('is-hidden');
+ });
+
+ it('can display with an initial search string', function () {
+ searchFieldView = createSearchFieldView({
+ searchString: 'foo'
+ }).render();
+ expect(searchFieldView.$('.search-field').val(), 'foo');
+ });
+
+ it('refreshes the collection when performing a search', function () {
+ var requests = AjaxHelpers.requests(this);
+ searchFieldView = createSearchFieldView().render();
+ searchFieldView.$('.search-field').val('foo');
+ searchFieldView.$('.action-search').click();
+ AjaxHelpers.expectRequestURL(requests, mockUrl, {
+ page: '1',
+ page_size: '10',
+ sort_order: '',
+ text_search: 'foo'
+ });
+ AjaxHelpers.respondWithJson(requests, {
+ count: 10,
+ current_page: 1,
+ num_pages: 1,
+ start: 0,
+ results: []
+ });
+ expect(searchFieldView.$('.search-field').val(), 'foo');
+ });
+
+ it('can clear the search', function () {
+ var requests = AjaxHelpers.requests(this);
+ searchFieldView = createSearchFieldView({
+ searchString: 'foo'
+ }).render();
+ searchFieldView.$('.action-clear').click();
+ AjaxHelpers.expectRequestURL(requests, mockUrl, {
+ page: '1',
+ page_size: '10',
+ sort_order: '',
+ text_search: ''
+ });
+ AjaxHelpers.respondWithJson(requests, {
+ count: 10,
+ current_page: 1,
+ num_pages: 1,
+ start: 0,
+ results: []
+ });
+ expect(searchFieldView.$('.search-field').val(), '');
+ expect(searchFieldView.$('.action-clear')).toHaveClass('is-hidden');
+ });
+ });
+});
diff --git a/common/static/common/js/spec_helpers/ajax_helpers.js b/common/static/common/js/spec_helpers/ajax_helpers.js
index 7f0ce09ecc..e699805512 100644
--- a/common/static/common/js/spec_helpers/ajax_helpers.js
+++ b/common/static/common/js/spec_helpers/ajax_helpers.js
@@ -1,7 +1,7 @@
define(['sinon', 'underscore', 'URI'], function(sinon, _, URI) {
'use strict';
- var fakeServer, fakeRequests, expectRequest, expectJsonRequest, expectPostRequest, expectJsonRequestURL,
+ var fakeServer, fakeRequests, expectRequest, expectJsonRequest, expectPostRequest, expectRequestURL,
respondWithJson, respondWithError, respondWithTextError, respondWithNoContent;
/* These utility methods are used by Jasmine tests to create a mock server or
@@ -77,7 +77,7 @@ define(['sinon', 'underscore', 'URI'], function(sinon, _, URI) {
* @param expectedParameters An object representing the URL parameters
* @param requestIndex An optional index for the request (by default, the last request is used)
*/
- expectJsonRequestURL = function(requests, expectedUrl, expectedParameters, requestIndex) {
+ expectRequestURL = function(requests, expectedUrl, expectedParameters, requestIndex) {
var request, parameters;
if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1;
@@ -153,15 +153,15 @@ define(['sinon', 'underscore', 'URI'], function(sinon, _, URI) {
};
return {
- 'server': fakeServer,
- 'requests': fakeRequests,
- 'expectRequest': expectRequest,
- 'expectJsonRequest': expectJsonRequest,
- 'expectJsonRequestURL': expectJsonRequestURL,
- 'expectPostRequest': expectPostRequest,
- 'respondWithJson': respondWithJson,
- 'respondWithError': respondWithError,
- 'respondWithTextError': respondWithTextError,
- 'respondWithNoContent': respondWithNoContent,
+ server: fakeServer,
+ requests: fakeRequests,
+ expectRequest: expectRequest,
+ expectJsonRequest: expectJsonRequest,
+ expectPostRequest: expectPostRequest,
+ expectRequestURL: expectRequestURL,
+ respondWithJson: respondWithJson,
+ respondWithError: respondWithError,
+ respondWithTextError: respondWithTextError,
+ respondWithNoContent: respondWithNoContent
};
});
diff --git a/common/static/common/templates/components/search-field.underscore b/common/static/common/templates/components/search-field.underscore
new file mode 100644
index 0000000000..aac29f640d
--- /dev/null
+++ b/common/static/common/templates/components/search-field.underscore
@@ -0,0 +1,12 @@
+
diff --git a/common/static/js/spec/main_requirejs.js b/common/static/js/spec/main_requirejs.js
index ac29c68b3c..24b00f2185 100644
--- a/common/static/js/spec/main_requirejs.js
+++ b/common/static/js/spec/main_requirejs.js
@@ -155,13 +155,14 @@
define([
// Run the common tests that use RequireJS.
+ 'common-requirejs/include/common/js/spec/components/feedback_spec.js',
'common-requirejs/include/common/js/spec/components/list_spec.js',
'common-requirejs/include/common/js/spec/components/paginated_view_spec.js',
'common-requirejs/include/common/js/spec/components/paging_collection_spec.js',
'common-requirejs/include/common/js/spec/components/paging_header_spec.js',
'common-requirejs/include/common/js/spec/components/paging_footer_spec.js',
- 'common-requirejs/include/common/js/spec/components/view_utils_spec.js',
- 'common-requirejs/include/common/js/spec/components/feedback_spec.js'
+ 'common-requirejs/include/common/js/spec/components/search_field_spec.js',
+ 'common-requirejs/include/common/js/spec/components/view_utils_spec.js'
]);
}).call(this, requirejs, define);
diff --git a/common/test/acceptance/pages/lms/teams.py b/common/test/acceptance/pages/lms/teams.py
index 351e091b6b..9ad67de7c8 100644
--- a/common/test/acceptance/pages/lms/teams.py
+++ b/common/test/acceptance/pages/lms/teams.py
@@ -142,6 +142,11 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin):
"""Return a list of the topic names present on the page."""
return self.q(css=CARD_TITLE_CSS).map(lambda e: e.text).results
+ @property
+ def topic_descriptions(self):
+ """Return a list of the topic descriptions present on the page."""
+ return self.q(css='p.card-description').map(lambda e: e.text).results
+
def browse_teams_for_topic(self, topic_name):
"""
Show the teams list for `topic_name`.
@@ -159,36 +164,32 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin):
self.wait_for_ajax()
-class BrowseTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin):
+class BaseTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin):
"""
The paginated UI for browsing teams within a Topic on the Teams
page.
"""
def __init__(self, browser, course_id, topic):
"""
- Set up `self.url_path` on instantiation, since it dynamically
- reflects the current topic. Note that `topic` is a dict
- representation of a topic following the same convention as a
- course module's topic.
+ Note that `topic` is a dict representation of a topic following
+ the same convention as a course module's topic.
"""
- super(BrowseTeamsPage, self).__init__(browser, course_id)
+ super(BaseTeamsPage, self).__init__(browser, course_id)
self.topic = topic
- self.url_path = "teams/#topics/{topic_id}".format(topic_id=self.topic['id'])
def is_browser_on_page(self):
- """Check if we're on the teams list page for a particular topic."""
- self.wait_for_element_presence('.team-actions', 'Wait for the bottom links to be present')
+ """Check if we're on a teams list page for a particular topic."""
has_correct_url = self.url.endswith(self.url_path)
teams_list_view_present = self.q(css='.teams-main').present
return has_correct_url and teams_list_view_present
@property
- def header_topic_name(self):
+ def header_name(self):
"""Get the topic name displayed by the page header"""
return self.q(css=TEAMS_HEADER_CSS + ' .page-title')[0].text
@property
- def header_topic_description(self):
+ def header_description(self):
"""Get the topic description displayed by the page header"""
return self.q(css=TEAMS_HEADER_CSS + ' .page-description')[0].text
@@ -229,6 +230,48 @@ class BrowseTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin):
).click()
self.wait_for_ajax()
+ @property
+ def _showing_search_results(self):
+ """
+ Returns true if showing search results.
+ """
+ return self.header_description.startswith(u"Showing results for")
+
+ def search(self, string):
+ """
+ Searches for the specified string, and returns a SearchTeamsPage
+ representing the search results page.
+ """
+ self.q(css='.search-field').first.fill(string)
+ self.q(css='.action-search').first.click()
+ self.wait_for(
+ lambda: self._showing_search_results,
+ description="Showing search results"
+ )
+ page = SearchTeamsPage(self.browser, self.course_id, self.topic)
+ page.wait_for_page()
+ return page
+
+
+class BrowseTeamsPage(BaseTeamsPage):
+ """
+ The paginated UI for browsing teams within a Topic on the Teams
+ page.
+ """
+ def __init__(self, browser, course_id, topic):
+ super(BrowseTeamsPage, self).__init__(browser, course_id, topic)
+ self.url_path = "teams/#topics/{topic_id}".format(topic_id=self.topic['id'])
+
+
+class SearchTeamsPage(BaseTeamsPage):
+ """
+ The paginated UI for showing team search results.
+ page.
+ """
+ def __init__(self, browser, course_id, topic):
+ super(SearchTeamsPage, self).__init__(browser, course_id, topic)
+ self.url_path = "teams/#topics/{topic_id}/search".format(topic_id=self.topic['id'])
+
class CreateOrEditTeamPage(CoursePage, FieldsMixin):
"""
diff --git a/common/test/acceptance/tests/lms/test_teams.py b/common/test/acceptance/tests/lms/test_teams.py
index 0315311254..11366b142b 100644
--- a/common/test/acceptance/tests/lms/test_teams.py
+++ b/common/test/acceptance/tests/lms/test_teams.py
@@ -444,7 +444,7 @@ class BrowseTopicsTest(TeamsTabBase):
{u"max_team_size": 1, u"topics": [{"name": "", "id": "", "description": initial_description}]}
)
self.topics_page.visit()
- truncated_description = self.topics_page.topic_cards[0].text
+ truncated_description = self.topics_page.topic_descriptions[0]
self.assertLess(len(truncated_description), len(initial_description))
self.assertTrue(truncated_description.endswith('...'))
self.assertIn(truncated_description.split('...')[0], initial_description)
@@ -467,8 +467,8 @@ class BrowseTopicsTest(TeamsTabBase):
self.topics_page.browse_teams_for_topic('Example Topic')
browse_teams_page = BrowseTeamsPage(self.browser, self.course_id, topic)
self.assertTrue(browse_teams_page.is_browser_on_page())
- self.assertEqual(browse_teams_page.header_topic_name, 'Example Topic')
- self.assertEqual(browse_teams_page.header_topic_description, 'Description')
+ self.assertEqual(browse_teams_page.header_name, 'Example Topic')
+ self.assertEqual(browse_teams_page.header_description, 'Description')
@attr('shard_5')
@@ -503,15 +503,24 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
def verify_page_header(self):
"""Verify that the page header correctly reflects the current topic's name and description."""
- self.assertEqual(self.browse_teams_page.header_topic_name, self.topic['name'])
- self.assertEqual(self.browse_teams_page.header_topic_description, self.topic['description'])
+ self.assertEqual(self.browse_teams_page.header_name, self.topic['name'])
+ self.assertEqual(self.browse_teams_page.header_description, self.topic['description'])
- def verify_on_page(self, page_num, total_teams, pagination_header_text, footer_visible):
+ def verify_search_header(self, search_results_page, search_query):
+ """Verify that the page header correctly reflects the current topic's name and description."""
+ self.assertEqual(search_results_page.header_name, 'Team Search')
+ self.assertEqual(
+ search_results_page.header_description,
+ 'Showing results for "{search_query}"'.format(search_query=search_query)
+ )
+
+ def verify_on_page(self, teams_page, page_num, total_teams, pagination_header_text, footer_visible):
"""
Verify that we are on the correct team list page.
Arguments:
- page_num (int): The one-indexed page we expect to be on
+ teams_page (BaseTeamsPage): The teams page object that should be the current page.
+ page_num (int): The one-indexed page number that we expect to be on
total_teams (list): An unsorted list of all the teams for the
current topic
pagination_header_text (str): Text we expect to see in the
@@ -520,13 +529,13 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
footer controls.
"""
sorted_teams = self.teams_with_default_sort_order(total_teams)
- self.assertTrue(self.browse_teams_page.get_pagination_header_text().startswith(pagination_header_text))
+ self.assertTrue(teams_page.get_pagination_header_text().startswith(pagination_header_text))
self.verify_teams(
- self.browse_teams_page,
+ teams_page,
sorted_teams[(page_num - 1) * self.TEAMS_PAGE_SIZE:page_num * self.TEAMS_PAGE_SIZE]
)
self.assertEqual(
- self.browse_teams_page.pagination_controls_visible(),
+ teams_page.pagination_controls_visible(),
footer_visible,
msg='Expected paging footer to be ' + 'visible' if footer_visible else 'invisible'
)
@@ -648,11 +657,11 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
teams = self.create_teams(self.topic, self.TEAMS_PAGE_SIZE + 1, time_between_creation=1)
self.browse_teams_page.visit()
self.verify_page_header()
- self.verify_on_page(1, teams, 'Showing 1-10 out of 11 total', True)
+ self.verify_on_page(self.browse_teams_page, 1, teams, 'Showing 1-10 out of 11 total', True)
self.browse_teams_page.press_next_page_button()
- self.verify_on_page(2, teams, 'Showing 11-11 out of 11 total', True)
+ self.verify_on_page(self.browse_teams_page, 2, teams, 'Showing 11-11 out of 11 total', True)
self.browse_teams_page.press_previous_page_button()
- self.verify_on_page(1, teams, 'Showing 1-10 out of 11 total', True)
+ self.verify_on_page(self.browse_teams_page, 1, teams, 'Showing 1-10 out of 11 total', True)
def test_teams_page_input(self):
"""
@@ -670,25 +679,21 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
teams = self.create_teams(self.topic, self.TEAMS_PAGE_SIZE + 10, time_between_creation=1)
self.browse_teams_page.visit()
self.verify_page_header()
- self.verify_on_page(1, teams, 'Showing 1-10 out of 20 total', True)
+ self.verify_on_page(self.browse_teams_page, 1, teams, 'Showing 1-10 out of 20 total', True)
self.browse_teams_page.go_to_page(2)
- self.verify_on_page(2, teams, 'Showing 11-20 out of 20 total', True)
+ self.verify_on_page(self.browse_teams_page, 2, teams, 'Showing 11-20 out of 20 total', True)
self.browse_teams_page.go_to_page(1)
- self.verify_on_page(1, teams, 'Showing 1-10 out of 20 total', True)
+ self.verify_on_page(self.browse_teams_page, 1, teams, 'Showing 1-10 out of 20 total', True)
- def test_navigation_links(self):
+ def test_browse_team_topics(self):
"""
Scenario: User should be able to navigate to "browse all teams" and "search team description" links.
- Given I am enrolled in a course with a team configuration and a topic
- containing one team
- When I visit the Teams page for that topic
+ Given I am enrolled in a course with teams enabled
+ When I visit the Teams page for a topic
Then I should see the correct page header
- And I should see the link to "browse all team"
- And I should navigate to that link
- And I see the relevant page loaded
- And I should see the link to "search teams"
- And I should navigate to that link
- And I see the relevant page loaded
+ And I should see the link to "browse teams in other topics"
+ When I should navigate to that link
+ Then I should see the topic browse page
"""
self.browse_teams_page.visit()
self.verify_page_header()
@@ -696,10 +701,23 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
self.browse_teams_page.click_browse_all_teams_link()
self.assertTrue(self.topics_page.is_browser_on_page())
+ def test_search(self):
+ """
+ Scenario: User should be able to search for a team
+ Given I am enrolled in a course with teams enabled
+ When I visit the Teams page for that topic
+ And I search for 'banana'
+ Then I should see the search result page
+ And the search header should be shown
+ And 0 results should be shown
+ """
+ # Note: all searches will return 0 results with the mock search server
+ # used by Bok Choy.
+ self.create_teams(self.topic, 5)
self.browse_teams_page.visit()
- self.verify_page_header()
- self.browse_teams_page.click_search_team_link()
- # TODO Add search page expectation once that implemented.
+ search_results_page = self.browse_teams_page.search('banana')
+ self.verify_search_header(search_results_page, 'banana')
+ self.assertTrue(search_results_page.get_pagination_header_text().startswith('Showing 0 out of 0 total'))
@attr('shard_5')
@@ -726,8 +744,8 @@ class TeamFormActions(TeamsTabBase):
self.browse_teams_page.click_create_team_link()
self.verify_page_header(
title='Create a New Team',
- description='Create a new team if you can\'t find existing teams to '
- 'join, or if you would like to learn with friends you know.',
+ description='Create a new team if you can\'t find an existing team to join, '
+ 'or if you would like to learn with friends you know.',
breadcrumbs='All Topics {topic_name}'.format(topic_name=self.topic['name'])
)
diff --git a/lms/djangoapps/teams/management/commands/reindex_course_team.py b/lms/djangoapps/teams/management/commands/reindex_course_team.py
index 7bf21a9f2f..901789180f 100644
--- a/lms/djangoapps/teams/management/commands/reindex_course_team.py
+++ b/lms/djangoapps/teams/management/commands/reindex_course_team.py
@@ -53,8 +53,8 @@ class Command(BaseCommand):
if len(args) == 0 and not options.get('all', False):
raise CommandError(u"reindex_course_team requires one or more arguments: ")
- elif not settings.FEATURES.get('ENABLE_TEAMS_SEARCH', False):
- raise CommandError(u"ENABLE_TEAMS_SEARCH must be enabled")
+ elif not settings.FEATURES.get('ENABLE_TEAMS', False):
+ raise CommandError(u"ENABLE_TEAMS must be enabled to use course team indexing")
if options.get('all', False):
course_teams = CourseTeam.objects.all()
diff --git a/lms/djangoapps/teams/management/commands/tests/test_reindex_course_team.py b/lms/djangoapps/teams/management/commands/tests/test_reindex_course_team.py
index 885bf48188..63b7593bcd 100644
--- a/lms/djangoapps/teams/management/commands/tests/test_reindex_course_team.py
+++ b/lms/djangoapps/teams/management/commands/tests/test_reindex_course_team.py
@@ -39,9 +39,9 @@ class ReindexCourseTeamTest(SharedModuleStoreTestCase):
def test_teams_search_flag_disabled_raises_command_error(self):
""" Test that raises CommandError for disabled feature flag. """
with mock.patch('django.conf.settings.FEATURES') as features:
- features.return_value = {"ENABLE_TEAMS_SEARCH": False}
+ features.return_value = {"ENABLE_TEAMS": False}
with self.assertRaises(SystemExit), nostderr():
- with self.assertRaisesRegexp(CommandError, ".* ENABLE_TEAMS_SEARCH must be enabled .*"):
+ with self.assertRaisesRegexp(CommandError, ".* ENABLE_TEAMS must be enabled .*"):
call_command('reindex_course_team')
def test_given_invalid_team_id_raises_command_error(self):
diff --git a/lms/djangoapps/teams/search_indexes.py b/lms/djangoapps/teams/search_indexes.py
index 22af7227d4..7532b48ac6 100644
--- a/lms/djangoapps/teams/search_indexes.py
+++ b/lms/djangoapps/teams/search_indexes.py
@@ -15,7 +15,7 @@ class CourseTeamIndexer(object):
"""
INDEX_NAME = "course_team_index"
DOCUMENT_TYPE_NAME = "course_team"
- ENABLE_SEARCH_KEY = "ENABLE_TEAMS_SEARCH"
+ ENABLE_SEARCH_KEY = "ENABLE_TEAMS"
def __init__(self, course_team):
self.course_team = course_team
diff --git a/lms/djangoapps/teams/static/teams/js/collections/base.js b/lms/djangoapps/teams/static/teams/js/collections/base.js
index 01410af938..9a11592a3b 100644
--- a/lms/djangoapps/teams/static/teams/js/collections/base.js
+++ b/lms/djangoapps/teams/static/teams/js/collections/base.js
@@ -11,31 +11,11 @@
this.teamEvents = options.teamEvents;
this.teamEvents.bind('teams:update', this.onUpdate, this);
- this.isStale = false;
},
onUpdate: function(event) {
+ // Mark the collection as stale so that it knows to refresh when needed.
this.isStale = true;
- },
-
- /**
- * Refreshes the collection if it has been marked as stale.
- * @param force If true, it will always refresh.
- * @returns {promise} Returns a promise representing the refresh
- */
- refresh: function(force) {
- var self = this,
- deferred = $.Deferred();
- if (force || this.isStale) {
- this.setPage(1)
- .done(function() {
- self.isStale = false;
- deferred.resolve();
- });
- } else {
- deferred.resolve();
- }
- return deferred.promise();
}
});
return BaseCollection;
diff --git a/lms/djangoapps/teams/static/teams/js/collections/team.js b/lms/djangoapps/teams/static/teams/js/collections/team.js
index a67dbd1fd9..3b0fac856a 100644
--- a/lms/djangoapps/teams/static/teams/js/collections/team.js
+++ b/lms/djangoapps/teams/static/teams/js/collections/team.js
@@ -14,7 +14,7 @@
topic_id: this.topic_id = options.topic_id,
expand: 'user',
course_id: function () { return encodeURIComponent(self.course_id); },
- order_by: function () { return this.sortField; }
+ order_by: function () { return self.searchString ? '' : this.sortField; }
},
BaseCollection.prototype.server_api
);
diff --git a/lms/djangoapps/teams/static/teams/js/collections/topic.js b/lms/djangoapps/teams/static/teams/js/collections/topic.js
index b3b60849ec..b88392a838 100644
--- a/lms/djangoapps/teams/static/teams/js/collections/topic.js
+++ b/lms/djangoapps/teams/static/teams/js/collections/topic.js
@@ -25,7 +25,9 @@
},
onUpdate: function(event) {
- this.isStale = this.isStale || event.action === 'create';
+ if (event.action === 'create') {
+ this.isStale = true;
+ }
},
model: TopicModel
diff --git a/lms/djangoapps/teams/static/teams/js/spec/collections/topic_collection_spec.js b/lms/djangoapps/teams/static/teams/js/spec/collections/topic_collection_spec.js
index ee929edf69..da43b583d6 100644
--- a/lms/djangoapps/teams/static/teams/js/spec/collections/topic_collection_spec.js
+++ b/lms/djangoapps/teams/static/teams/js/spec/collections/topic_collection_spec.js
@@ -28,7 +28,7 @@ define(['backbone', 'URI', 'underscore', 'common/js/spec_helpers/ajax_helpers',
});
it('passes a course_id to the server', function () {
- testRequestParam(this, 'course_id', 'my/course/id');
+ testRequestParam(this, 'course_id', TeamSpecHelpers.testCourseID);
});
it('URL encodes its course_id ', function () {
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 cab815b09a..c32a0827b5 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
@@ -1,24 +1,13 @@
-define(["jquery", "backbone", "teams/js/teams_tab_factory"],
- function($, Backbone, TeamsTabFactory) {
+define(['jquery', 'backbone', 'teams/js/teams_tab_factory',
+ 'teams/js/spec_helpers/team_spec_helpers'],
+ function($, Backbone, TeamsTabFactory, TeamSpecHelpers) {
'use strict';
-
+
describe("Teams Tab Factory", function() {
var teamsTab;
var initializeTeamsTabFactory = function() {
- TeamsTabFactory({
- topics: {results: []},
- topicsUrl: '',
- teamsUrl: '',
- maxTeamSize: 9999,
- courseID: 'edX/DemoX/Demo_Course',
- userInfo: {
- username: 'test-user',
- privileged: false,
- staff: false,
- team_memberships_data: null
- }
- });
+ TeamsTabFactory(TeamSpecHelpers.createMockContext());
};
beforeEach(function() {
diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js
index 97c959000c..843c5ce186 100644
--- a/lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js
+++ b/lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js
@@ -13,21 +13,21 @@ define([
var teamsUrl = '/api/team/v0/teams/',
createTeamData = {
id: null,
- name: "TeamName",
- course_id: "a/b/c",
- topic_id: "awesomeness",
- date_created: "",
- description: "TeamDescription",
- country: "US",
- language: "en",
+ name: 'TeamName',
+ course_id: TeamSpecHelpers.testCourseID,
+ topic_id: TeamSpecHelpers.testTopicID,
+ date_created: '',
+ description: 'TeamDescription',
+ country: 'US',
+ language: 'en',
membership: [],
last_activity_at: ''
},
editTeamData = {
- name: "UpdatedAvengers",
- description: "We do not discuss about avengers.",
- country: "US",
- language: "en"
+ name: 'UpdatedAvengers',
+ description: 'We do not discuss about avengers.',
+ country: 'US',
+ language: 'en'
},
verifyValidation = function (requests, teamEditView, fieldsData) {
_.each(fieldsData, function (fieldData) {
@@ -38,17 +38,19 @@ define([
var message = teamEditView.$('.wrapper-msg');
expect(message.hasClass('is-hidden')).toBeFalsy();
- var actionMessage = (teamAction === 'create' ? 'Your team could not be created.' : 'Your team could not be updated.');
+ var actionMessage = (
+ teamAction === 'create' ? 'Your team could not be created.' : 'Your team could not be updated.'
+ );
expect(message.find('.title').text().trim()).toBe(actionMessage);
expect(message.find('.copy').text().trim()).toBe(
- "Check the highlighted fields below and try again."
+ 'Check the highlighted fields below and try again.'
);
_.each(fieldsData, function (fieldData) {
if (fieldData[2] === 'error') {
- expect(teamEditView.$(fieldData[0].split(" ")[0] + '.error').length).toBe(1);
+ expect(teamEditView.$(fieldData[0].split(' ')[0] + '.error').length).toBe(1);
} else if (fieldData[2] === 'success') {
- expect(teamEditView.$(fieldData[0].split(" ")[0] + '.error').length).toBe(0);
+ expect(teamEditView.$(fieldData[0].split(' ')[0] + '.error').length).toBe(0);
}
});
@@ -58,9 +60,9 @@ define([
teamAction;
var createEditTeamView = function () {
- var teamModel = {};
+ var testTeam = {};
if (teamAction === 'edit') {
- teamModel = new TeamModel(
+ testTeam = new TeamModel(
{
id: editTeamID,
name: 'Avengers',
@@ -80,16 +82,9 @@ define([
teamEvents: TeamSpecHelpers.teamEvents,
el: $('.teams-content'),
action: teamAction,
- model: teamModel,
- teamParams: {
- teamsUrl: teamsUrl,
- courseID: "a/b/c",
- topicID: 'awesomeness',
- topicName: 'Awesomeness',
- languages: [['aa', 'Afar'], ['fr', 'French'], ['en', 'English']],
- countries: [['af', 'Afghanistan'], ['CA', 'Canada'], ['US', 'United States']],
- teamsDetailUrl: teamModel.url
- }
+ model: testTeam,
+ topic: TeamSpecHelpers.createMockTopic(),
+ context: TeamSpecHelpers.testContext
}).render();
};
@@ -133,13 +128,13 @@ define([
teamEditView.$('.u-field-name input').val(teamsData.name);
teamEditView.$('.u-field-textarea textarea').val(teamsData.description);
- teamEditView.$('.u-field-language select').val(teamsData.language).attr("selected", "selected");
- teamEditView.$('.u-field-country select').val(teamsData.country).attr("selected", "selected");
+ teamEditView.$('.u-field-language select').val(teamsData.language).attr('selected', 'selected');
+ teamEditView.$('.u-field-country select').val(teamsData.country).attr('selected', 'selected');
teamEditView.$('.create-team.form-actions .action-primary').click();
AjaxHelpers.expectJsonRequest(requests, requestMethod(), teamsUrl, teamsData);
- AjaxHelpers.respondWithJson(requests, _.extend(_.extend({}, teamsData), teamAction === 'create' ? {id: '123'} : {}));
+ AjaxHelpers.respondWithJson(requests, _.extend({}, teamsData, teamAction === 'create' ? {id: '123'} : {}));
expect(teamEditView.$('.create-team.wrapper-msg .copy').text().trim().length).toBe(0);
expect(Backbone.history.navigate.calls[0].args).toContain(expectedUrl);
@@ -209,10 +204,10 @@ define([
errorCode,
{'user_message': 'User message', 'developer_message': 'Developer message'}
);
- expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe("User message");
+ expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe('User message');
} else {
AjaxHelpers.respondWithError(requests);
- expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe("An error occurred. Please try again.");
+ expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe('An error occurred. Please try again.');
}
};
@@ -233,7 +228,9 @@ define([
});
it('can create a team', function () {
- assertTeamCreateUpdateInfo(this, createTeamData, teamsUrl, 'teams/awesomeness/123');
+ assertTeamCreateUpdateInfo(
+ this, createTeamData, teamsUrl, 'teams/' + TeamSpecHelpers.testTopicID + '/123'
+ );
});
it('shows validation error message when field is empty', function () {
@@ -244,16 +241,16 @@ define([
assertValidationMessagesWhenInvalidData(this);
});
- it("shows an error message for HTTP 500", function () {
+ it('shows an error message for HTTP 500', function () {
assertShowMessageOnError(this, createTeamData, teamsUrl, 500);
});
- it("shows correct error message when server returns an error", function () {
+ it('shows correct error message when server returns an error', function () {
assertShowMessageOnError(this, createTeamData, teamsUrl, 400);
});
- it("changes route on cancel click", function () {
- assertRedirectsToCorrectUrlOnCancel('topics/awesomeness');
+ it('changes route on cancel click', function () {
+ assertRedirectsToCorrectUrlOnCancel('topics/' + TeamSpecHelpers.testTopicID);
});
});
@@ -272,7 +269,10 @@ define([
copyTeamsData.country = 'CA';
copyTeamsData.language = 'fr';
- assertTeamCreateUpdateInfo(this, copyTeamsData, teamsUrl + editTeamID + '?expand=user', 'teams/awesomeness/' + editTeamID);
+ assertTeamCreateUpdateInfo(
+ this, copyTeamsData, teamsUrl + editTeamID + '?expand=user',
+ 'teams/' + TeamSpecHelpers.testTopicID + '/' + editTeamID
+ );
});
it('shows validation error message when field is empty', function () {
@@ -283,16 +283,16 @@ define([
assertValidationMessagesWhenInvalidData(this);
});
- it("shows an error message for HTTP 500", function () {
+ it('shows an error message for HTTP 500', function () {
assertShowMessageOnError(this, editTeamData, teamsUrl + editTeamID + '?expand=user', 500);
});
- it("shows correct error message when server returns an error", function () {
+ it('shows correct error message when server returns an error', function () {
assertShowMessageOnError(this, editTeamData, teamsUrl + editTeamID + '?expand=user', 400);
});
- it("changes route on cancel click", function () {
- assertRedirectsToCorrectUrlOnCancel('teams/awesomeness/' + editTeamID);
+ it('changes route on cancel click', function () {
+ assertRedirectsToCorrectUrlOnCancel('teams/' + TeamSpecHelpers.testTopicID + '/' + editTeamID);
});
});
});
diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/my_teams_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/my_teams_spec.js
index 92367ef589..eb0ae73a1a 100644
--- a/lms/djangoapps/teams/static/teams/js/spec/views/my_teams_spec.js
+++ b/lms/djangoapps/teams/static/teams/js/spec/views/my_teams_spec.js
@@ -13,17 +13,16 @@ define([
});
var createMyTeamsView = function(options) {
- return new MyTeamsView({
- el: '.teams-container',
- collection: options.teams || TeamSpecHelpers.createMockTeams(),
- teamMemberships: options.teamMemberships || TeamSpecHelpers.createMockTeamMemberships(),
- showActions: true,
- teamParams: {
- topicID: 'test-topic',
- countries: TeamSpecHelpers.testCountries,
- languages: TeamSpecHelpers.testLanguages
- }
- }).render();
+ return new MyTeamsView(_.extend(
+ {
+ el: '.teams-container',
+ collection: options.teams || TeamSpecHelpers.createMockTeams(),
+ teamMemberships: TeamSpecHelpers.createMockTeamMemberships(),
+ showActions: true,
+ context: TeamSpecHelpers.testContext
+ },
+ options
+ )).render();
};
it('can render itself', function () {
@@ -62,15 +61,16 @@ define([
expect(myTeamsView.$el.text().trim()).toBe('You are not currently a member of any team.');
teamMemberships.teamEvents.trigger('teams:update', { action: 'create' });
myTeamsView.render();
- AjaxHelpers.expectJsonRequestURL(
+ AjaxHelpers.expectRequestURL(
requests,
- 'api/teams/team_memberships',
+ TeamSpecHelpers.testContext.teamMembershipsUrl,
{
expand : 'team',
- username : 'testUser',
- course_id : 'my/course/id',
+ username : TeamSpecHelpers.testContext.userInfo.username,
+ course_id : TeamSpecHelpers.testContext.courseID,
page : '1',
- page_size : '10'
+ page_size : '10',
+ text_search: ''
}
);
AjaxHelpers.respondWithJson(requests, {});
diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/team_profile_header_actions_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/team_profile_header_actions_spec.js
index 5fc597c7f1..af509310ba 100644
--- a/lms/djangoapps/teams/static/teams/js/spec/views/team_profile_header_actions_spec.js
+++ b/lms/djangoapps/teams/static/teams/js/spec/views/team_profile_header_actions_spec.js
@@ -10,12 +10,10 @@ define([
createMembershipData,
createHeaderActionsView,
verifyErrorMessage,
- ACCOUNTS_API_URL = '/api/user/v1/accounts/',
- TEAMS_URL = '/api/team/v0/teams/',
- TEAMS_MEMBERSHIP_URL = '/api/team/v0/team_membership/';
+ ACCOUNTS_API_URL = '/api/user/v1/accounts/';
createTeamsUrl = function (teamId) {
- return TEAMS_URL + teamId + '?expand=user';
+ return TeamSpecHelpers.testContext.teamsUrl + teamId + '?expand=user';
};
createTeamModelData = function (teamId, teamName, membership) {
@@ -27,21 +25,22 @@ define([
};
};
- createHeaderActionsView = function(maxTeamSize, currentUsername, teamModelData, showEditButton) {
- var teamId = 'teamA';
-
- var model = new TeamModel(teamModelData, { parse: true });
+ createHeaderActionsView = function(requests, maxTeamSize, currentUsername, teamModelData, showEditButton) {
+ var model = new TeamModel(teamModelData, { parse: true }),
+ context = TeamSpecHelpers.createMockContext({
+ maxTeamSize: maxTeamSize,
+ userInfo: TeamSpecHelpers.createMockUserInfo({
+ username: currentUsername
+ })
+ });
return new TeamProfileHeaderActionsView(
{
courseID: TeamSpecHelpers.testCourseID,
teamEvents: TeamSpecHelpers.teamEvents,
+ context: context,
model: model,
- teamsUrl: createTeamsUrl(teamId),
- maxTeamSize: maxTeamSize,
- currentUsername: currentUsername,
- teamMembershipsUrl: TEAMS_MEMBERSHIP_URL,
- topicID: '',
+ topic: TeamSpecHelpers.createMockTopic(),
showEditButton: showEditButton
}
).render();
@@ -67,7 +66,7 @@ define([
});
verifyErrorMessage = function (requests, errorMessage, expectedMessage, joinTeam) {
- var view = createHeaderActionsView(1, 'ma', createTeamModelData('teamA', 'teamAlpha', []));
+ var view = createHeaderActionsView(requests, 1, 'ma', createTeamModelData('teamA', 'teamAlpha', []));
if (joinTeam) {
// if we want the error to return when user try to join team, respond with no membership
AjaxHelpers.respondWithJson(requests, {"count": 0});
@@ -78,8 +77,9 @@ define([
};
it('can render itself', function () {
+ var requests = AjaxHelpers.requests(this);
var teamModelData = createTeamModelData('teamA', 'teamAlpha', createMembershipData('ma'));
- var view = createHeaderActionsView(1, 'ma', teamModelData);
+ var view = createHeaderActionsView(requests, 1, 'ma', teamModelData);
expect(view.$('.join-team').length).toEqual(1);
});
@@ -90,14 +90,14 @@ define([
var teamId = 'teamA';
var teamName = 'teamAlpha';
var teamModelData = createTeamModelData(teamId, teamName, []);
- var view = createHeaderActionsView(1, currentUsername, teamModelData);
+ var view = createHeaderActionsView(requests, 1, currentUsername, teamModelData);
// a get request will be sent to get user membership info
// because current user is not member of current team
AjaxHelpers.expectRequest(
requests,
'GET',
- TEAMS_MEMBERSHIP_URL + '?' + $.param({
+ TeamSpecHelpers.testContext.teamMembershipsUrl + '?' + $.param({
'username': currentUsername, 'course_id': TeamSpecHelpers.testCourseID
})
);
@@ -111,7 +111,7 @@ define([
AjaxHelpers.expectRequest(
requests,
'POST',
- TEAMS_MEMBERSHIP_URL,
+ TeamSpecHelpers.testContext.teamMembershipsUrl,
$.param({'username': currentUsername, 'team_id': teamId})
);
AjaxHelpers.respondWithJson(requests, {});
@@ -135,14 +135,14 @@ define([
it('shows already member message', function () {
var requests = AjaxHelpers.requests(this);
var currentUsername = 'ma1';
- var view = createHeaderActionsView(1, currentUsername, createTeamModelData('teamA', 'teamAlpha', []));
+ var view = createHeaderActionsView(requests, 1, currentUsername, createTeamModelData('teamA', 'teamAlpha', []));
// a get request will be sent to get user membership info
// because current user is not member of current team
AjaxHelpers.expectRequest(
requests,
'GET',
- TEAMS_MEMBERSHIP_URL + '?' + $.param({
+ TeamSpecHelpers.testContext.teamMembershipsUrl + '?' + $.param({
'username': currentUsername, 'course_id': TeamSpecHelpers.testCourseID
})
);
@@ -156,6 +156,7 @@ define([
it('shows team full message', function () {
var requests = AjaxHelpers.requests(this);
var view = createHeaderActionsView(
+ requests,
1,
'ma1',
createTeamModelData('teamA', 'teamAlpha', createMembershipData('ma'))
@@ -199,7 +200,6 @@ define([
});
it('shows correct error message if initializing the view fails', function () {
- // Rendering the view sometimes require fetching user's memberships. This may fail.
var requests = AjaxHelpers.requests(this);
// verify user_message
@@ -225,23 +225,26 @@ define([
view,
createAndAssertView;
- createAndAssertView = function(showEditButton) {
+ createAndAssertView = function(requests, showEditButton) {
teamModelData = createTeamModelData('aveA', 'avengers', createMembershipData('ma'));
- view = createHeaderActionsView(1, 'ma', teamModelData, showEditButton);
+ view = createHeaderActionsView(requests, 1, 'ma', teamModelData, showEditButton);
expect(view.$('.action-edit-team').length).toEqual(showEditButton ? 1 : 0);
};
it('renders when option showEditButton is true', function () {
- createAndAssertView(true);
+ var requests = AjaxHelpers.requests(this);
+ createAndAssertView(requests, true);
});
it('does not render when option showEditButton is false', function () {
- createAndAssertView(false);
+ var requests = AjaxHelpers.requests(this);
+ createAndAssertView(requests, false);
});
it("can navigate to correct url", function () {
+ var requests = AjaxHelpers.requests(this);
spyOn(Backbone.history, 'navigate');
- createAndAssertView(true);
+ createAndAssertView(requests, true);
var editButton = view.$('.action-edit-team');
expect(editButton.length).toEqual(1);
diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/team_profile_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/team_profile_spec.js
index d2582fcd01..8e295fb611 100644
--- a/lms/djangoapps/teams/static/teams/js/spec/views/team_profile_spec.js
+++ b/lms/djangoapps/teams/static/teams/js/spec/views/team_profile_spec.js
@@ -11,7 +11,7 @@ define([
DEFAULT_MEMBERSHIP = [
{
'user': {
- 'username': 'bilbo',
+ 'username': TeamSpecHelpers.testUser,
'profile_image': {
'has_image': true,
'image_url_medium': '/image-url'
@@ -42,20 +42,8 @@ define([
profileView = new TeamProfileView({
teamEvents: TeamSpecHelpers.teamEvents,
courseID: TeamSpecHelpers.testCourseID,
+ context: TeamSpecHelpers.testContext,
model: teamModel,
- maxTeamSize: options.maxTeamSize || 3,
- requestUsername: 'bilbo',
- countries : [
- ['', ''],
- ['US', 'United States'],
- ['CA', 'Canada']
- ],
- languages : [
- ['', ''],
- ['en', 'English'],
- ['fr', 'French']
- ],
- teamMembershipDetailUrl: 'api/team/v0/team_membership/team_id,bilbo',
setFocusToHeaderFunc: function() {
$('.teams-content').focus();
}
@@ -88,7 +76,9 @@ define([
$('.prompt.warning .action-primary').click();
// expect a request to DELETE the team membership
- AjaxHelpers.expectJsonRequest(requests, 'DELETE', 'api/team/v0/team_membership/test-team,bilbo');
+ AjaxHelpers.expectJsonRequest(
+ requests, 'DELETE', '/api/team/v0/team_membership/test-team,' + TeamSpecHelpers.testUser
+ );
AjaxHelpers.respondWithNoContent(requests);
// expect a request to refetch the user's team memberships
@@ -135,7 +125,7 @@ define([
expect(view.$('.team-detail-header').text()).toBe('Team Details');
expect(view.$('.team-country').text()).toContain('United States');
expect(view.$('.team-language').text()).toContain('English');
- expect(view.$('.team-capacity').text()).toContain(members + ' / 3 Members');
+ expect(view.$('.team-capacity').text()).toContain(members + ' / 6 Members');
expect(view.$('.team-member').length).toBe(members);
expect(Boolean(view.$('.leave-team-link').length)).toBe(memberOfTeam);
};
@@ -176,9 +166,9 @@ define([
expect(view.$('.team-user-membership-status').text().trim()).toBe('You are a member of this team.');
// assert tooltip text.
- expect(view.$('.member-profile p').text()).toBe('bilbo');
+ expect(view.$('.member-profile p').text()).toBe(TeamSpecHelpers.testUser);
// assert user profile page url.
- expect(view.$('.member-profile').attr('href')).toBe('/u/bilbo');
+ expect(view.$('.member-profile').attr('href')).toBe('/u/' + TeamSpecHelpers.testUser);
//Verify that the leave team link is present
expect(view.$(leaveTeamLinkSelector).text()).toContain('Leave Team');
diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/teams_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/teams_spec.js
index 7815235301..6809e4adba 100644
--- a/lms/djangoapps/teams/static/teams/js/spec/views/teams_spec.js
+++ b/lms/djangoapps/teams/static/teams/js/spec/views/teams_spec.js
@@ -17,11 +17,7 @@ define([
collection: options.teams || TeamSpecHelpers.createMockTeams(),
teamMemberships: options.teamMemberships || TeamSpecHelpers.createMockTeamMemberships(),
showActions: true,
- teamParams: {
- topicID: 'test-topic',
- countries: TeamSpecHelpers.testCountries,
- languages: TeamSpecHelpers.testLanguages
- }
+ context: TeamSpecHelpers.testContext
}).render();
};
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 83fccf20c5..9d7f402335 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
@@ -8,14 +8,6 @@ define([
'use strict';
describe('TeamsTab', function () {
- var expectContent = function (teamsTabView, text) {
- expect(teamsTabView.$('.page-content-main').text()).toContain(text);
- };
-
- var expectHeader = function (teamsTabView, text) {
- expect(teamsTabView.$('.teams-header').text()).toContain(text);
- };
-
var expectError = function (teamsTabView, text) {
expect(teamsTabView.$('.warning').text()).toContain(text);
};
@@ -26,30 +18,17 @@ define([
var createTeamsTabView = function(options) {
var defaultTopics = {
- count: 1,
+ count: 5,
num_pages: 1,
current_page: 1,
start: 0,
- results: [{
- description: 'test description',
- name: 'test topic',
- id: 'test_topic',
- team_count: 0
- }]
+ results: TeamSpecHelpers.createMockTopicData(1, 5)
},
teamsTabView = new TeamsTabView(
- _.extend(
- {
- el: $('.teams-content'),
- topics: defaultTopics,
- userInfo: TeamSpecHelpers.createMockUserInfo(),
- topicsUrl: 'api/topics/',
- topicUrl: 'api/topics/topic_id,test/course/id',
- teamsUrl: 'api/teams/',
- courseID: 'test/course/id'
- },
- options || {}
- )
+ {
+ el: $('.teams-content'),
+ context: TeamSpecHelpers.createMockContext(options)
+ }
);
teamsTabView.start();
return teamsTabView;
@@ -82,7 +61,7 @@ define([
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.expectRequest(requests, 'GET', '/api/team/v0/topics/no_such_topic,course/1', null);
AjaxHelpers.respondWithError(requests, 404);
expectError(teamsTabView, 'The topic "no_such_topic" could not be found.');
expectFocus(teamsTabView.$('.warning'));
@@ -91,8 +70,8 @@ define([
it('displays and focuses an error message when trying to navigate to a nonexistent team', function () {
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?expand=user', null);
+ teamsTabView.router.navigate('teams/' + TeamSpecHelpers.testTopicID + '/no_such_team', {trigger: true});
+ AjaxHelpers.expectRequest(requests, 'GET', '/api/team/v0/teams/no_such_team?expand=user', null);
AjaxHelpers.respondWithError(requests, 404);
expectError(teamsTabView, 'The team "no_such_team" could not be found.');
expectFocus(teamsTabView.$('.warning'));
@@ -113,7 +92,7 @@ define([
it('allows access to a team which an unprivileged user is a member of', function () {
var teamsTabView = createTeamsTabView({
userInfo: TeamSpecHelpers.createMockUserInfo({
- username: 'test-user',
+ username: TeamSpecHelpers.testUser,
privileged: false
})
});
@@ -121,7 +100,7 @@ define([
attributes: {
membership: [{
user: {
- username: 'test-user'
+ username: TeamSpecHelpers.testUser
}
}]
}
@@ -137,5 +116,103 @@ define([
})).toBe(true);
});
});
+
+ describe('Search', function () {
+ var verifyTeamsRequest = function(requests, options) {
+ AjaxHelpers.expectRequestURL(requests, TeamSpecHelpers.testContext.teamsUrl,
+ _.extend(
+ {
+ topic_id: TeamSpecHelpers.testTopicID,
+ expand: 'user',
+ course_id: TeamSpecHelpers.testCourseID,
+ order_by: '',
+ page: '1',
+ page_size: '10',
+ text_search: ''
+ },
+ options
+ ));
+ };
+
+ it('can search teams', function () {
+ var requests = AjaxHelpers.requests(this),
+ teamsTabView = createTeamsTabView();
+ teamsTabView.browseTopic(TeamSpecHelpers.testTopicID);
+ verifyTeamsRequest(requests, {
+ order_by: 'last_activity_at',
+ text_search: ''
+ });
+ AjaxHelpers.respondWithJson(requests, {});
+ teamsTabView.$('.search-field').val('foo');
+ teamsTabView.$('.action-search').click();
+ verifyTeamsRequest(requests, {
+ order_by: '',
+ text_search: 'foo'
+ });
+ AjaxHelpers.respondWithJson(requests, {});
+ expect(teamsTabView.$('.page-title').text()).toBe('Team Search');
+ expect(teamsTabView.$('.page-description').text()).toBe('Showing results for "foo"');
+ });
+
+ it('can clear a search', function () {
+ var requests = AjaxHelpers.requests(this),
+ teamsTabView = createTeamsTabView();
+ teamsTabView.browseTopic(TeamSpecHelpers.testTopicID);
+ AjaxHelpers.respondWithJson(requests, {});
+
+ // Perform a search
+ teamsTabView.$('.search-field').val('foo');
+ teamsTabView.$('.action-search').click();
+ AjaxHelpers.respondWithJson(requests, {});
+
+ // Clear the search and submit it again
+ teamsTabView.$('.search-field').val('');
+ teamsTabView.$('.action-search').click();
+ verifyTeamsRequest(requests, {
+ order_by: 'last_activity_at',
+ text_search: ''
+ });
+ AjaxHelpers.respondWithJson(requests, {});
+ expect(teamsTabView.$('.page-title').text()).toBe('Test Topic 1');
+ expect(teamsTabView.$('.page-description').text()).toBe('Test description 1');
+ });
+
+ it('clears the search when navigating away and then back', function () {
+ var requests = AjaxHelpers.requests(this),
+ teamsTabView = createTeamsTabView();
+ teamsTabView.browseTopic(TeamSpecHelpers.testTopicID);
+ AjaxHelpers.respondWithJson(requests, {});
+
+ // Perform a search
+ teamsTabView.$('.search-field').val('foo');
+ teamsTabView.$('.action-search').click();
+ AjaxHelpers.respondWithJson(requests, {});
+
+ // Navigate back to the teams list
+ teamsTabView.$('.breadcrumbs a').last().click();
+ verifyTeamsRequest(requests, {
+ order_by: 'last_activity_at',
+ text_search: ''
+ });
+ AjaxHelpers.respondWithJson(requests, {});
+ expect(teamsTabView.$('.page-title').text()).toBe('Test Topic 1');
+ expect(teamsTabView.$('.page-description').text()).toBe('Test description 1');
+ });
+
+ it('does not switch to showing results when the search returns an error', function () {
+ var requests = AjaxHelpers.requests(this),
+ teamsTabView = createTeamsTabView();
+ teamsTabView.browseTopic(TeamSpecHelpers.testTopicID);
+ AjaxHelpers.respondWithJson(requests, {});
+
+ // Perform a search
+ teamsTabView.$('.search-field').val('foo');
+ teamsTabView.$('.action-search').click();
+ AjaxHelpers.respondWithError(requests);
+ expect(teamsTabView.$('.page-title').text()).toBe('Test Topic 1');
+ expect(teamsTabView.$('.page-description').text()).toBe('Test description 1');
+ expect(teamsTabView.$('.search-field').val(), 'foo');
+ });
+ });
});
});
diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/topic_teams_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/topic_teams_spec.js
index 415ada244c..a67a5298dc 100644
--- a/lms/djangoapps/teams/static/teams/js/spec/views/topic_teams_spec.js
+++ b/lms/djangoapps/teams/static/teams/js/spec/views/topic_teams_spec.js
@@ -11,14 +11,11 @@ define([
var createTopicTeamsView = function(options) {
return new TopicTeamsView({
el: '.teams-container',
+ model: TeamSpecHelpers.createMockTopic(),
collection: options.teams || TeamSpecHelpers.createMockTeams(),
teamMemberships: options.teamMemberships || TeamSpecHelpers.createMockTeamMemberships(),
showActions: true,
- teamParams: {
- topicID: 'test-topic',
- countries: TeamSpecHelpers.testCountries,
- languages: TeamSpecHelpers.testLanguages
- }
+ context: TeamSpecHelpers.testContext
}).render();
};
@@ -27,8 +24,8 @@ define([
options = {showActions: true};
}
var expectedTitle = 'Are you having trouble finding a team to join?',
- expectedMessage = '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.',
+ expectedMessage = 'Browse teams in other topics or search teams in this topic. ' +
+ 'If you still can\'t find a team to join, create a new team in this topic.',
title = teamsView.$('.title').text().trim(),
message = teamsView.$('.copy').text().trim();
if (options.showActions) {
@@ -65,17 +62,16 @@ define([
var emptyMembership = TeamSpecHelpers.createMockTeamMemberships([]),
teamsView = createTopicTeamsView({ teamMemberships: emptyMembership });
spyOn(Backbone.history, 'navigate');
- teamsView.$('a.browse-teams').click();
+ teamsView.$('.browse-teams').click();
expect(Backbone.history.navigate.calls[0].args).toContain('browse');
});
- it('can search teams', function () {
+ it('gives the search field focus when clicking on the search teams link', function () {
var emptyMembership = TeamSpecHelpers.createMockTeamMemberships([]),
teamsView = createTopicTeamsView({ teamMemberships: emptyMembership });
- spyOn(Backbone.history, 'navigate');
- teamsView.$('a.search-teams').click();
- // TODO! Should be updated once team description search feature is available
- expect(Backbone.history.navigate.calls[0].args).toContain('browse');
+ spyOn($.fn, 'focus').andCallThrough();
+ teamsView.$('.search-teams').click();
+ expect(teamsView.$('.search-field').first().focus).toHaveBeenCalled();
});
it('can show the create team modal', function () {
@@ -83,7 +79,9 @@ define([
teamsView = createTopicTeamsView({ teamMemberships: emptyMembership });
spyOn(Backbone.history, 'navigate');
teamsView.$('a.create-team').click();
- expect(Backbone.history.navigate.calls[0].args).toContain('topics/test-topic/create-team');
+ expect(Backbone.history.navigate.calls[0].args).toContain(
+ 'topics/' + TeamSpecHelpers.testTopicID + '/create-team'
+ );
});
it('does not show actions for a user already in a team', function () {
@@ -118,13 +116,13 @@ define([
verifyActions(teamsView, {showActions: true});
teamMemberships.teamEvents.trigger('teams:update', { action: 'create' });
teamsView.render();
- AjaxHelpers.expectJsonRequestURL(
+ AjaxHelpers.expectRequestURL(
requests,
'foo',
{
expand : 'team',
username : 'testUser',
- course_id : 'my/course/id',
+ course_id : TeamSpecHelpers.testCourseID,
page : '1',
page_size : '10'
}
diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/topics_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/topics_spec.js
index abf39839c4..049339675f 100644
--- a/lms/djangoapps/teams/static/teams/js/spec/views/topics_spec.js
+++ b/lms/djangoapps/teams/static/teams/js/spec/views/topics_spec.js
@@ -10,7 +10,8 @@ define([
return new TopicsView({
teamEvents: TeamSpecHelpers.teamEvents,
el: '.topics-container',
- collection: topicCollection
+ collection: topicCollection,
+ context: TeamSpecHelpers.createMockContext()
}).render();
};
@@ -48,14 +49,15 @@ define([
topicsView = createTopicsView();
triggerUpdateEvent(topicsView);
- AjaxHelpers.expectJsonRequestURL(
+ AjaxHelpers.expectRequestURL(
requests,
- 'api/teams/topics',
+ TeamSpecHelpers.testContext.topicUrl,
{
- course_id : 'my/course/id',
- page : '1',
- page_size : '5', // currently the page size is determined by the size of the collection
- order_by : 'name'
+ course_id: TeamSpecHelpers.testCourseID,
+ page: '1',
+ page_size: '5', // currently the page size is determined by the size of the collection
+ order_by: 'name',
+ text_search: ''
}
);
});
@@ -66,14 +68,15 @@ define([
// Staff are not immediately added to the team, but may choose to join after the create event.
triggerUpdateEvent(topicsView, true);
- AjaxHelpers.expectJsonRequestURL(
+ AjaxHelpers.expectRequestURL(
requests,
- 'api/teams/topics',
+ TeamSpecHelpers.testContext.topicUrl,
{
- course_id : 'my/course/id',
- page : '1',
- page_size : '5', // currently the page size is determined by the size of the collection
- order_by : 'name'
+ course_id: TeamSpecHelpers.testCourseID,
+ page: '1',
+ page_size: '5', // currently the page size is determined by the size of the collection
+ order_by: 'name',
+ text_search: ''
}
);
});
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 08d3ada6ba..2c51ef5ae2 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
@@ -3,13 +3,15 @@ define([
'underscore',
'teams/js/collections/team',
'teams/js/collections/team_membership',
- 'teams/js/collections/topic'
-], function (Backbone, _, TeamCollection, TeamMembershipCollection, TopicCollection) {
+ 'teams/js/collections/topic',
+ 'teams/js/models/topic'
+], function (Backbone, _, TeamCollection, TeamMembershipCollection, TopicCollection, TopicModel) {
'use strict';
var createMockPostResponse, createMockDiscussionResponse, createAnnotatedContentInfo, createMockThreadResponse,
- createMockTopicData, createMockTopicCollection,
+ createMockTopicData, createMockTopicCollection, createMockTopic,
testCourseID = 'course/1',
testUser = 'testUser',
+ testTopicID = 'test-topic-1',
testTeamDiscussionID = "12345",
teamEvents = _.clone(Backbone.Events),
testCountries = [
@@ -52,7 +54,7 @@ define([
},
{
teamEvents: teamEvents,
- course_id: 'my/course/id',
+ course_id: testCourseID,
parse: true
}
);
@@ -81,18 +83,22 @@ define([
num_pages: 3,
current_page: 1,
start: 0,
+ sort_order: 'last_activity_at',
results: teamMembershipData
},
- _.extend(_.extend({}, {
+ _.extend(
+ {},
+ {
teamEvents: teamEvents,
- course_id: 'my/course/id',
+ course_id: testCourseID,
parse: true,
- url: 'api/teams/team_memberships',
+ url: testContext.teamMembershipsUrl,
username: testUser,
privileged: false,
staff: false
- }),
- options)
+ },
+ options
+ )
);
};
@@ -144,7 +150,7 @@ define([
group_id: 1,
endorsed: false
},
- options || {}
+ options
);
};
@@ -228,21 +234,56 @@ define([
context: "standalone",
endorsed: false
},
- options || {}
+ options
);
};
createMockTopicData = function (startIndex, stopIndex) {
return _.map(_.range(startIndex, stopIndex + 1), function (i) {
return {
- "description": "description " + i,
- "name": "topic " + i,
- "id": "id " + i,
+ "description": "Test description " + i,
+ "name": "Test Topic " + i,
+ "id": "test-topic-" + i,
"team_count": 0
};
});
};
+ createMockTopic = function(options) {
+ return new TopicModel(_.extend(
+ {
+ id: testTopicID,
+ name: 'Test Topic 1',
+ description: 'Test description 1'
+ },
+ options
+ ));
+ };
+
+ var testContext = {
+ courseID: testCourseID,
+ topics: {
+ count: 5,
+ num_pages: 1,
+ current_page: 1,
+ start: 0,
+ results: createMockTopicData(1, 5)
+ },
+ maxTeamSize: 6,
+ languages: testLanguages,
+ countries: testCountries,
+ topicUrl: '/api/team/v0/topics/topic_id,' + testCourseID,
+ teamsUrl: '/api/team/v0/teams/',
+ teamsDetailUrl: '/api/team/v0/teams/team_id',
+ teamMembershipsUrl: '/api/team/v0/team_memberships/',
+ teamMembershipDetailUrl: '/api/team/v0/team_membership/team_id,' + testUser,
+ userInfo: createMockUserInfo()
+ };
+
+ var createMockContext = function(options) {
+ return _.extend({}, testContext, options);
+ };
+
createMockTopicCollection = function (topicData) {
topicData = topicData !== undefined ? topicData : createMockTopicData(1, 5);
@@ -253,13 +294,13 @@ define([
num_pages: 2,
start: 0,
results: topicData,
- sort_order: "name"
+ sort_order: 'name'
},
{
teamEvents: teamEvents,
- course_id: 'my/course/id',
+ course_id: testCourseID,
parse: true,
- url: 'api/teams/topics'
+ url: testContext.topicUrl
}
);
};
@@ -268,14 +309,18 @@ define([
teamEvents: teamEvents,
testCourseID: testCourseID,
testUser: testUser,
+ testTopicID: testTopicID,
testCountries: testCountries,
testLanguages: testLanguages,
testTeamDiscussionID: testTeamDiscussionID,
+ testContext: testContext,
createMockTeamData: createMockTeamData,
createMockTeams: createMockTeams,
createMockTeamMembershipsData: createMockTeamMembershipsData,
createMockTeamMemberships: createMockTeamMemberships,
createMockUserInfo: createMockUserInfo,
+ createMockContext: createMockContext,
+ createMockTopic: createMockTopic,
createMockPostResponse: createMockPostResponse,
createMockDiscussionResponse: createMockDiscussionResponse,
createAnnotatedContentInfo: createAnnotatedContentInfo,
diff --git a/lms/djangoapps/teams/static/teams/js/teams_tab_factory.js b/lms/djangoapps/teams/static/teams/js/teams_tab_factory.js
index 441e1e11f6..d736d4da99 100644
--- a/lms/djangoapps/teams/static/teams/js/teams_tab_factory.js
+++ b/lms/djangoapps/teams/static/teams/js/teams_tab_factory.js
@@ -4,7 +4,10 @@
define(['jquery', 'underscore', 'backbone', 'teams/js/views/teams_tab'],
function ($, _, Backbone, TeamsTabView) {
return function (options) {
- var teamsTab = new TeamsTabView(_.extend(options, {el: $('.teams-content')}));
+ var teamsTab = new TeamsTabView({
+ el: $('.teams-content'),
+ context: options
+ });
teamsTab.start();
};
});
diff --git a/lms/djangoapps/teams/static/teams/js/views/edit_team.js b/lms/djangoapps/teams/static/teams/js/views/edit_team.js
index 34f20d6e0f..fc6ca4616e 100644
--- a/lms/djangoapps/teams/static/teams/js/views/edit_team.js
+++ b/lms/djangoapps/teams/static/teams/js/views/edit_team.js
@@ -21,22 +21,19 @@
initialize: function(options) {
this.teamEvents = options.teamEvents;
- this.courseID = options.teamParams.courseID;
- this.topicID = options.teamParams.topicID;
+ this.context = options.context;
+ this.topic = options.topic;
this.collection = options.collection;
- this.teamsUrl = options.teamParams.teamsUrl;
- this.languages = options.teamParams.languages;
- this.countries = options.teamParams.countries;
- this.teamsDetailUrl = options.teamParams.teamsDetailUrl;
this.action = options.action;
if (this.action === 'create') {
this.teamModel = new TeamModel({});
- this.teamModel.url = this.teamsUrl;
+ this.teamModel.url = this.context.teamsUrl;
this.primaryButtonTitle = gettext("Create");
} else if(this.action === 'edit' ) {
this.teamModel = options.model;
- this.teamModel.url = this.teamsDetailUrl.replace('team_id', options.model.get('id')) + '?expand=user';
+ this.teamModel.url = this.context.teamsDetailUrl.replace('team_id', options.model.get('id')) +
+ '?expand=user';
this.primaryButtonTitle = gettext("Update");
}
@@ -63,7 +60,7 @@
required: false,
showMessages: false,
titleIconName: 'fa-comment-o',
- options: this.languages,
+ options: this.context.languages,
helpMessage: gettext('The language that team members primarily use to communicate with each other.')
});
@@ -74,7 +71,7 @@
required: false,
showMessages: false,
titleIconName: 'fa-globe',
- options: this.countries,
+ options: this.context.countries,
helpMessage: gettext('The country that team members primarily identify with.')
});
},
@@ -117,8 +114,8 @@
};
if (this.action === 'create') {
- data.course_id = this.courseID;
- data.topic_id = this.topicID;
+ data.course_id = this.context.courseID;
+ data.topic_id = this.topic.id;
} else if (this.action === 'edit' ) {
saveOptions.patch = true;
saveOptions.contentType = 'application/merge-patch+json';
@@ -137,7 +134,7 @@
team: result
});
Backbone.history.navigate(
- 'teams/' + view.topicID + '/' + view.teamModel.id,
+ 'teams/' + view.topic.id + '/' + view.teamModel.id,
{trigger: true}
);
})
@@ -208,9 +205,9 @@
event.preventDefault();
var url;
if (this.action === 'create') {
- url = 'topics/' + this.topicID;
+ url = 'topics/' + this.topic.id;
} else if (this.action === 'edit' ) {
- url = 'teams/' + this.topicID + '/' + this.teamModel.get('id');
+ url = 'teams/' + this.topic.id + '/' + this.teamModel.get('id');
}
Backbone.history.navigate(url, {trigger: true});
}
diff --git a/lms/djangoapps/teams/static/teams/js/views/team_profile.js b/lms/djangoapps/teams/static/teams/js/views/team_profile.js
index 10beac0a7e..0f49d7c934 100644
--- a/lms/djangoapps/teams/static/teams/js/views/team_profile.js
+++ b/lms/djangoapps/teams/static/teams/js/views/team_profile.js
@@ -18,15 +18,11 @@
},
initialize: function (options) {
this.teamEvents = options.teamEvents;
- this.courseID = options.courseID;
- this.maxTeamSize = options.maxTeamSize;
- this.requestUsername = options.requestUsername;
- this.isPrivileged = options.isPrivileged;
- this.teamMembershipDetailUrl = options.teamMembershipDetailUrl;
+ this.context = options.context;
this.setFocusToHeaderFunc = options.setFocusToHeaderFunc;
- this.countries = TeamUtils.selectorOptionsArrayToHashWithBlank(options.countries);
- this.languages = TeamUtils.selectorOptionsArrayToHashWithBlank(options.languages);
+ this.countries = TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.countries);
+ this.languages = TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.languages);
this.listenTo(this.model, "change", this.render);
},
@@ -34,18 +30,17 @@
render: function () {
var memberships = this.model.get('membership'),
discussionTopicID = this.model.get('discussion_topic_id'),
- isMember = TeamUtils.isUserMemberOfTeam(memberships, this.requestUsername);
+ isMember = TeamUtils.isUserMemberOfTeam(memberships, this.context.userInfo.username);
this.$el.html(_.template(teamTemplate, {
- courseID: this.courseID,
+ courseID: this.context.courseID,
discussionTopicID: discussionTopicID,
- readOnly: !(this.isPrivileged || isMember),
+ readOnly: !(this.context.userInfo.privileged || isMember),
country: this.countries[this.model.get('country')],
language: this.languages[this.model.get('language')],
- membershipText: TeamUtils.teamCapacityText(memberships.length, this.maxTeamSize),
+ membershipText: TeamUtils.teamCapacityText(memberships.length, this.context.maxTeamSize),
isMember: isMember,
- hasCapacity: memberships.length < this.maxTeamSize,
+ hasCapacity: memberships.length < this.context.maxTeamSize,
hasMembers: memberships.length >= 1
-
}));
this.discussionView = new TeamDiscussionView({
el: this.$('.discussion-module')
@@ -84,7 +79,7 @@
function() {
$.ajax({
type: 'DELETE',
- url: view.teamMembershipDetailUrl.replace('team_id', view.model.get('id'))
+ url: view.context.teamMembershipDetailUrl.replace('team_id', view.model.get('id'))
}).done(function (data) {
view.model.fetch()
.done(function() {
diff --git a/lms/djangoapps/teams/static/teams/js/views/team_profile_header_actions.js b/lms/djangoapps/teams/static/teams/js/views/team_profile_header_actions.js
index 292ee796f3..e05783ca71 100644
--- a/lms/djangoapps/teams/static/teams/js/views/team_profile_header_actions.js
+++ b/lms/djangoapps/teams/static/teams/js/views/team_profile_header_actions.js
@@ -21,21 +21,19 @@
initialize: function(options) {
this.teamEvents = options.teamEvents;
this.template = _.template(teamProfileHeaderActionsTemplate);
- this.courseID = options.courseID;
- this.maxTeamSize = options.maxTeamSize;
- this.currentUsername = options.currentUsername;
- this.teamMembershipsUrl = options.teamMembershipsUrl;
+ this.context = options.context;
this.showEditButton = options.showEditButton;
- this.topicID = options.topicID;
+ this.topic = options.topic;
this.listenTo(this.model, "change", this.render);
},
render: function() {
var view = this,
+ username = this.context.userInfo.username,
message,
showJoinButton,
teamHasSpace;
- this.getUserTeamInfo(this.currentUsername, view.maxTeamSize).done(function (info) {
+ this.getUserTeamInfo(username, this.context.maxTeamSize).done(function (info) {
teamHasSpace = info.teamHasSpace;
// if user is the member of current team then we wouldn't show anything
@@ -62,8 +60,8 @@
var view = this;
$.ajax({
type: 'POST',
- url: view.teamMembershipsUrl,
- data: {'username': view.currentUsername, 'team_id': view.model.get('id')}
+ url: view.context.teamMembershipsUrl,
+ data: {'username': view.context.userInfo.username, 'team_id': view.model.get('id')}
}).done(function (data) {
view.model.fetch()
.done(function() {
@@ -97,8 +95,8 @@
var view = this;
$.ajax({
type: 'GET',
- url: view.teamMembershipsUrl,
- data: {'username': username, 'course_id': view.courseID}
+ url: view.context.teamMembershipsUrl,
+ data: {'username': username, 'course_id': view.context.courseID}
}).done(function (data) {
info.alreadyMember = (data.count > 0);
info.memberOfCurrentTeam = false;
@@ -115,9 +113,13 @@
return deferred.promise();
},
+
editTeam: function (event) {
event.preventDefault();
- Backbone.history.navigate('topics/' + this.topicID + '/' + this.model.get('id') +'/edit-team', {trigger: true});
+ Backbone.history.navigate(
+ 'topics/' + this.topic.id + '/' + this.model.get('id') +'/edit-team',
+ {trigger: true}
+ );
}
});
});
diff --git a/lms/djangoapps/teams/static/teams/js/views/teams.js b/lms/djangoapps/teams/static/teams/js/views/teams.js
index 697dae049a..be8d8b14f6 100644
--- a/lms/djangoapps/teams/static/teams/js/views/teams.js
+++ b/lms/djangoapps/teams/static/teams/js/views/teams.js
@@ -18,14 +18,14 @@
initialize: function (options) {
this.topic = options.topic;
this.teamMemberships = options.teamMemberships;
- this.teamParams = options.teamParams;
+ this.context = options.context;
this.itemViewClass = TeamCardView.extend({
router: options.router,
topic: options.topic,
- maxTeamSize: options.maxTeamSize,
+ maxTeamSize: this.context.maxTeamSize,
srInfo: this.srInfo,
- countries: TeamUtils.selectorOptionsArrayToHashWithBlank(options.teamParams.countries),
- languages: TeamUtils.selectorOptionsArrayToHashWithBlank(options.teamParams.languages)
+ countries: TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.countries),
+ languages: TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.languages)
});
PaginatedView.prototype.initialize.call(this);
}
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 461159ecc3..309082bbc9 100644
--- a/lms/djangoapps/teams/static/teams/js/views/teams_tab.js
+++ b/lms/djangoapps/teams/static/teams/js/views/teams_tab.js
@@ -4,6 +4,7 @@
define(['backbone',
'underscore',
'gettext',
+ 'common/js/components/views/search_field',
'js/components/header/views/header',
'js/components/header/models/header',
'js/components/tabbed/views/tabbed_view',
@@ -19,12 +20,12 @@
'teams/js/views/edit_team',
'teams/js/views/team_profile_header_actions',
'text!teams/templates/teams_tab.underscore'],
- function (Backbone, _, gettext, HeaderView, HeaderModel, TabbedView,
+ function (Backbone, _, gettext, SearchFieldView, HeaderView, HeaderModel, TabbedView,
TopicModel, TopicCollection, TeamModel, TeamCollection, TeamMembershipCollection,
TopicsView, TeamProfileView, MyTeamsView, TopicTeamsView, TeamEditView,
TeamProfileHeaderActionsView, teamsTemplate) {
var TeamsHeaderModel = HeaderModel.extend({
- initialize: function (attributes) {
+ initialize: function () {
_.extend(this.defaults, {nav_aria_label: gettext('teams')});
HeaderModel.prototype.initialize.call(this);
}
@@ -48,18 +49,7 @@
var TeamTabView = Backbone.View.extend({
initialize: function(options) {
var router;
- this.courseID = options.courseID;
- this.topics = options.topics;
- this.topicUrl = options.topicUrl;
- this.teamsUrl = options.teamsUrl;
- this.teamsDetailUrl = options.teamsDetailUrl;
- this.teamMembershipsUrl = options.teamMembershipsUrl;
- this.teamMembershipDetailUrl = options.teamMembershipDetailUrl;
- this.maxTeamSize = options.maxTeamSize;
- this.languages = options.languages;
- this.countries = options.countries;
- this.userInfo = options.userInfo;
- this.teamsBaseUrl = options.teamsBaseUrl;
+ this.context = options.context;
// This slightly tedious approach is necessary
// to use regular expressions within Backbone
// routes, allowing us to capture which tab
@@ -74,6 +64,7 @@
// being picked up by the backbone router.
}, this)],
['topics/:topic_id(/)', _.bind(this.browseTopic, this)],
+ ['topics/:topic_id/search(/)', _.bind(this.searchTeams, this)],
['topics/:topic_id/create-team(/)', _.bind(this.newTeam, this)],
['topics/:topic_id/:team_id/edit-team(/)', _.bind(this.editTeam, this)],
['teams/:topic_id/:team_id(/)', _.bind(this.browseTeam, this)],
@@ -87,14 +78,14 @@
this.teamEvents = _.clone(Backbone.Events);
this.teamMemberships = new TeamMembershipCollection(
- this.userInfo.team_memberships_data,
+ this.context.userInfo.team_memberships_data,
{
teamEvents: this.teamEvents,
- url: this.teamMembershipsUrl,
- course_id: this.courseID,
- username: this.userInfo.username,
- privileged: this.userInfo.privileged,
- staff: this.userInfo.staff,
+ url: this.context.teamMembershipsUrl,
+ course_id: this.context.courseID,
+ username: this.context.userInfo.username,
+ privileged: this.context.userInfo.privileged,
+ staff: this.context.userInfo.staff,
parse: true
}
).bootstrap();
@@ -102,23 +93,17 @@
this.myTeamsView = new MyTeamsView({
router: this.router,
teamEvents: this.teamEvents,
+ context: this.context,
collection: this.teamMemberships,
- teamMemberships: this.teamMemberships,
- maxTeamSize: this.maxTeamSize,
- teamParams: {
- courseID: this.courseID,
- teamsUrl: this.teamsUrl,
- languages: this.languages,
- countries: this.countries
- }
+ teamMemberships: this.teamMemberships
});
this.topicsCollection = new TopicCollection(
- this.topics,
+ this.context.topics,
{
teamEvents: this.teamEvents,
- url: options.topicsUrl,
- course_id: this.courseID,
+ url: this.context.topicsUrl,
+ course_id: this.context.courseID,
parse: true
}
).bootstrap();
@@ -129,21 +114,19 @@
collection: this.topicsCollection
});
- this.mainView = this.tabbedView = new ViewWithHeader({
- header: new HeaderView({
- model: new TeamsHeaderModel({
- 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."),
- title: gettext("Teams")
- })
- }),
- main: new TabbedView({
+ 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({
tabs: [{
title: gettext('My Team'),
url: 'my-teams',
view: this.myTeamsView
}, {
title: interpolate(
- // Translators: sr_start and sr_end surround text meant only for screen readers. The whole string will be shown to users as "Browse teams" if they are using a screenreader, and "Browse" otherwise.
+ // Translators: sr_start and sr_end surround text meant only for screen readers.
+ // The whole string will be shown to users as "Browse teams" if they are using a
+ // screenreader, and "Browse" otherwise.
gettext("Browse %(sr_start)s teams %(sr_end)s"),
{"sr_start": '', "sr_end": ''}, true
),
@@ -190,36 +173,50 @@
});
},
+ /**
+ * Show the search results for a team.
+ */
+ searchTeams: function (topicID) {
+ var view = this;
+ if (!this.teamsCollection) {
+ this.router.navigate('topics/' + topicID, {trigger: true});
+ } else {
+ this.getTopic(topicID).done(function (topic) {
+ view.mainView = view.createTeamsListView({
+ topic: topic,
+ collection: view.teamsCollection,
+ title: gettext('Team Search'),
+ description: interpolate(
+ gettext('Showing results for "%(searchString)s"'),
+ { searchString: view.teamsCollection.searchString },
+ true
+ ),
+ breadcrumbs: view.createBreadcrumbs(topic),
+ showSortControls: false
+ });
+ view.render();
+ });
+ }
+ },
+
/**
* Render the create new team form.
*/
newTeam: function (topicID) {
- var self = this,
- createViewWithHeader;
+ var view = this;
this.getTopic(topicID).done(function (topic) {
- var view = new TeamEditView({
- action: 'create',
- teamEvents: self.teamEvents,
- teamParams: {
- courseID: self.courseID,
- topicID: topic.get('id'),
- teamsUrl: self.teamsUrl,
- topicName: topic.get('name'),
- languages: self.languages,
- countries: self.countries,
- teamsDetailUrl: self.teamsDetailUrl
- }
+ view.mainView = view.createViewWithHeader({
+ parentTopic: topic,
+ title: gettext("Create a New Team"),
+ description: gettext("Create a new team if you can't find an existing team to join, or if you would like to learn with friends you know."),
+ mainView: new TeamEditView({
+ action: 'create',
+ teamEvents: view.teamEvents,
+ context: view.context,
+ topic: topic
+ })
});
- createViewWithHeader = self.createViewWithHeader({
- mainView: view,
- subject: {
- name: gettext("Create a New Team"),
- description: gettext("Create a new team if you can't find existing teams to join, or if you would like to learn with friends you know.")
- },
- parentTopic: topic
- });
- self.mainView = createViewWithHeader;
- self.render();
+ view.render();
});
},
@@ -234,27 +231,17 @@
var view = new TeamEditView({
action: 'edit',
teamEvents: self.teamEvents,
- teamParams: {
- courseID: self.courseID,
- topicID: topic.get('id'),
- teamsUrl: self.teamsUrl,
- topicName: topic.get('name'),
- languages: self.languages,
- countries: self.countries,
- teamsDetailUrl: self.teamsDetailUrl
- },
+ context: self.context,
+ topic: topic,
model: team
});
editViewWithHeader = self.createViewWithHeader({
- mainView: view,
- subject: {
- name: gettext("Edit Team"),
- description: gettext("If you make significant changes, make sure you notify members of the team before making these changes.")
- },
- parentTeam: team,
- parentTopic: topic
- }
- );
+ title: gettext("Edit Team"),
+ description: gettext("If you make significant changes, make sure you notify members of the team before making these changes."),
+ mainView: view,
+ topic: topic,
+ team: team
+ });
self.mainView = editViewWithHeader;
self.render();
});
@@ -267,54 +254,74 @@
getTeamsView: function (topicID) {
// Lazily load the teams-for-topic view in
// order to avoid making an extra AJAX call.
- var self = this,
- router = this.router,
+ var view = this,
deferred = $.Deferred();
- if (this.teamsCollection && this.teamsCollection.topic_id === topicID && this.teamsView) {
+ if (this.teamsView && this.teamsCollection && this.teamsCollection.topic_id === topicID) {
+ this.teamsCollection.setSearchString('');
deferred.resolve(this.teamsView);
} else {
this.getTopic(topicID)
.done(function(topic) {
var collection = new TeamCollection([], {
- teamEvents: self.teamEvents,
- course_id: self.courseID,
+ teamEvents: view.teamEvents,
+ course_id: view.context.courseID,
topic_id: topicID,
- url: self.teamsUrl,
+ url: view.context.teamsUrl,
per_page: 10
});
- self.teamsCollection = collection;
+ view.teamsCollection = collection;
collection.goTo(1)
.done(function() {
- var teamsView = new TopicTeamsView({
- router: self.router,
+ var teamsView = view.createTeamsListView({
topic: topic,
collection: collection,
- teamMemberships: self.teamMemberships,
- maxTeamSize: self.maxTeamSize,
- teamParams: {
- courseID: self.courseID,
- topicID: topic.get('id'),
- teamsUrl: self.teamsUrl,
- topicName: topic.get('name'),
- languages: self.languages,
- countries: self.countries,
- teamsDetailUrl: self.teamsDetailUrl
- }
+ showSortControls: true
});
- deferred.resolve(
- self.createViewWithHeader(
- {
- mainView: teamsView,
- subject: topic
- }
- )
- );
+ deferred.resolve(teamsView);
});
});
}
return deferred.promise();
},
+ createTeamsListView: function(options) {
+ var topic = options.topic,
+ collection = options.collection,
+ teamsView = new TopicTeamsView({
+ router: this.router,
+ context: this.context,
+ model: topic,
+ collection: collection,
+ teamMemberships: this.teamMemberships,
+ showSortControls: options.showSortControls
+ }),
+ searchFieldView = new SearchFieldView({
+ type: 'teams',
+ label: gettext('Search teams'),
+ collection: collection
+ }),
+ viewWithHeader = this.createViewWithHeader({
+ subject: topic,
+ mainView: teamsView,
+ headerActionsView: searchFieldView,
+ title: options.title,
+ description: options.description,
+ breadcrumbs: options.breadcrumbs
+ });
+ // Listen to requests to sync the collection and redirect it as follows:
+ // 1. If the collection includes a search, show the search results page
+ // 2. If not, then show the regular topic teams page
+ // Note: Backbone makes this a no-op if redirecting to the current page.
+ this.listenTo(collection, 'sync', function() {
+ if (collection.searchString) {
+ Backbone.history.navigate('topics/' + topic.get('id') + '/search', {trigger: true});
+ } else {
+ Backbone.history.navigate('topics/' + topic.get('id'), {trigger: true});
+ }
+ });
+ return viewWithHeader;
+ },
+
/**
* Browse to the team with the specified team ID belonging to the specified topic.
*/
@@ -338,34 +345,23 @@
*/
getBrowseTeamView: function (topicID, teamID) {
var self = this,
- deferred = $.Deferred(),
- courseID = this.courseID;
+ deferred = $.Deferred();
self.getTopic(topicID).done(function(topic) {
self.getTeam(teamID, true).done(function(team) {
var view = new TeamProfileView({
teamEvents: self.teamEvents,
router: self.router,
- courseID: courseID,
+ context: self.context,
model: team,
- maxTeamSize: self.maxTeamSize,
- isPrivileged: self.userInfo.privileged,
- requestUsername: self.userInfo.username,
- countries: self.countries,
- languages: self.languages,
- teamMembershipDetailUrl: self.teamMembershipDetailUrl,
setFocusToHeaderFunc: self.setFocusToHeader
});
var TeamProfileActionsView = new TeamProfileHeaderActionsView({
teamEvents: self.teamEvents,
- courseID: courseID,
+ context: self.context,
model: team,
- teamsUrl: self.teamsUrl,
- maxTeamSize: self.maxTeamSize,
- currentUsername: self.userInfo.username,
- teamMembershipsUrl: self.teamMembershipsUrl,
- topicID: topicID,
- showEditButton: self.userInfo.privileged || self.userInfo.staff
+ topic: topic,
+ showEditButton: self.context.userInfo.privileged || self.context.userInfo.staff
});
deferred.resolve(
self.createViewWithHeader(
@@ -382,52 +378,55 @@
return deferred.promise();
},
- createViewWithHeader: function (options) {
- var router = this.router,
- breadcrumbs, headerView,
- viewDescription, viewTitle;
- breadcrumbs = [{
+ createBreadcrumbs: function(topic, team) {
+ var breadcrumbs = [{
title: gettext('All Topics'),
url: '#browse'
}];
- if (options.parentTopic) {
+ if (topic) {
breadcrumbs.push({
- title: options.parentTopic.get('name'),
- url: '#topics/' + options.parentTopic.id
+ title: topic.get('name'),
+ url: '#topics/' + topic.id
});
- }
- if (options.parentTeam) {
- breadcrumbs.push({
- title: options.parentTeam.get('name'),
- url: '#teams/' + options.parentTopic.id + '/' + options.parentTeam.id
- });
- }
- if (options.subject instanceof Backbone.Model) {
- viewDescription = options.subject.get('description');
- viewTitle = options.subject.get('name');
-
- } else if (options.subject) {
- viewDescription = options.subject.description;
- viewTitle = options.subject.name;
- }
-
- headerView = new HeaderView({
- model: new TeamsHeaderModel({
- description: viewDescription,
- title: viewTitle,
- breadcrumbs: breadcrumbs
- }),
- headerActionsView: options.headerActionsView,
- events: {
- 'click nav.breadcrumbs a.nav-item': function (event) {
- var url = $(event.currentTarget).attr('href');
- event.preventDefault();
- router.navigate(url, {trigger: true});
- }
+ if (team) {
+ breadcrumbs.push({
+ title: team.get('name'),
+ url: '#teams/' + topic.id + '/' + team.id
+ });
}
+ }
+ return breadcrumbs;
+ },
+
+ createHeaderModel: function(options) {
+ var subject = options.subject,
+ breadcrumbs = options.breadcrumbs,
+ title = options.title || subject.get('name'),
+ description = options.description || subject.get('description');
+ if (!breadcrumbs) {
+ breadcrumbs = this.createBreadcrumbs(options.topic, options.team);
+ }
+ return new TeamsHeaderModel({
+ breadcrumbs: breadcrumbs,
+ title: title,
+ description: description
});
+ },
+
+ createViewWithHeader: function(options) {
+ var router = this.router;
return new ViewWithHeader({
- header: headerView,
+ header: new HeaderView({
+ model: this.createHeaderModel(options),
+ headerActionsView: options.headerActionsView,
+ events: {
+ 'click nav.breadcrumbs a.nav-item': function (event) {
+ var url = $(event.currentTarget).attr('href');
+ event.preventDefault();
+ router.navigate(url, {trigger: true});
+ }
+ }
+ }),
main: options.mainView
});
},
@@ -450,7 +449,7 @@
} else {
topic = new TopicModel({
id: topicID,
- url: self.topicUrl.replace('topic_id', topicID)
+ url: self.context.topicUrl.replace('topic_id', topicID)
});
topic.fetch()
.done(function() {
@@ -476,7 +475,7 @@
var team = this.teamsCollection ? this.teamsCollection.get(teamID) : null,
self = this,
deferred = $.Deferred(),
- teamUrl = this.teamsUrl + teamID + (expandUser ? '?expand=user': '');
+ teamUrl = this.context.teamsUrl + teamID + (expandUser ? '?expand=user': '');
if (team) {
team.url = teamUrl;
deferred.resolve(team);
@@ -570,11 +569,11 @@
* belongs to the team.
*/
readOnlyDiscussion: function (team) {
- var self = this;
+ var userInfo = this.context.userInfo;
return !(
- self.userInfo.privileged ||
+ userInfo.privileged ||
_.any(team.attributes.membership, function (membership) {
- return membership.user.username === self.userInfo.username;
+ return membership.user.username === userInfo.username;
})
);
}
diff --git a/lms/djangoapps/teams/static/teams/js/views/topic_card.js b/lms/djangoapps/teams/static/teams/js/views/topic_card.js
index c64ebcd619..b1d5292dde 100644
--- a/lms/djangoapps/teams/static/teams/js/views/topic_card.js
+++ b/lms/djangoapps/teams/static/teams/js/views/topic_card.js
@@ -36,6 +36,7 @@
configuration: 'square_card',
cardClass: 'topic-card',
+ pennant: gettext('Topic'),
title: function () { return this.model.get('name'); },
description: function () { return this.model.get('description'); },
details: function () { return this.detailViews; },
diff --git a/lms/djangoapps/teams/static/teams/js/views/topic_teams.js b/lms/djangoapps/teams/static/teams/js/views/topic_teams.js
index 26457b54f6..b545483d5d 100644
--- a/lms/djangoapps/teams/static/teams/js/views/topic_teams.js
+++ b/lms/djangoapps/teams/static/teams/js/views/topic_teams.js
@@ -15,6 +15,7 @@
},
initialize: function(options) {
+ this.showSortControls = options.showSortControls;
TeamsView.prototype.initialize.call(this, options);
},
@@ -24,21 +25,29 @@
this.collection.refresh(),
this.teamMemberships.refresh()
).done(function() {
- TeamsView.prototype.render.call(self);
+ TeamsView.prototype.render.call(self);
- if (self.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': '',
- 'search_span_start': '',
- 'create_span_start': '',
- 'span_end': ''
- }
- );
- self.$el.append(_.template(teamActionsTemplate, {message: message}));
- }
- });
+ if (self.teamMemberships.canUserCreateTeam()) {
+ var message = interpolate_text(
+ // Translators: this string is shown at the bottom of the teams page
+ // to find a team to join or else to create a new one. There are three
+ // links that need to be included in the message:
+ // 1. Browse teams in other topics
+ // 2. search teams
+ // 3. create a new team
+ // Be careful to start each link with the appropriate start indicator
+ // (e.g. {browse_span_start} for #1) and finish it with {span_end}.
+ _.escape(gettext("{browse_span_start}Browse teams in other topics{span_end} or {search_span_start}search teams{span_end} in this topic. 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': '',
+ 'search_span_start': '',
+ 'create_span_start': '',
+ 'span_end': ''
+ }
+ );
+ self.$el.append(_.template(teamActionsTemplate, {message: message}));
+ }
+ });
return this;
},
@@ -48,21 +57,25 @@
},
searchTeams: function (event) {
+ var searchField = $('.page-header-search .search-field');
event.preventDefault();
- // TODO! Will navigate to correct place once required functionality is available
- Backbone.history.navigate('browse', {trigger: true});
+ searchField.focus();
+ searchField.select();
+ $('html, body').animate({
+ scrollTop: 0
+ }, 500);
},
showCreateTeamForm: function (event) {
event.preventDefault();
- Backbone.history.navigate('topics/' + this.teamParams.topicID + '/create-team', {trigger: true});
+ Backbone.history.navigate('topics/' + this.model.id + '/create-team', {trigger: true});
},
createHeaderView: function () {
return new PagingHeader({
collection: this.options.collection,
srInfo: this.srInfo,
- showSortControls: true
+ showSortControls: this.showSortControls
});
}
});
diff --git a/lms/djangoapps/teams/views.py b/lms/djangoapps/teams/views.py
index 1e0b392ef7..325a076900 100644
--- a/lms/djangoapps/teams/views.py
+++ b/lms/djangoapps/teams/views.py
@@ -175,7 +175,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
* text_search: Searches for full word matches on the name, description,
country, and language fields. NOTES: Search is on full names for countries
and languages, not the ISO codes. Text_search cannot be requested along with
- with order_by. Searching relies on the ENABLE_TEAMS_SEARCH flag being set to True.
+ with order_by.
* order_by: Cannot be called along with with text_search. Must be one of the following:
@@ -311,7 +311,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
status=status.HTTP_400_BAD_REQUEST
)
- if 'text_search' in request.QUERY_PARAMS and 'order_by' in request.QUERY_PARAMS:
+ text_search = request.QUERY_PARAMS.get('text_search', None)
+ if text_search and request.QUERY_PARAMS.get('order_by', None):
return Response(
build_api_error(ugettext_noop("text_search and order_by cannot be provided together")),
status=status.HTTP_400_BAD_REQUEST
@@ -327,13 +328,12 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
return Response(error, status=status.HTTP_400_BAD_REQUEST)
result_filter.update({'topic_id': request.QUERY_PARAMS['topic_id']})
- if 'text_search' in request.QUERY_PARAMS and CourseTeamIndexer.search_is_enabled():
+ if text_search and CourseTeamIndexer.search_is_enabled():
search_engine = CourseTeamIndexer.engine()
- text_search = request.QUERY_PARAMS['text_search'].encode('utf-8')
result_filter.update({'course_id': course_id_string})
search_results = search_engine.search(
- query_string=text_search,
+ query_string=text_search.encode('utf-8'),
field_dictionary=result_filter,
size=MAXIMUM_SEARCH_SIZE,
)
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index 50d593591b..77b2779eda 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -631,7 +631,7 @@ PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM = ENV_TOKENS.get(
if FEATURES.get('ENABLE_COURSEWARE_SEARCH') or \
FEATURES.get('ENABLE_DASHBOARD_SEARCH') or \
FEATURES.get('ENABLE_COURSE_DISCOVERY') or \
- FEATURES.get('ENABLE_TEAMS_SEARCH'):
+ FEATURES.get('ENABLE_TEAMS'):
# Use ElasticSearch as the search engine herein
SEARCH_ENGINE = "search.elastic.ElasticSearchEngine"
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 0fe7508b63..86d0a47ce7 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -401,9 +401,6 @@ FEATURES = {
# Teams feature
'ENABLE_TEAMS': True,
- # Enable indexing teams for search
- 'ENABLE_TEAMS_SEARCH': False,
-
# Show video bumper in LMS
'ENABLE_VIDEO_BUMPER': False,
diff --git a/lms/envs/test.py b/lms/envs/test.py
index f237a9af23..77f9282572 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -485,9 +485,6 @@ FEATURES['ENABLE_EDXNOTES'] = True
# Enable teams feature for tests.
FEATURES['ENABLE_TEAMS'] = True
-# Enable indexing teams for search
-FEATURES['ENABLE_TEAMS_SEARCH'] = True
-
# Add milestones to Installed apps for testing
INSTALLED_APPS += ('milestones', 'openedx.core.djangoapps.call_stack_manager')
diff --git a/lms/static/js/components/header/views/header.js b/lms/static/js/components/header/views/header.js
index 73910aefb1..1686040c27 100644
--- a/lms/static/js/components/header/views/header.js
+++ b/lms/static/js/components/header/views/header.js
@@ -17,7 +17,7 @@
var json = this.model.attributes;
this.$el.html(this.template(json));
if (this.headerActionsView) {
- this.headerActionsView.setElement(this.$('.header-action-view')).render();
+ this.headerActionsView.setElement(this.$('.page-header-secondary')).render();
}
return this;
}
diff --git a/lms/static/sass/views/_teams.scss b/lms/static/sass/views/_teams.scss
index fee03e9455..393ae96873 100644
--- a/lms/static/sass/views/_teams.scss
+++ b/lms/static/sass/views/_teams.scss
@@ -49,16 +49,17 @@
.page-header.has-secondary {
- .page-header-main {
- display: inline-block;
- width: flex-grid(8,12);
- }
+ .page-header-main {
+ display: inline-block;
+ width: flex-grid(8,12);
+ }
- .page-header-secondary {
- display: inline-block;
- width: flex-grid(4,12);
- @include text-align(right);
- }
+ .page-header-secondary {
+ display: inline-block;
+ width: flex-grid(4,12);
+ @include text-align(right);
+ vertical-align: text-bottom;
+ }
}
// ui bits
@@ -104,8 +105,10 @@
.action-search {
@extend %button-reset;
- padding: ($baseline/4) ($baseline/2);
-
+ padding: ($baseline/5) ($baseline/2);
+ vertical-align: middle;
+ background-color: $gray-l3;
+ text-shadow: none;
.icon {
color: $gray-l3;
diff --git a/lms/templates/components/header/header.underscore b/lms/templates/components/header/header.underscore
index 5fd91299da..4ab011b38f 100644
--- a/lms/templates/components/header/header.underscore
+++ b/lms/templates/components/header/header.underscore
@@ -12,5 +12,5 @@
<%- title %>
<%- description %>
-
+