Add searching to the teams view
TNL-1935
This commit is contained in:
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
71
common/static/common/js/components/views/search_field.js
Normal file
71
common/static/common/js/components/views/search_field.js
Normal file
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
105
common/static/common/js/spec/components/search_field_spec.js
Normal file
105
common/static/common/js/spec/components/search_field_spec.js
Normal file
@@ -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('<section class="test-search"></section>');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="page-header-search wrapper-search-<%= type %>">
|
||||
<form class="search-form">
|
||||
<div class="wrapper-search-input">
|
||||
<label for="search-<%= type %>" class="search-label">><%- searchLabel %></label>
|
||||
<input id="search-<%= type %>" class="search-field" type="text" value="<%- searchString %>" placeholder="<%- searchLabel %>" />
|
||||
<button type="button" class="action action-clear <%= searchLabel ? '' : 'is-hidden' %>" aria-label="<%- gettext('Clear search') %>">
|
||||
<i class="icon fa fa-times-circle" aria-hidden="true"></i><span class="sr"><%- gettext('Search') %></span>
|
||||
</button>
|
||||
</div>
|
||||
<button type="submit" class="action action-search"><span class="icon fa-search" aria-hidden="true"></span><span class="sr"><%- gettext('Search') %></span></button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -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);
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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'])
|
||||
)
|
||||
|
||||
|
||||
@@ -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: <course_team_id>")
|
||||
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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
},
|
||||
|
||||
onUpdate: function(event) {
|
||||
this.isStale = this.isStale || event.action === 'create';
|
||||
if (event.action === 'create') {
|
||||
this.isStale = true;
|
||||
}
|
||||
},
|
||||
|
||||
model: TopicModel
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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, {});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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: ''
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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}
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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": '<span class="sr">', "sr_end": '</span>'}, 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;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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; },
|
||||
|
||||
@@ -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': '<a class="browse-teams" href="">',
|
||||
'search_span_start': '<a class="search-teams" href="">',
|
||||
'create_span_start': '<a class="create-team" href="">',
|
||||
'span_end': '</a>'
|
||||
}
|
||||
);
|
||||
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': '<a class="browse-teams" href="">',
|
||||
'search_span_start': '<a class="search-teams" href="">',
|
||||
'create_span_start': '<a class="create-team" href="">',
|
||||
'span_end': '</a>'
|
||||
}
|
||||
);
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
<h2 class="page-title"><%- title %></h2>
|
||||
<p class="page-description"><%- description %></p>
|
||||
</div>
|
||||
<div class="header-action-view"></div>
|
||||
<div class="page-header-secondary"></div>
|
||||
</header>
|
||||
|
||||
Reference in New Issue
Block a user