Commit 07e4b885 by Tyler Hallada Committed by GitHub

AN-8229 Course List CSV download button (#642)

* Add download csv button to course list page

Also, a11y fix: make visible results-num span aria-hidden so the text is not
read twice by a screen-reader.

* Add acceptance test for downloading csv

* Use non-deprecated karma gulp runner

* Update jasmine-core npm package

* Output node version before running tests

* Try updating all karma-related npm packages

* Update translations
parent 5795af25
...@@ -19,6 +19,10 @@ export PATH=$PATH:$PWD/node_modules/.bin ...@@ -19,6 +19,10 @@ export PATH=$PATH:$PWD/node_modules/.bin
# https://github.com/GeoNode/geonode/pull/1070 # https://github.com/GeoNode/geonode/pull/1070
echo '{ "allow_root": true }' > /root/.bowerrc echo '{ "allow_root": true }' > /root/.bowerrc
# Output node.js version
node --version
npm --version
make develop make develop
make migrate make migrate
......
import requests
from bok_choy.promise import EmptyPromise from bok_choy.promise import EmptyPromise
from bok_choy.web_app_test import WebAppTest from bok_choy.web_app_test import WebAppTest
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
...@@ -30,6 +31,7 @@ class CourseIndexTests(AnalyticsDashboardWebAppTestMixin, WebAppTest): ...@@ -30,6 +31,7 @@ class CourseIndexTests(AnalyticsDashboardWebAppTestMixin, WebAppTest):
self._test_clear_all_filters() self._test_clear_all_filters()
if ENABLE_COURSE_LIST_FILTERS: if ENABLE_COURSE_LIST_FILTERS:
self._test_filters() self._test_filters()
self._test_download_csv()
def _test_course_list(self): def _test_course_list(self):
""" """
...@@ -274,3 +276,22 @@ class CourseIndexTests(AnalyticsDashboardWebAppTestMixin, WebAppTest): ...@@ -274,3 +276,22 @@ class CourseIndexTests(AnalyticsDashboardWebAppTestMixin, WebAppTest):
('instructor_paced', 'Instructor-Paced', False), ('instructor_paced', 'Instructor-Paced', False),
('self_paced', 'Self-Paced', True), ('self_paced', 'Self-Paced', True),
]) ])
def _test_download_csv(self):
# Download button is present
download_button = self.page.q(css='a.action-download-data')
self.assertTrue(download_button.present)
link = download_button.attrs('href')[0]
# Steal the cookies from the logged-in firefox browser and use them in a python-initiated request
kwargs = dict()
session_id = [{i['name']: i['value']} for i in self.browser.get_cookies() if i['name'] == u'sessionid']
if session_id:
kwargs.update({
'cookies': session_id[0]
})
response = requests.get(link, **kwargs)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.headers['content-type'], 'text/csv')
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-07 15:07-0500\n" "POT-Creation-Date: 2017-03-07 16:06-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -804,7 +804,7 @@ msgstr "" ...@@ -804,7 +804,7 @@ msgstr ""
msgid "External Tools" msgid "External Tools"
msgstr "" msgstr ""
#: courses/templates/courses/index.html:9 courses/views/course_summaries.py:29 #: courses/templates/courses/index.html:9 courses/views/course_summaries.py:30
msgid "Courses" msgid "Courses"
msgstr "" msgstr ""
...@@ -1097,7 +1097,7 @@ msgid "Courseware" ...@@ -1097,7 +1097,7 @@ msgid "Courseware"
msgstr "" msgstr ""
#. Translators: Do not translate UTC. #. Translators: Do not translate UTC.
#: courses/views/course_summaries.py:38 #: courses/views/course_summaries.py:39
#, python-format #, python-format
msgid "" msgid ""
"Course summary data was last updated %(update_date)s at %(update_time)s UTC." "Course summary data was last updated %(update_date)s at %(update_time)s UTC."
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-07 15:08-0500\n" "POT-Creation-Date: 2017-03-07 16:06-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -376,7 +376,11 @@ msgstr "" ...@@ -376,7 +376,11 @@ msgstr ""
msgid "pacing_type" msgid "pacing_type"
msgstr "" msgstr ""
#: static/apps/course-list/list/views/course-list.js:79 #: static/apps/course-list/list/views/course-list.js:66
msgid "Download full course list to CSV"
msgstr ""
#: static/apps/course-list/list/views/course-list.js:91
#: static/dist/apps/course-list/app/course-list-main.js:6464 #: static/dist/apps/course-list/app/course-list-main.js:6464
msgid "Course list controls" msgid "Course list controls"
msgstr "" msgstr ""
......
...@@ -4,6 +4,7 @@ from braces.views import LoginRequiredMixin ...@@ -4,6 +4,7 @@ from braces.views import LoginRequiredMixin
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import Http404 from django.http import Http404
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from waffle import switch_is_active from waffle import switch_is_active
...@@ -53,7 +54,8 @@ class CourseIndex(CourseAPIMixin, LoginRequiredMixin, TrackedViewMixin, LastUpda ...@@ -53,7 +54,8 @@ class CourseIndex(CourseAPIMixin, LoginRequiredMixin, TrackedViewMixin, LastUpda
data = { data = {
'course_list_json': summaries, 'course_list_json': summaries,
'enable_course_filters': switch_is_active('enable_course_filters') 'enable_course_filters': switch_is_active('enable_course_filters'),
'course_list_download_url': reverse('courses:index_csv'),
} }
context['js_data']['course'] = data context['js_data']['course'] = data
context['page_data'] = self.get_page_data(context) context['page_data'] = self.get_page_data(context)
......
<span class="num-results"><%- numResults %></span> <span class="num-results" aria-hidden="true"><%- numResults %></span>
...@@ -5,10 +5,10 @@ ...@@ -5,10 +5,10 @@
</div> </div>
<div class="col col-12 sm-col-12 md-col-10 course-list-results-col"> <div class="col col-12 sm-col-12 md-col-10 course-list-results-col">
<div class="row"> <div class="row">
<div class="col col-12 sm-col-12 md-col-3 md-pre-9"> <div class="col col-12 sm-col-12 md-col-3">
<div class="course-list-num-results"></div> <div class="course-list-num-results"></div>
</div> </div>
<div class="col col-12 sm-col-12 md-col-3"> <div class="col col-12 sm-col-12 md-col-3 md-pre-6">
<div class="course-list-download-data"></div> <div class="course-list-download-data"></div>
</div> </div>
</div> </div>
......
...@@ -12,6 +12,7 @@ define(function(require) { ...@@ -12,6 +12,7 @@ define(function(require) {
ActiveFiltersView = require('components/generic-list/list/views/active-filters'), ActiveFiltersView = require('components/generic-list/list/views/active-filters'),
CourseListControlsView = require('course-list/list/views/controls'), CourseListControlsView = require('course-list/list/views/controls'),
CourseListResultsView = require('course-list/list/views/results'), CourseListResultsView = require('course-list/list/views/results'),
DownloadDataView = require('components/download/views/download-data'),
ListView = require('components/generic-list/list/views/list'), ListView = require('components/generic-list/list/views/list'),
NumResultsView = require('components/generic-list/list/views/num-results'), NumResultsView = require('components/generic-list/list/views/num-results'),
...@@ -27,6 +28,7 @@ define(function(require) { ...@@ -27,6 +28,7 @@ define(function(require) {
regions: { regions: {
activeFilters: '.course-list-active-filters', activeFilters: '.course-list-active-filters',
controls: '.course-list-table-controls', controls: '.course-list-table-controls',
downloadData: '.course-list-download-data',
results: '.course-list-results', results: '.course-list-results',
numResults: '.course-list-num-results' numResults: '.course-list-num-results'
}, },
...@@ -55,6 +57,16 @@ define(function(require) { ...@@ -55,6 +57,16 @@ define(function(require) {
} }
}, },
{ {
region: 'downloadData',
class: DownloadDataView,
options: {
collection: this.options.collection,
trackingModel: this.options.trackingModel,
trackCategory: 'course_list',
downloadDataMessage: gettext('Download full course list to CSV')
}
},
{
region: 'results', region: 'results',
class: CourseListResultsView, class: CourseListResultsView,
options: { options: {
......
...@@ -798,21 +798,6 @@ body.view-dashboard { ...@@ -798,21 +798,6 @@ body.view-dashboard {
} }
.learners-results-col {
@media (min-width: $bp-screen-md) {
.row .col:first-child {
margin-top: $padding-large-vertical * 1.6;
}
}
.learners-num-results {
text-align: left;
margin-bottom: 0px;
margin-left: $padding-xs-horizontal;
margin-right: 0px;
}
}
} }
// styles for the learner details summary // styles for the learner details summary
......
...@@ -56,11 +56,18 @@ ...@@ -56,11 +56,18 @@
} }
} }
.#{$app-name}-num-results { .#{$app-name}-results-col {
text-align: right; @media (min-width: $bp-screen-md) {
margin-bottom: $padding-small-vertical; .row .col:first-child {
margin-right: $padding-xs-horizontal; margin-top: $padding-large-vertical * 1.6;
font-size: $font-size-small; }
}
.#{$app-name}-num-results {
text-align: left;
margin-left: $padding-xs-horizontal;
font-size: $font-size-small;
}
} }
.section-action { .section-action {
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
var eslint = require('gulp-eslint'), var eslint = require('gulp-eslint'),
gulp = require('gulp'), gulp = require('gulp'),
karma = require('karma').server, Server = require('karma').Server,
path = require('path'), path = require('path'),
browserSync = require('browser-sync'), browserSync = require('browser-sync'),
extend = require('util')._extend, // eslint-disable-line no-underscore-dangle extend = require('util')._extend, // eslint-disable-line no-underscore-dangle
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
singleRun: true, singleRun: true,
browsers: ['PhantomJS'] browsers: ['PhantomJS']
}; };
karma.start(extend(defaultOptions, options), cb); new Server(extend(defaultOptions, options), cb).start();
} }
gulp.task('lint', function() { gulp.task('lint', function() {
......
...@@ -16,20 +16,19 @@ ...@@ -16,20 +16,19 @@
"eslint-config-edx": "^1.2.0", "eslint-config-edx": "^1.2.0",
"gulp": "^3.8.8", "gulp": "^3.8.8",
"gulp-eslint": "^2.0.0", "gulp-eslint": "^2.0.0",
"gulp-karma": "0.0.5", "jasmine-core": "^2.5.2",
"jasmine-core": "^2.4.1",
"jscs": "^1.10.0", "jscs": "^1.10.0",
"karma": "^1.3.0", "karma": "^1.5.0",
"karma-coverage": "^0.2.6", "karma-chrome-launcher": "^2.0.0",
"karma-chrome-launcher": "^0.2.3", "karma-coverage": "^1.1.1",
"karma-jasmine": "^0.3.6", "karma-jasmine": "^1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"karma-jasmine-jquery": "^0.1.1", "karma-jasmine-jquery": "^0.1.1",
"karma-jasmine-html-reporter": "^0.2.0", "karma-phantomjs-launcher": "^1.0.2",
"karma-phantomjs-launcher": "^1.0.0", "karma-requirejs": "^1.1.0",
"karma-requirejs": "^0.2.2", "karma-sinon": "^1.0.5",
"karma-sinon": "^1.0.3", "phantomjs-prebuilt": "^2.1.14",
"phantomjs-prebuilt": "^2.1.7", "sinon": "1.17.7"
"sinon": "1.17.3"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "eslint-config-edx" "extends": "eslint-config-edx"
......
# Test dependencies go here. # Test dependencies go here.
-r base.txt -r base.txt
bok-choy>=0.4.7 bok-choy>=0.6.2
coverage==4.2 coverage==4.2
ddt==1.1.0 ddt==1.1.0
django-dynamic-fixture==1.9.0 django-dynamic-fixture==1.9.0
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment