Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
2c774fd2
Commit
2c774fd2
authored
Aug 26, 2015
by
Peter Fogg
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow sorting of teams in a topic.
TNL-1937
parent
a8284058
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
151 additions
and
42 deletions
+151
-42
common/test/acceptance/pages/lms/teams.py
+35
-10
common/test/acceptance/tests/lms/test_teams.py
+95
-21
lms/djangoapps/teams/static/teams/js/collections/team.js
+5
-3
lms/djangoapps/teams/static/teams/js/views/teams.js
+0
-4
lms/djangoapps/teams/static/teams/js/views/topic_teams.js
+15
-4
lms/static/sass/views/_teams.scss
+1
-0
No files found.
common/test/acceptance/pages/lms/teams.py
View file @
2c774fd2
...
...
@@ -20,6 +20,25 @@ TEAMS_HEADER_CSS = '.teams-header'
CREATE_TEAM_LINK_CSS
=
'.create-team'
class
TeamCardsMixin
(
object
):
"""Provides common operations on the team card component."""
@property
def
team_cards
(
self
):
"""Get all the team cards on the page."""
return
self
.
q
(
css
=
'.team-card'
)
@property
def
team_names
(
self
):
"""Return the names of each team on the page."""
return
self
.
q
(
css
=
'h3.card-title'
)
.
map
(
lambda
e
:
e
.
text
)
.
results
@property
def
team_descriptions
(
self
):
"""Return the names of each team on the page."""
return
self
.
q
(
css
=
'p.card-description'
)
.
map
(
lambda
e
:
e
.
text
)
.
results
class
TeamsPage
(
CoursePage
):
"""
Teams page/tab.
...
...
@@ -84,7 +103,7 @@ class TeamsPage(CoursePage):
self
.
q
(
css
=
'a.nav-item'
)
.
filter
(
text
=
topic
)[
0
]
.
click
()
class
MyTeamsPage
(
CoursePage
,
PaginatedUIMixin
):
class
MyTeamsPage
(
CoursePage
,
PaginatedUIMixin
,
TeamCardsMixin
):
"""
The 'My Teams' tab of the Teams page.
"""
...
...
@@ -98,11 +117,6 @@ class MyTeamsPage(CoursePage, PaginatedUIMixin):
return
False
return
'is-active'
in
button_classes
[
0
]
@property
def
team_cards
(
self
):
"""Get all the team cards on the page."""
return
self
.
q
(
css
=
'.team-card'
)
class
BrowseTopicsPage
(
CoursePage
,
PaginatedUIMixin
):
"""
...
...
@@ -145,7 +159,7 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin):
self
.
wait_for_ajax
()
class
BrowseTeamsPage
(
CoursePage
,
PaginatedUIMixin
):
class
BrowseTeamsPage
(
CoursePage
,
PaginatedUIMixin
,
TeamCardsMixin
):
"""
The paginated UI for browsing teams within a Topic on the Teams
page.
...
...
@@ -179,9 +193,13 @@ class BrowseTeamsPage(CoursePage, PaginatedUIMixin):
return
self
.
q
(
css
=
TEAMS_HEADER_CSS
+
' .page-description'
)[
0
]
.
text
@property
def
team_cards
(
self
):
"""Get all the team cards on the page."""
return
self
.
q
(
css
=
'.team-card'
)
def
sort_order
(
self
):
"""Return the current sort order on the page."""
return
self
.
q
(
css
=
'#paging-header-select option'
)
.
filter
(
lambda
e
:
e
.
is_selected
()
)
.
results
[
0
]
.
text
.
strip
()
def
click_create_team_link
(
self
):
""" Click on create team link."""
...
...
@@ -204,6 +222,13 @@ class BrowseTeamsPage(CoursePage, PaginatedUIMixin):
query
.
first
.
click
()
self
.
wait_for_ajax
()
def
sort_teams_by
(
self
,
sort_order
):
"""Sort the list of teams by the given `sort_order`."""
self
.
q
(
css
=
'#paging-header-select option[value={sort_order}]'
.
format
(
sort_order
=
sort_order
)
)
.
click
()
self
.
wait_for_ajax
()
class
CreateOrEditTeamPage
(
CoursePage
,
FieldsMixin
):
"""
...
...
common/test/acceptance/tests/lms/test_teams.py
View file @
2c774fd2
...
...
@@ -3,7 +3,9 @@ Acceptance tests for the teams feature.
"""
import
json
import
random
import
time
from
dateutil.parser
import
parse
import
ddt
from
flaky
import
flaky
from
nose.plugins.attrib
import
attr
...
...
@@ -38,7 +40,7 @@ class TeamsTabBase(UniqueCourseTest):
"""Create `num_topics` test topics."""
return
[{
u"description"
:
i
,
u"name"
:
i
,
u"id"
:
i
}
for
i
in
map
(
str
,
xrange
(
num_topics
))]
def
create_teams
(
self
,
topic
,
num_teams
):
def
create_teams
(
self
,
topic
,
num_teams
,
time_between_creation
=
0
):
"""Create `num_teams` teams belonging to `topic`."""
teams
=
[]
for
i
in
xrange
(
num_teams
):
...
...
@@ -55,6 +57,10 @@ class TeamsTabBase(UniqueCourseTest):
data
=
json
.
dumps
(
team
),
headers
=
self
.
course_fixture
.
headers
)
# Sadly, this sleep is necessary in order to ensure that
# sorting by last_activity_at works correctly when running
# in Jenkins.
time
.
sleep
(
time_between_creation
)
teams
.
append
(
json
.
loads
(
response
.
text
))
return
teams
...
...
@@ -107,15 +113,8 @@ class TeamsTabBase(UniqueCourseTest):
self
.
assertEqual
(
expected_team
[
'name'
],
team_card_name
)
self
.
assertEqual
(
expected_team
[
'description'
],
team_card_description
)
team_cards
=
page
.
team_cards
team_card_names
=
[
team_card
.
find_element_by_css_selector
(
'.card-title'
)
.
text
for
team_card
in
team_cards
.
results
]
team_card_descriptions
=
[
team_card
.
find_element_by_css_selector
(
'.card-description'
)
.
text
for
team_card
in
team_cards
.
results
]
team_card_names
=
page
.
team_names
team_card_descriptions
=
page
.
team_descriptions
map
(
assert_team_equal
,
expected_teams
,
team_card_names
,
team_card_descriptions
)
def
verify_my_team_count
(
self
,
expected_number_of_teams
):
...
...
@@ -473,6 +472,7 @@ class BrowseTopicsTest(TeamsTabBase):
@attr
(
'shard_5'
)
@ddt.ddt
class
BrowseTeamsWithinTopicTest
(
TeamsTabBase
):
"""
Tests for browsing Teams within a Topic on the Teams page.
...
...
@@ -482,10 +482,25 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
def
setUp
(
self
):
super
(
BrowseTeamsWithinTopicTest
,
self
)
.
setUp
()
self
.
topic
=
{
u"name"
:
u"Example Topic"
,
u"id"
:
"example_topic"
,
u"description"
:
"Description"
}
self
.
set_team_configuration
({
'course_id'
:
self
.
course_id
,
'max_team_size'
:
10
,
'topics'
:
[
self
.
topic
]})
self
.
max_team_size
=
10
self
.
set_team_configuration
({
'course_id'
:
self
.
course_id
,
'max_team_size'
:
self
.
max_team_size
,
'topics'
:
[
self
.
topic
]
})
self
.
browse_teams_page
=
BrowseTeamsPage
(
self
.
browser
,
self
.
course_id
,
self
.
topic
)
self
.
topics_page
=
BrowseTopicsPage
(
self
.
browser
,
self
.
course_id
)
def
teams_with_default_sort_order
(
self
,
teams
):
"""Return a list of teams sorted according to the default ordering
(last_activity_at, with a secondary sort by open slots).
"""
return
sorted
(
sorted
(
teams
,
key
=
lambda
t
:
len
(
t
[
'membership'
]),
reverse
=
True
),
key
=
lambda
t
:
parse
(
t
[
'last_activity_at'
])
.
replace
(
microsecond
=
0
),
reverse
=
True
)
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'
])
...
...
@@ -504,11 +519,11 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
footer_visible (bool): Whether we expect to see the pagination
footer controls.
"""
alphabetized_teams
=
sorted
(
total_teams
,
key
=
lambda
team
:
team
[
'name'
]
)
self
.
assert
Equal
(
self
.
browse_teams_page
.
get_pagination_header_text
(),
pagination_header_text
)
sorted_teams
=
self
.
teams_with_default_sort_order
(
total_teams
)
self
.
assert
True
(
self
.
browse_teams_page
.
get_pagination_header_text
()
.
startswith
(
pagination_header_text
)
)
self
.
verify_teams
(
self
.
browse_teams_page
,
alphabetiz
ed_teams
[(
page_num
-
1
)
*
self
.
TEAMS_PAGE_SIZE
:
page_num
*
self
.
TEAMS_PAGE_SIZE
]
sort
ed_teams
[(
page_num
-
1
)
*
self
.
TEAMS_PAGE_SIZE
:
page_num
*
self
.
TEAMS_PAGE_SIZE
]
)
self
.
assertEqual
(
self
.
browse_teams_page
.
pagination_controls_visible
(),
...
...
@@ -516,6 +531,63 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
msg
=
'Expected paging footer to be '
+
'visible'
if
footer_visible
else
'invisible'
)
@ddt.data
(
(
'open_slots'
,
'last_activity_at'
,
True
),
(
'last_activity_at'
,
'open_slots'
,
True
)
)
@ddt.unpack
def
test_sort_teams
(
self
,
sort_order
,
secondary_sort_order
,
reverse
):
"""
Scenario: the user should be able to sort the list of teams by open slots or last activity
Given I am enrolled in a course with team configuration and topics
When I visit the Teams page
And I browse teams within a topic
Then I should see a list of teams for that topic
When I choose a sort order
Then I should see the paginated list of teams in that order
"""
teams
=
self
.
create_teams
(
self
.
topic
,
self
.
TEAMS_PAGE_SIZE
+
1
)
for
i
,
team
in
enumerate
(
random
.
sample
(
teams
,
len
(
teams
))):
for
_
in
range
(
i
):
user_info
=
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
)
.
visit
()
.
user_info
self
.
create_membership
(
user_info
[
'username'
],
team
[
'id'
])
team
[
'open_slots'
]
=
self
.
max_team_size
-
i
# Parse last activity date, removing microseconds because
# the Django ORM does not support them. Will be fixed in
# Django 1.8.
team
[
'last_activity_at'
]
=
parse
(
team
[
'last_activity_at'
])
.
replace
(
microsecond
=
0
)
# Re-authenticate as staff after creating users
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
,
staff
=
True
)
.
visit
()
self
.
browse_teams_page
.
visit
()
self
.
browse_teams_page
.
sort_teams_by
(
sort_order
)
team_names
=
self
.
browse_teams_page
.
team_names
self
.
assertEqual
(
len
(
team_names
),
self
.
TEAMS_PAGE_SIZE
)
sorted_teams
=
[
team
[
'name'
]
for
team
in
sorted
(
sorted
(
teams
,
key
=
lambda
t
:
t
[
secondary_sort_order
],
reverse
=
reverse
),
key
=
lambda
t
:
t
[
sort_order
],
reverse
=
reverse
)
][:
self
.
TEAMS_PAGE_SIZE
]
self
.
assertEqual
(
team_names
,
sorted_teams
)
def
test_default_sort_order
(
self
):
"""
Scenario: the list of teams should be sorted by last activity by default
Given I am enrolled in a course with team configuration and topics
When I visit the Teams page
And I browse teams within a topic
Then I should see a list of teams for that topic, sorted by last activity
"""
self
.
create_teams
(
self
.
topic
,
self
.
TEAMS_PAGE_SIZE
+
1
)
self
.
browse_teams_page
.
visit
()
self
.
assertEqual
(
self
.
browse_teams_page
.
sort_order
,
'last activity'
)
def
test_no_teams
(
self
):
"""
Scenario: Visiting a topic with no teams should not display any teams.
...
...
@@ -529,7 +601,7 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
"""
self
.
browse_teams_page
.
visit
()
self
.
verify_page_header
()
self
.
assert
Equal
(
self
.
browse_teams_page
.
get_pagination_header_text
(),
'Showing 0 out of 0 total'
)
self
.
assert
True
(
self
.
browse_teams_page
.
get_pagination_header_text
()
.
startswith
(
'Showing 0 out of 0 total'
)
)
self
.
assertEqual
(
len
(
self
.
browse_teams_page
.
team_cards
),
0
,
msg
=
'Expected to see no team cards'
)
self
.
assertFalse
(
self
.
browse_teams_page
.
pagination_controls_visible
(),
...
...
@@ -548,10 +620,12 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
And I should see a button to add a team
And I should not see a pagination footer
"""
teams
=
self
.
create_teams
(
self
.
topic
,
self
.
TEAMS_PAGE_SIZE
)
teams
=
self
.
teams_with_default_sort_order
(
self
.
create_teams
(
self
.
topic
,
self
.
TEAMS_PAGE_SIZE
,
time_between_creation
=
1
)
)
self
.
browse_teams_page
.
visit
()
self
.
verify_page_header
()
self
.
assert
Equal
(
self
.
browse_teams_page
.
get_pagination_header_text
(),
'Showing 1-10 out of 10 total'
)
self
.
assert
True
(
self
.
browse_teams_page
.
get_pagination_header_text
()
.
startswith
(
'Showing 1-10 out of 10 total'
)
)
self
.
verify_teams
(
self
.
browse_teams_page
,
teams
)
self
.
assertFalse
(
self
.
browse_teams_page
.
pagination_controls_visible
(),
...
...
@@ -571,7 +645,7 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
And when I click on the previous page button
Then I should see that I am on the first page of results
"""
teams
=
self
.
create_teams
(
self
.
topic
,
self
.
TEAMS_PAGE_SIZE
+
1
)
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
)
...
...
@@ -593,7 +667,7 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
When I input the first page
Then I should see that I am on the first page of results
"""
teams
=
self
.
create_teams
(
self
.
topic
,
self
.
TEAMS_PAGE_SIZE
+
10
)
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
)
...
...
@@ -848,13 +922,13 @@ class CreateTeamTest(TeamFormActions):
Then I should see teams list page without any new team.
And if I switch to "My Team", it shows no teams
"""
self
.
assert
Equal
(
self
.
browse_teams_page
.
get_pagination_header_text
(),
'Showing 0 out of 0 total'
)
self
.
assert
True
(
self
.
browse_teams_page
.
get_pagination_header_text
()
.
startswith
(
'Showing 0 out of 0 total'
)
)
self
.
verify_and_navigate_to_create_team_page
()
self
.
create_or_edit_team_page
.
cancel_team
()
self
.
assertTrue
(
self
.
browse_teams_page
.
is_browser_on_page
())
self
.
assert
Equal
(
self
.
browse_teams_page
.
get_pagination_header_text
(),
'Showing 0 out of 0 total'
)
self
.
assert
True
(
self
.
browse_teams_page
.
get_pagination_header_text
()
.
startswith
(
'Showing 0 out of 0 total'
)
)
self
.
teams_page
.
click_all_topics
()
self
.
teams_page
.
verify_team_count_in_first_topic
(
0
)
...
...
lms/djangoapps/teams/static/teams/js/collections/team.js
View file @
2c774fd2
...
...
@@ -3,6 +3,8 @@
define
([
'teams/js/collections/base'
,
'teams/js/models/team'
,
'gettext'
],
function
(
BaseCollection
,
TeamModel
,
gettext
)
{
var
TeamCollection
=
BaseCollection
.
extend
({
sortField
:
'last_activity_at'
,
initialize
:
function
(
teams
,
options
)
{
var
self
=
this
;
BaseCollection
.
prototype
.
initialize
.
call
(
this
,
options
);
...
...
@@ -12,14 +14,14 @@
topic_id
:
this
.
topic_id
=
options
.
topic_id
,
expand
:
'user'
,
course_id
:
function
()
{
return
encodeURIComponent
(
self
.
course_id
);
},
order_by
:
function
()
{
return
'name'
;
}
// TODO surface sort order in UI
order_by
:
function
()
{
return
this
.
sortField
;
}
},
BaseCollection
.
prototype
.
server_api
);
delete
this
.
server_api
.
sort_order
;
// Sort order is not specified for the Team API
this
.
registerSortableField
(
'
name'
,
gettext
(
'name
'
));
this
.
registerSortableField
(
'open_slots'
,
gettext
(
'open
_
slots'
));
this
.
registerSortableField
(
'
last_activity_at'
,
gettext
(
'last activity
'
));
this
.
registerSortableField
(
'open_slots'
,
gettext
(
'open
slots'
));
},
model
:
TeamModel
...
...
lms/djangoapps/teams/static/teams/js/views/teams.js
View file @
2c774fd2
...
...
@@ -10,10 +10,6 @@
var
TeamsView
=
PaginatedView
.
extend
({
type
:
'teams'
,
events
:
{
'click button.action'
:
''
// entry point for team creation
},
srInfo
:
{
id
:
"heading-browse-teams"
,
text
:
gettext
(
'All teams'
)
...
...
lms/djangoapps/teams/static/teams/js/views/topic_teams.js
View file @
2c774fd2
;(
function
(
define
)
{
'use strict'
;
define
([
'backbone'
,
'gettext'
,
'teams/js/views/teams'
,
'text!teams/templates/team-actions.underscore'
],
function
(
Backbone
,
gettext
,
TeamsView
,
teamActionsTemplate
)
{
define
([
'backbone'
,
'gettext'
,
'teams/js/views/teams'
,
'common/js/components/views/paging_header'
,
'text!teams/templates/team-actions.underscore'
],
function
(
Backbone
,
gettext
,
TeamsView
,
PagingHeader
,
teamActionsTemplate
)
{
var
TopicTeamsView
=
TeamsView
.
extend
({
events
:
{
'click a.browse-teams'
:
'browseTeams'
,
...
...
@@ -54,6 +57,14 @@
showCreateTeamForm
:
function
(
event
)
{
event
.
preventDefault
();
Backbone
.
history
.
navigate
(
'topics/'
+
this
.
teamParams
.
topicID
+
'/create-team'
,
{
trigger
:
true
});
},
createHeaderView
:
function
()
{
return
new
PagingHeader
({
collection
:
this
.
options
.
collection
,
srInfo
:
this
.
srInfo
,
showSortControls
:
true
});
}
});
...
...
lms/static/sass/views/_teams.scss
View file @
2c774fd2
...
...
@@ -164,6 +164,7 @@
label
{
// override
color
:
inherit
;
font-size
:
inherit
;
cursor
:
auto
;
}
.listing-sort-select
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment