Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lms/djangoapps/instructor/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1424,6 +1424,8 @@ def all_issued_certificates(self, request, course_id):
('total_issued_certificate', _('Total Certificates Issued')),
('report_run_date', _('Date Report Run'))
]
query_features += ['download_url']
query_features_names += [('download_url', _('Certificate link'))]
certificates_data = instructor_analytics_basic.issued_certificates(course_key, query_features)
if csv_required.lower() == 'true':
__, data_rows = instructor_analytics_csvs.format_dictlist(certificates_data, query_features)
Expand Down
8 changes: 6 additions & 2 deletions lms/djangoapps/instructor/views/instructor_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def instructor_dashboard_2(request, course_id): # lint-amnesty, pylint: disable
sections_content.append(_section_discussions_management(course, access))
sections.extend(sections_content)

if access['data_researcher']:
if access['data_researcher'] or access['staff'] or access['instructor']:
sections.append(_section_data_download(course, access))

analytics_dashboard_message = None
Expand Down Expand Up @@ -603,6 +603,10 @@ def _section_student_admin(course, access):
kwargs={'course_id': str(course_key)}
),
'spoc_gradebook_url': reverse('spoc_gradebook', kwargs={'course_id': str(course_key)}),
'calculate_grades_csv_url': reverse('calculate_grades_csv', kwargs={'course_id': str(course_key)}),
'list_report_downloads_url': reverse(
'wikimedia_general:list_report_downloads_student_admin', kwargs={'course_id': str(course_key)}
),
Comment thread
eemaanamir marked this conversation as resolved.
}
if is_writable_gradebook_enabled(course_key) and settings.WRITABLE_GRADEBOOK_URL:
section_data['writable_gradebook_url'] = f'{settings.WRITABLE_GRADEBOOK_URL}/{str(course_key)}'
Expand Down Expand Up @@ -668,7 +672,7 @@ def _section_data_download(course, access):
),
'export_ora2_summary_url': reverse('export_ora2_summary', kwargs={'course_id': str(course_key)}),
}
if not access.get('data_researcher'):
if not (access.get('data_researcher') or access.get('staff') or access.get('instructor')):
section_data['is_hidden'] = True
return section_data

Expand Down
3 changes: 2 additions & 1 deletion lms/djangoapps/instructor_analytics/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
AVAILABLE_FEATURES = STUDENT_FEATURES + PROFILE_FEATURES + PROGRAM_ENROLLMENT_FEATURES
COURSE_REGISTRATION_FEATURES = ('code', 'course_id', 'created_by', 'created_at', 'is_valid')
COUPON_FEATURES = ('code', 'course_id', 'percentage_discount', 'description', 'expiration_date', 'is_active')
CERTIFICATE_FEATURES = ('course_id', 'mode', 'status', 'grade', 'created_date', 'is_active', 'error_reason')
CERTIFICATE_FEATURES = ('course_id', 'mode', 'status', 'grade', 'created_date', 'is_active', 'error_reason',
'download_url')

UNAVAILABLE = "[unavailable]"

Expand Down
12 changes: 4 additions & 8 deletions lms/djangoapps/instructor_task/tasks_helper/grades.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.partitions.partitions_service import PartitionService # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.split_test_block import get_split_user_partitions # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.util.misc import get_default_short_labeler

from .runner import TaskProgress
from .utils import upload_csv_to_report_store, upload_csv_file_to_report_store
Expand Down Expand Up @@ -131,10 +132,8 @@ def graded_assignments(self):
graded_subsections_map = OrderedDict()
for subsection_index, subsection_info in enumerate(subsection_infos, start=1):
subsection = subsection_info['subsection_block']
header_name = "{assignment_type} {subsection_index}: {subsection_name}".format(
assignment_type=assignment_type_name,
subsection_index=subsection_index,
subsection_name=subsection.display_name,
header_name = "{short_label}".format(
short_label=get_default_short_labeler(self.course)(subsection.format)
)
graded_subsections_map[subsection.location] = header_name

Expand Down Expand Up @@ -533,10 +532,7 @@ def _success_headers(self):
self._grades_header() +
(['Cohort Name'] if self.context.cohorts_enabled else []) +
[f'Experiment Group ({partition.name})' for partition in self.context.course_experiments] +
(['Team Name'] if self.context.teams_enabled else []) +
['Enrollment Track', 'Verification Status'] +
['Certificate Eligible', 'Certificate Delivered', 'Certificate Type'] +
['Enrollment Status']
(['Team Name'] if self.context.teams_enabled else [])
Comment thread
eemaanamir marked this conversation as resolved.
)

def _error_headers(self):
Expand Down
56 changes: 53 additions & 3 deletions lms/static/js/instructor_dashboard/student_admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
(function() {
'use strict';

var PendingInstructorTasks, createTaskListTable, findAndAssert, statusAjaxError;
var PendingInstructorTasks, ReportDownloads, createTaskListTable, findAndAssert, statusAjaxError;

statusAjaxError = function() {
return window.InstructorDashboard.util.statusAjaxError.apply(this, arguments);
Expand All @@ -17,6 +17,10 @@
return window.InstructorDashboard.util.PendingInstructorTasks;
};

ReportDownloads = function() {
return window.InstructorDashboard.util.ReportDownloads;
};

findAndAssert = function($root, selector) {
var item, msg;
item = $root.find(selector);
Expand Down Expand Up @@ -66,14 +70,20 @@
this.$btn_rescore_problem_if_higher_all = this.$section.find("input[name='rescore-problem-all-if-higher']");
this.$btn_task_history_all = this.$section.find("input[name='task-history-all']");
this.$table_task_history_all = this.$section.find('.task-history-all-table');
this.report_downloads = new (ReportDownloads())(this.$section);
this.instructor_tasks = new (PendingInstructorTasks())(this.$section);
this.$reports = this.$section.find('.reports-download-container');
this.$reports_request_response = this.$reports.find('.request-response');
this.$reports_request_response_error = this.$reports.find('.request-response-error');
this.$request_err_enrollment_status = findAndAssert(this.$section, '.student-enrollment-status-container .request-response-error');
this.$request_err_progress = findAndAssert(this.$section, '.student-progress-container .request-response-error');
this.$request_err_grade = findAndAssert(this.$section, '.student-grade-container .request-response-error');
this.$request_err_ee = this.$section.find('.entrance-exam-grade-container .request-response-error');
this.$request_response_error_all = this.$section.find('.course-specific-container .request-response-error');
this.$enrollment_status_link = findAndAssert(this.$section, 'a.enrollment-status-link');
this.$enrollment_status = findAndAssert(this.$section, '.student-enrollment-status');
this.$async_report_btn = this.$section.find("input[class='async-report-btn']");
this.clear_display();
this.$enrollment_status_link.click(function(e) {
var errorMessage, fullErrorMessage, uniqStudentIdentifier;
e.preventDefault();
Expand Down Expand Up @@ -477,6 +487,33 @@
})
});
});
this.$async_report_btn.click(function(e) {
var url = $(e.target).data('endpoint');
var errorMessage = '';
studentadmin.clear_display();
return $.ajax({
type: 'POST',
dataType: 'json',
url: url,
error: function(error) {
if (error.responseText) {
errorMessage = JSON.parse(error.responseText);
} else if (e.target.name === 'calculate-grades-csv') {
errorMessage = gettext('Error generating grades. Please try again.');
}
studentadmin.$reports_request_response_error.text(errorMessage);
return studentadmin.$reports_request_response_error.css({
display: 'block'
});
},
success: function(data) {
studentadmin.$reports_request_response.text(data.status);
return $('.msg-confirm').css({
display: 'block'
});
}
});
});
}

StudentAdmin.prototype.rescore_problem_single = function(onlyIfHigher) {
Expand Down Expand Up @@ -688,11 +725,24 @@
};

StudentAdmin.prototype.onClickTitle = function() {
return this.instructor_tasks.task_poller.start();
this.clear_display();
this.instructor_tasks.task_poller.start();
return this.report_downloads.downloads_poller.start();
};

StudentAdmin.prototype.onExit = function() {
return this.instructor_tasks.task_poller.stop();
this.instructor_tasks.task_poller.stop();
return this.report_downloads.downloads_poller.stop();
};
StudentAdmin.prototype.clear_display = function() {
this.$reports_request_response.empty();
this.$reports_request_response_error.empty();
$('.msg-confirm').css({
display: 'none'
});
return $('.msg-error').css({
display: 'none'
});
};

return StudentAdmin;
Expand Down
1 change: 1 addition & 0 deletions lms/static/js/instructor_dashboard/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@
$tablePlaceholder = $('<div/>', {
class: 'slickgrid'
});
$tablePlaceholder.css("height", "300px");
this.$report_downloads_table.append($tablePlaceholder);
grid = new Slick.Grid($tablePlaceholder, reportDownloadsData, columns, options);
grid.onClick.subscribe(function(event) {
Expand Down
50 changes: 25 additions & 25 deletions lms/templates/instructor/instructor_dashboard_2/data_download.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,31 @@ <h3 class="hd hd-3">${_("Reports")}</h3>

<p>${_("Please be patient and do not click these buttons multiple times. Clicking these buttons multiple times will significantly slow the generation process.")}</p>

%if settings.FEATURES.get('ALLOW_COURSE_STAFF_GRADE_DOWNLOADS') or section_data['access']['admin']:
<p>${_("Click to generate a CSV grade report for all currently enrolled students.")}</p>
<p>
<input type="button" name="calculate-grades-csv" class="async-report-btn" value="${_("Generate Grade Report")}" data-endpoint="${ section_data['calculate_grades_csv_url'] }"/>
<input type="button" name="problem-grade-report" class="async-report-btn" value="${_("Generate Problem Grade Report")}" data-endpoint="${ section_data['problem_grade_report_url'] }"/>
<input type="button" name="export-ora2-data" class="async-report-btn" value="${_("Generate ORA Data Report")}" data-endpoint="${ section_data['export_ora2_data_url'] }"/>
<input type="button" name="export-ora2-summary" class="async-report-btn" value="${_("Generate ORA Summary Report")}" data-endpoint="${ section_data['export_ora2_summary_url'] }"/>
</p>

<p>${_("Click to generate a ZIP file that contains all submission texts and attachments.")}</p>

<p><input type="button" name="export-ora2-data" class="async-report-btn" value="${_("Generate Submission Files Archive")}" data-endpoint="${ section_data['export_ora2_submission_files_url'] }"/></p>

%endif
<div class="issued_certificates">
<p>${_("Click to list certificates that are issued for this course:")}</p>
<p>
<input type="button" name="issued-certificates-list" value="${_("View Certificates Issued")}" data-csv="false" data-endpoint="${ section_data['get_issued_certificates_url'] }">
<input type="button" name="issued-certificates-csv" value="${_("Download CSV of Certificates Issued")}" data-csv="true" data-endpoint="${ section_data['get_issued_certificates_url'] }">
</p>
<div class="data-display-table certificate-data-display-table" id="data-issued-certificates-table"></div>
<div class="issued-certificates-error request-response-error msg msg-error copy"></div>
</div>


<p>${_("Click to generate a CSV file of all students enrolled in this course, along with profile information such as email address and username:")}</p>

<p><input type="button" name="list-profiles-csv" value="${_("Download profile information as a CSV")}" data-endpoint="${ section_data['get_students_features_url'] }" data-csv="true"></p>
Expand Down Expand Up @@ -76,37 +101,12 @@ <h3 class="hd hd-3">${_("Reports")}</h3>
)}
</div>

<div class="issued_certificates">
<p>${_("Click to list certificates that are issued for this course:")}</p>
<p>
<input type="button" name="issued-certificates-list" value="${_("View Certificates Issued")}" data-csv="false" data-endpoint="${ section_data['get_issued_certificates_url'] }">
<input type="button" name="issued-certificates-csv" value="${_("Download CSV of Certificates Issued")}" data-csv="true" data-endpoint="${ section_data['get_issued_certificates_url'] }">
</p>
<div class="data-display-table certificate-data-display-table" id="data-issued-certificates-table"></div>
<div class="issued-certificates-error request-response-error msg msg-error copy"></div>
</div>

% if not disable_buttons:
<p>${_("For smaller courses, click to list profile information for enrolled students directly on this page:")}</p>
<p><input type="button" name="list-profiles" value="${_("List enrolled students' profile information")}" data-endpoint="${ section_data['get_students_features_url'] }"></p>
%endif
<div class="data-display-table profile-data-display-table" id="data-student-profiles-table"></div>

%if settings.FEATURES.get('ALLOW_COURSE_STAFF_GRADE_DOWNLOADS') or section_data['access']['admin']:
<p>${_("Click to generate a CSV grade report for all currently enrolled students.")}</p>
<p>
<input type="button" name="calculate-grades-csv" class="async-report-btn" value="${_("Generate Grade Report")}" data-endpoint="${ section_data['calculate_grades_csv_url'] }"/>
<input type="button" name="problem-grade-report" class="async-report-btn" value="${_("Generate Problem Grade Report")}" data-endpoint="${ section_data['problem_grade_report_url'] }"/>
<input type="button" name="export-ora2-data" class="async-report-btn" value="${_("Generate ORA Data Report")}" data-endpoint="${ section_data['export_ora2_data_url'] }"/>
<input type="button" name="export-ora2-summary" class="async-report-btn" value="${_("Generate ORA Summary Report")}" data-endpoint="${ section_data['export_ora2_summary_url'] }"/>
</p>

<p>${_("Click to generate a ZIP file that contains all submission texts and attachments.")}</p>

<p><input type="button" name="export-ora2-data" class="async-report-btn" value="${_("Generate Submission Files Archive")}" data-endpoint="${ section_data['export_ora2_submission_files_url'] }"/></p>

%endif

<div class="request-response msg msg-confirm copy" id="report-request-response"></div>
<div class="request-response-error msg msg-error copy" id="report-request-response-error"></div>
<br>
Expand Down
37 changes: 34 additions & 3 deletions lms/templates/instructor/instructor_dashboard_2/student_admin.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<%page args="section_data" expression_filter="h"/>
<%! from django.utils.translation import gettext as _ %>
<%!
from django.utils.translation import gettext as _
from openedx.core.djangolib.markup import HTML, Text
%>
%if section_data['access']['staff'] or section_data['access']['instructor']:
<div class="action-type-container">
%if section_data.get('writable_gradebook_url') or section_data.get('is_small_course'):
Expand All @@ -13,9 +16,13 @@ <h4 class="hd hd-4">${_("View gradebook for enrolled learners")}</h4>
<br><br>
<span name="gradebook-link"><a href="${ section_data['spoc_gradebook_url'] }" class="gradebook-link"> ${_("View Gradebook")} </a></span>
%endif
<br><br>
<hr>
<br><br>
%endif
<label>${_("Click to generate a CSV grade report for all currently enrolled students.!!")}</label>
<br><br>
<input type="button" name="calculate-grades-csv" class="async-report-btn" value="${_('Generate Grade Report')}" data-endpoint="${ section_data['calculate_grades_csv_url'] }"/>

<hr>
</div>

<div class="student-enrollment-status-container action-type-container">
Expand Down Expand Up @@ -230,8 +237,32 @@ <h5 class="hd hd-5">${_("Task Status")}</h5>
</div>
%endif
%endif
<div class="reports-download-container action-type-container">
<div class="request-response msg msg-confirm copy" id="report-request-response"></div>
<div class="request-response-error msg msg-error copy" id="report-request-response-error"></div>
<br>
<h3 class="hd hd-3">${_("Reports Available for Download")}</h3>
<p>
${_("The reports listed below are available for download, identified by UTC date and time of generation.")}
</p>

## Translators: a table of URL links to report files appears after this sentence.
<p>
${Text(_("{strong_start}Note{strong_end}: {ul_start}{li_start}To keep student data secure, you cannot save or email these links for direct access. Copies of links expire within 5 minutes.{li_end}{li_start}Report files are deleted 90 days after generation. If you will need access to old reports, download and store the files, in accordance with your institution's data security policies.{li_end}{ul_end}")).format(
strong_start=HTML("<strong>"),
strong_end=HTML("</strong>"),
ul_start=HTML("<ul>"),
ul_end=HTML("</ul>"),
li_start=HTML("<li>"),
li_end=HTML("</li>"),
)}
</p><br>

<div class="report-downloads-table" id="report-downloads-table" data-endpoint="${ section_data['list_report_downloads_url'] }" ></div>
</div>

%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
<hr>
<div class="running-tasks-container action-type-container">
<h4 class="hd hd-4">${_("Pending Tasks")}</h4>
<div class="running-tasks-section">
Expand Down
Loading