diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index cac8ebf981..d4180eef0e 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -778,10 +778,12 @@ def calculate_grades_csv(request, course_id): """ try: instructor_task.api.submit_calculate_grades_csv(request, course_id) - return JsonResponse({"status" : "Grade calculation started"}) + success_status = _("Your grade report is being generated! You can view the status of the generation task in the 'Pending Instructor Tasks' section.") + return JsonResponse({"status": success_status}) except AlreadyRunningError: + already_running_status = _("A grade report generation task is already in progress. Check the 'Pending Instructor Tasks' table for the status of the task. When completed, the report will be available for download in the table below.") return JsonResponse({ - "status" : "Grade calculation already running" + "status": already_running_status }) diff --git a/lms/envs/common.py b/lms/envs/common.py index 73de4c00fc..a8575b89b6 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -196,6 +196,9 @@ MITX_FEATURES = { # Grade calculation started from the new instructor dashboard will write # grades CSV files to S3 and give links for downloads. 'ENABLE_S3_GRADE_DOWNLOADS' : True, + # Give course staff unrestricted access to grade downloads (if set to False, + # only edX superusers can perform the downloads) + 'ALLOW_COURSE_STAFF_GRADE_DOWNLOADS' : False, } # Used for A/B testing diff --git a/lms/static/coffee/src/instructor_dashboard/data_download.coffee b/lms/static/coffee/src/instructor_dashboard/data_download.coffee index e39b2f60c7..22bda4151d 100644 --- a/lms/static/coffee/src/instructor_dashboard/data_download.coffee +++ b/lms/static/coffee/src/instructor_dashboard/data_download.coffee @@ -17,17 +17,23 @@ class DataDownload # this object to call event handlers like 'onClickTitle' @$section.data 'wrapper', @ # gather elements - @$display = @$section.find '.data-display' - @$display_text = @$display.find '.data-display-text' - @$display_table = @$display.find '.data-display-table' - @$request_response_error = @$display.find '.request-response-error' - @$list_studs_btn = @$section.find("input[name='list-profiles']'") @$list_anon_btn = @$section.find("input[name='list-anon-ids']'") @$grade_config_btn = @$section.find("input[name='dump-gradeconf']'") + @$calculate_grades_csv_btn = @$section.find("input[name='calculate-grades-csv']'") + + # response areas + @$download = @$section.find '.data-download-container' + @$download_display_text = @$download.find '.data-display-text' + @$download_display_table = @$download.find '.data-display-table' + @$download_request_response_error = @$download.find '.request-response-error' + @$grades = @$section.find '.grades-download-container' + @$grades_request_response = @$grades.find '.request-response' + @$grades_request_response_error = @$grades.find '.request-response-error' @grade_downloads = new GradeDownloads(@$section) @instructor_tasks = new (PendingInstructorTasks()) @$section + @clear_display() # attach click handlers # The list-anon case is always CSV @@ -46,8 +52,9 @@ class DataDownload url += '/csv' location.href = url else + # Dynamically generate slickgrid table for displaying student profile information @clear_display() - @$display_table.text 'Loading...' + @$download_display_table.text gettext('Loading...') # fetch user list $.ajax @@ -55,7 +62,7 @@ class DataDownload url: url error: std_ajax_err => @clear_display() - @$request_response_error.text "Error getting student list." + @$download_request_response_error.text gettext("Error getting student list.") success: (data) => @clear_display() @@ -64,12 +71,13 @@ class DataDownload enableCellNavigation: true enableColumnReorder: false forceFitColumns: true + rowHeight: 35 columns = ({id: feature, field: feature, name: feature} for feature in data.queried_features) grid_data = data.students $table_placeholder = $ '
', class: 'slickgrid' - @$display_table.append $table_placeholder + @$download_display_table.append $table_placeholder grid = new Slick.Grid($table_placeholder, grid_data, columns, options) # grid.autosizeColumns() @@ -81,13 +89,31 @@ class DataDownload url: url error: std_ajax_err => @clear_display() - @$request_response_error.text "Error getting grading configuration." + @$download_request_response_error.text gettext("Error retrieving grading configuration.") success: (data) => @clear_display() - @$display_text.html data['grading_config_summary'] + @$download_display_text.html data['grading_config_summary'] + + @$calculate_grades_csv_btn.click (e) => + # Clear any CSS styling from the request-response areas + #$(".msg-confirm").css({"display":"none"}) + #$(".msg-error").css({"display":"none"}) + @clear_display() + url = @$calculate_grades_csv_btn.data 'endpoint' + $.ajax + dataType: 'json' + url: url + error: std_ajax_err => + @$grades_request_response_error.text gettext("Error generating grades. Please try again.") + $(".msg-error").css({"display":"block"}) + success: (data) => + @$grades_request_response.text data['status'] + $(".msg-confirm").css({"display":"block"}) # handler for when the section title is clicked. onClickTitle: -> + # Clear display of anything that was here before + @clear_display() @instructor_tasks.task_poller.start() @grade_downloads.downloads_poller.start() @@ -97,36 +123,32 @@ class DataDownload @grade_downloads.downloads_poller.stop() clear_display: -> - @$display_text.empty() - @$display_table.empty() - @$request_response_error.empty() + # Clear any generated tables, warning messages, etc. + @$download_display_text.empty() + @$download_display_table.empty() + @$download_request_response_error.empty() + @$grades_request_response.empty() + @$grades_request_response_error.empty() + # Clear any CSS styling from the request-response areas + $(".msg-confirm").css({"display":"none"}) + $(".msg-error").css({"display":"none"}) class GradeDownloads ### Grade Downloads -- links expire quickly, so we refresh every 5 mins #### constructor: (@$section) -> - @$grade_downloads_table = @$section.find ".grade-downloads-table" - @$calculate_grades_csv_btn = @$section.find("input[name='calculate-grades-csv']'") - @$display = @$section.find '.data-display' - @$display_text = @$display.find '.data-display-text' - @$request_response_error = @$display.find '.request-response-error' + + @$grades = @$section.find '.grades-download-container' + @$grades_request_response = @$grades.find '.request-response' + @$grades_request_response_error = @$grades.find '.request-response-error' + @$grade_downloads_table = @$grades.find ".grade-downloads-table" POLL_INTERVAL = 1000 * 60 * 5 # 5 minutes in ms @downloads_poller = new window.InstructorDashboard.util.IntervalManager( POLL_INTERVAL, => @reload_grade_downloads() ) - @$calculate_grades_csv_btn.click (e) => - url = @$calculate_grades_csv_btn.data 'endpoint' - $.ajax - dataType: 'json' - url: url - error: std_ajax_err => - @$request_response_error.text "Error generating grades." - success: (data) => - @$display_text.html data['status'] - reload_grade_downloads: -> endpoint = @$grade_downloads_table.data 'endpoint' $.ajax @@ -145,15 +167,17 @@ class GradeDownloads options = enableCellNavigation: true enableColumnReorder: false - autoHeight: true + rowHeight: 30 forceFitColumns: true columns = [ id: 'link' field: 'link' - name: 'File' - sortable: false, - minWidth: 200, + name: gettext('File Name (Newest First)') + toolTip: gettext("Links are generated on demand and expire within 5 minutes due to the sensitive nature of student grade information.") + sortable: false + minWidth: 150 + cssClass: "file-download-link" formatter: (row, cell, value, columnDef, dataContext) -> '' + dataContext['name'] + '' ] @@ -161,8 +185,7 @@ class GradeDownloads $table_placeholder = $ '', class: 'slickgrid' @$grade_downloads_table.append $table_placeholder grid = new Slick.Grid($table_placeholder, grade_downloads_data, columns, options) - - + grid.autosizeColumns() # export for use diff --git a/lms/static/coffee/src/instructor_dashboard/util.coffee b/lms/static/coffee/src/instructor_dashboard/util.coffee index af5b99f419..14a2f2b8ca 100644 --- a/lms/static/coffee/src/instructor_dashboard/util.coffee +++ b/lms/static/coffee/src/instructor_dashboard/util.coffee @@ -43,7 +43,7 @@ create_task_list_table = ($table_tasks, tasks_data) -> id: 'task_type' field: 'task_type' name: 'Task Type' - minWidth: 100 + minWidth: 102 , id: 'task_input' field: 'task_input' diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss index d1a04ce185..2f8eb8947e 100644 --- a/lms/static/sass/course/instructor/_instructor_2.scss +++ b/lms/static/sass/course/instructor/_instructor_2.scss @@ -26,6 +26,13 @@ @include font-size(16); } + .file-download-link a { + font-size: 15px; + color: $link-color; + text-decoration: underline; + padding: 5px; + } + // system feedback - messages .msg { border-radius: 1px; @@ -117,7 +124,7 @@ section.instructor-dashboard-content-2 { .slickgrid { margin-left: 1px; color:#333333; - font-size:11px; + font-size:12px; font-family: verdana,arial,sans-serif; .slick-header-column { @@ -428,13 +435,26 @@ section.instructor-dashboard-content-2 { line-height: 1.3em; } - .data-display { + .data-download-container { .data-display-table { .slickgrid { height: 400px; } } } + + .grades-download-container { + .grade-downloads-table { + .slickgrid { + height: 300px; + padding: 5px; + } + // Disable horizontal scroll bar when grid only has 1 column. Remove this CSS class when more columns added. + .slick-viewport { + overflow-x: hidden !important; + } + } + } } diff --git a/lms/templates/instructor/instructor_dashboard_2/data_download.html b/lms/templates/instructor/instructor_dashboard_2/data_download.html index 7eb55fabfe..cbbc2a871b 100644 --- a/lms/templates/instructor/instructor_dashboard_2/data_download.html +++ b/lms/templates/instructor/instructor_dashboard_2/data_download.html @@ -2,25 +2,46 @@ <%page args="section_data"/> -${_("The following button displays a list of all students enrolled in this course, along with profile information such as email address and username. The data can also be downloaded as a CSV file.")}
-+
- + +${_("Displays the grading configuration for the course. The grading configuration is the breakdown of graded subsections of the course (such as exams and problem sets), and can be changed on the 'Grading' page (under 'Settings') in Studio.")}
+ + + +${_("Download a CSV of anonymized student IDs by clicking this button.")}
+ +${_("Available grades downloads:")}
+${_("The following button will generate a CSV grade report for all currently enrolled students. For large courses, generating this report may take a few hours.")}
+ +${_("The report is generated in the background, meaning it is OK to navigate away from this page while your report is generating. Generated reports appear in a table below and can be downloaded.")}
+ + + +${_("Reports Available for Download")}
+${_("File links are generated on demand and expire within 5 minutes due to the sensitive nature of student grade information. Please note that the report filename contains a timestamp that represents when your file was generated; this timestamp is UTC, not your local timezone.")}
- ${_("Specify the {platform_name} email address or username of a student here:").format(platform_name=settings.PLATFORM_NAME)}
@@ -26,7 +25,6 @@- ${_("Specify the {platform_name} email address or username of a student here:").format(platform_name=settings.PLATFORM_NAME)}
@@ -60,18 +58,18 @@%if section_data['access']['instructor']:
${_('You may also delete the entire state of a student for the specified problem:')}
- + %endif %if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS') and section_data['access']['instructor']:- ${_("Rescoring runs in the background, and status for active tasks will appear in a table on the Course Info tab. " + ${_("Rescoring runs in the background, and status for active tasks will appear in the 'Pending Instructor Tasks' table. " "To see status for all tasks submitted for this problem and student, click on this button:")}
- + %endif
- ${_("These actions run in the background, and status for active tasks will appear in a table on the Course Info tab. " + ${_("The above actions run in the background, and status for active tasks will appear in a table on the Course Info tab. " "To see status for all tasks submitted for this problem, click on this button")}:
- +