Add the ability for self-service course survey reports
This commit is contained in:
@@ -1358,6 +1358,29 @@ def get_exec_summary_report(request, course_id):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ensure_csrf_cookie
|
||||||
|
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||||
|
@require_level('staff')
|
||||||
|
def get_course_survey_results(request, course_id):
|
||||||
|
"""
|
||||||
|
get the survey results report for the particular course.
|
||||||
|
"""
|
||||||
|
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
|
||||||
|
try:
|
||||||
|
instructor_task.api.submit_course_survey_report(request, course_key)
|
||||||
|
status_response = _("The survey report is being created."
|
||||||
|
" To view the status of the report, see Pending Instructor Tasks below.")
|
||||||
|
except AlreadyRunningError:
|
||||||
|
status_response = _(
|
||||||
|
"The survey report is currently being created."
|
||||||
|
" To view the status of the report, see Pending Instructor Tasks below."
|
||||||
|
" You will be able to download the report when it is complete."
|
||||||
|
)
|
||||||
|
return JsonResponse({
|
||||||
|
"status": status_response
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||||
@require_level('staff')
|
@require_level('staff')
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ urlpatterns = patterns(
|
|||||||
'instructor.views.api.get_enrollment_report', name="get_enrollment_report"),
|
'instructor.views.api.get_enrollment_report', name="get_enrollment_report"),
|
||||||
url(r'get_exec_summary_report$',
|
url(r'get_exec_summary_report$',
|
||||||
'instructor.views.api.get_exec_summary_report', name="get_exec_summary_report"),
|
'instructor.views.api.get_exec_summary_report', name="get_exec_summary_report"),
|
||||||
|
url(r'get_course_survey_results$',
|
||||||
|
'instructor.views.api.get_course_survey_results', name="get_course_survey_results"),
|
||||||
|
|
||||||
# Coupon Codes..
|
# Coupon Codes..
|
||||||
url(r'get_coupon_codes',
|
url(r'get_coupon_codes',
|
||||||
|
|||||||
@@ -169,7 +169,6 @@ def instructor_dashboard_2(request, course_id):
|
|||||||
'disable_buttons': disable_buttons,
|
'disable_buttons': disable_buttons,
|
||||||
'analytics_dashboard_message': analytics_dashboard_message
|
'analytics_dashboard_message': analytics_dashboard_message
|
||||||
}
|
}
|
||||||
|
|
||||||
return render_to_response('instructor/instructor_dashboard_2/instructor_dashboard_2.html', context)
|
return render_to_response('instructor/instructor_dashboard_2/instructor_dashboard_2.html', context)
|
||||||
|
|
||||||
|
|
||||||
@@ -516,6 +515,8 @@ def _section_data_download(course, access):
|
|||||||
'list_report_downloads_url': reverse('list_report_downloads', kwargs={'course_id': unicode(course_key)}),
|
'list_report_downloads_url': reverse('list_report_downloads', kwargs={'course_id': unicode(course_key)}),
|
||||||
'calculate_grades_csv_url': reverse('calculate_grades_csv', kwargs={'course_id': unicode(course_key)}),
|
'calculate_grades_csv_url': reverse('calculate_grades_csv', kwargs={'course_id': unicode(course_key)}),
|
||||||
'problem_grade_report_url': reverse('problem_grade_report', kwargs={'course_id': unicode(course_key)}),
|
'problem_grade_report_url': reverse('problem_grade_report', kwargs={'course_id': unicode(course_key)}),
|
||||||
|
'course_has_survey': True if course.course_survey_name else False,
|
||||||
|
'course_survey_results_url': reverse('get_course_survey_results', kwargs={'course_id': unicode(course_key)}),
|
||||||
}
|
}
|
||||||
return section_data
|
return section_data
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from instructor_task.tasks import (
|
|||||||
enrollment_report_features_csv,
|
enrollment_report_features_csv,
|
||||||
calculate_may_enroll_csv,
|
calculate_may_enroll_csv,
|
||||||
exec_summary_report_csv,
|
exec_summary_report_csv,
|
||||||
|
course_survey_report_csv,
|
||||||
generate_certificates,
|
generate_certificates,
|
||||||
proctored_exam_results_csv
|
proctored_exam_results_csv
|
||||||
)
|
)
|
||||||
@@ -436,6 +437,20 @@ def submit_executive_summary_report(request, course_key): # pylint: disable=inv
|
|||||||
return submit_task(request, task_type, task_class, course_key, task_input, task_key)
|
return submit_task(request, task_type, task_class, course_key, task_input, task_key)
|
||||||
|
|
||||||
|
|
||||||
|
def submit_course_survey_report(request, course_key): # pylint: disable=invalid-name
|
||||||
|
"""
|
||||||
|
Submits a task to generate a HTML File containing the executive summary report.
|
||||||
|
|
||||||
|
Raises AlreadyRunningError if HTML File is already being updated.
|
||||||
|
"""
|
||||||
|
task_type = 'course_survey_report'
|
||||||
|
task_class = course_survey_report_csv
|
||||||
|
task_input = {}
|
||||||
|
task_key = ""
|
||||||
|
|
||||||
|
return submit_task(request, task_type, task_class, course_key, task_input, task_key)
|
||||||
|
|
||||||
|
|
||||||
def submit_proctored_exam_results_report(request, course_key, features): # pylint: disable=invalid-name
|
def submit_proctored_exam_results_report(request, course_key, features): # pylint: disable=invalid-name
|
||||||
"""
|
"""
|
||||||
Submits a task to generate a HTML File containing the executive summary report.
|
Submits a task to generate a HTML File containing the executive summary report.
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ from instructor_task.tasks_helper import (
|
|||||||
upload_enrollment_report,
|
upload_enrollment_report,
|
||||||
upload_may_enroll_csv,
|
upload_may_enroll_csv,
|
||||||
upload_exec_summary_report,
|
upload_exec_summary_report,
|
||||||
|
upload_course_survey_report,
|
||||||
generate_students_certificates,
|
generate_students_certificates,
|
||||||
upload_proctored_exam_results_report
|
upload_proctored_exam_results_report
|
||||||
)
|
)
|
||||||
@@ -227,6 +228,18 @@ def exec_summary_report_csv(entry_id, xmodule_instance_args):
|
|||||||
return run_main_task(entry_id, task_fn, action_name)
|
return run_main_task(entry_id, task_fn, action_name)
|
||||||
|
|
||||||
|
|
||||||
|
@task(base=BaseInstructorTask, routing_key=settings.GRADES_DOWNLOAD_ROUTING_KEY) # pylint: disable=not-callable
|
||||||
|
def course_survey_report_csv(entry_id, xmodule_instance_args):
|
||||||
|
"""
|
||||||
|
Compute the survey report for a course and upload the
|
||||||
|
generated report to an S3 bucket for download.
|
||||||
|
"""
|
||||||
|
# Translators: This is a past-tense verb that is inserted into task progress messages as {action}.
|
||||||
|
action_name = ugettext_noop('generated')
|
||||||
|
task_fn = partial(upload_course_survey_report, xmodule_instance_args)
|
||||||
|
return run_main_task(entry_id, task_fn, action_name)
|
||||||
|
|
||||||
|
|
||||||
@task(base=BaseInstructorTask, routing_key=settings.GRADES_DOWNLOAD_ROUTING_KEY) # pylint: disable=not-callable
|
@task(base=BaseInstructorTask, routing_key=settings.GRADES_DOWNLOAD_ROUTING_KEY) # pylint: disable=not-callable
|
||||||
def proctored_exam_results_csv(entry_id, xmodule_instance_args):
|
def proctored_exam_results_csv(entry_id, xmodule_instance_args):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from shoppingcart.models import (
|
|||||||
PaidCourseRegistration, CourseRegCodeItem, InvoiceTransaction,
|
PaidCourseRegistration, CourseRegCodeItem, InvoiceTransaction,
|
||||||
Invoice, CouponRedemption, RegistrationCodeRedemption, CourseRegistrationCode
|
Invoice, CouponRedemption, RegistrationCodeRedemption, CourseRegistrationCode
|
||||||
)
|
)
|
||||||
|
from survey.models import SurveyAnswer
|
||||||
|
|
||||||
from track.views import task_track
|
from track.views import task_track
|
||||||
from util.file import course_filename_prefix_generator, UniversalNewlineIterator
|
from util.file import course_filename_prefix_generator, UniversalNewlineIterator
|
||||||
@@ -1307,6 +1308,62 @@ def upload_exec_summary_report(_xmodule_instance_args, _entry_id, course_id, _ta
|
|||||||
return task_progress.update_task_state(extra_meta=current_step)
|
return task_progress.update_task_state(extra_meta=current_step)
|
||||||
|
|
||||||
|
|
||||||
|
def upload_course_survey_report(_xmodule_instance_args, _entry_id, course_id, _task_input, action_name): # pylint: disable=invalid-name
|
||||||
|
"""
|
||||||
|
For a given `course_id`, generate a html report containing the survey results for a course.
|
||||||
|
"""
|
||||||
|
start_time = time()
|
||||||
|
start_date = datetime.now(UTC)
|
||||||
|
num_reports = 1
|
||||||
|
task_progress = TaskProgress(action_name, num_reports, start_time)
|
||||||
|
|
||||||
|
current_step = {'step': 'Gathering course survey report information'}
|
||||||
|
task_progress.update_task_state(extra_meta=current_step)
|
||||||
|
|
||||||
|
distinct_survey_fields_queryset = SurveyAnswer.objects.filter(course_key=course_id).values('field_name').distinct()
|
||||||
|
survey_fields = []
|
||||||
|
for unique_field_row in distinct_survey_fields_queryset:
|
||||||
|
survey_fields.append(unique_field_row['field_name'])
|
||||||
|
survey_fields.sort()
|
||||||
|
|
||||||
|
user_survey_answers = OrderedDict()
|
||||||
|
survey_answers_for_course = SurveyAnswer.objects.filter(course_key=course_id)
|
||||||
|
|
||||||
|
for survey_field_record in survey_answers_for_course:
|
||||||
|
user_id = survey_field_record.user.id
|
||||||
|
if user_id not in user_survey_answers.keys():
|
||||||
|
user_survey_answers[user_id] = {}
|
||||||
|
|
||||||
|
user_survey_answers[user_id][survey_field_record.field_name] = survey_field_record.field_value
|
||||||
|
|
||||||
|
header = ["User ID", "User Name", "Email"]
|
||||||
|
header.extend(survey_fields)
|
||||||
|
csv_rows = []
|
||||||
|
|
||||||
|
for user_id in user_survey_answers.keys():
|
||||||
|
row = []
|
||||||
|
row.append(user_id)
|
||||||
|
user_obj = User.objects.get(id=user_id)
|
||||||
|
row.append(user_obj.username)
|
||||||
|
row.append(user_obj.email)
|
||||||
|
for survey_field in survey_fields:
|
||||||
|
row.append(user_survey_answers[user_id].get(survey_field, ''))
|
||||||
|
csv_rows.append(row)
|
||||||
|
|
||||||
|
task_progress.attempted = task_progress.succeeded = len(csv_rows)
|
||||||
|
task_progress.skipped = task_progress.total - task_progress.attempted
|
||||||
|
|
||||||
|
csv_rows.insert(0, header)
|
||||||
|
|
||||||
|
current_step = {'step': 'Uploading CSV'}
|
||||||
|
task_progress.update_task_state(extra_meta=current_step)
|
||||||
|
|
||||||
|
# Perform the upload
|
||||||
|
upload_csv_to_report_store(csv_rows, 'course_survey_results', course_id, start_date)
|
||||||
|
|
||||||
|
return task_progress.update_task_state(extra_meta=current_step)
|
||||||
|
|
||||||
|
|
||||||
def upload_proctored_exam_results_report(_xmodule_instance_args, _entry_id, course_id, _task_input, action_name): # pylint: disable=invalid-name
|
def upload_proctored_exam_results_report(_xmodule_instance_args, _entry_id, course_id, _task_input, action_name): # pylint: disable=invalid-name
|
||||||
"""
|
"""
|
||||||
For a given `course_id`, generate a CSV file containing
|
For a given `course_id`, generate a CSV file containing
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from instructor_task.api import (
|
|||||||
submit_detailed_enrollment_features_csv,
|
submit_detailed_enrollment_features_csv,
|
||||||
submit_calculate_may_enroll_csv,
|
submit_calculate_may_enroll_csv,
|
||||||
submit_executive_summary_report,
|
submit_executive_summary_report,
|
||||||
|
submit_course_survey_report,
|
||||||
generate_certificates_for_all_students,
|
generate_certificates_for_all_students,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -231,6 +232,12 @@ class InstructorTaskCourseSubmitTest(TestReportMixin, InstructorTaskCourseTestCa
|
|||||||
)
|
)
|
||||||
self._test_resubmission(api_call)
|
self._test_resubmission(api_call)
|
||||||
|
|
||||||
|
def test_submit_course_survey_report(self):
|
||||||
|
api_call = lambda: submit_course_survey_report(
|
||||||
|
self.create_task_request(self.instructor), self.course.id
|
||||||
|
)
|
||||||
|
self._test_resubmission(api_call)
|
||||||
|
|
||||||
def test_submit_calculate_may_enroll(self):
|
def test_submit_calculate_may_enroll(self):
|
||||||
api_call = lambda: submit_calculate_may_enroll_csv(
|
api_call = lambda: submit_calculate_may_enroll_csv(
|
||||||
self.create_task_request(self.instructor),
|
self.create_task_request(self.instructor),
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ from verify_student.tests.factories import SoftwareSecurePhotoVerificationFactor
|
|||||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||||
from xmodule.partitions.partitions import Group, UserPartition
|
from xmodule.partitions.partitions import Group, UserPartition
|
||||||
from instructor_task.models import ReportStore
|
from instructor_task.models import ReportStore
|
||||||
|
from survey.models import SurveyForm, SurveyAnswer
|
||||||
from instructor_task.tasks_helper import (
|
from instructor_task.tasks_helper import (
|
||||||
cohort_students_and_upload,
|
cohort_students_and_upload,
|
||||||
upload_problem_responses_csv,
|
upload_problem_responses_csv,
|
||||||
@@ -41,6 +42,7 @@ from instructor_task.tasks_helper import (
|
|||||||
upload_may_enroll_csv,
|
upload_may_enroll_csv,
|
||||||
upload_enrollment_report,
|
upload_enrollment_report,
|
||||||
upload_exec_summary_report,
|
upload_exec_summary_report,
|
||||||
|
upload_course_survey_report,
|
||||||
generate_students_certificates,
|
generate_students_certificates,
|
||||||
)
|
)
|
||||||
from instructor_analytics.basic import UNAVAILABLE
|
from instructor_analytics.basic import UNAVAILABLE
|
||||||
@@ -953,6 +955,99 @@ class TestExecutiveSummaryReport(TestReportMixin, InstructorTaskCourseTestCase):
|
|||||||
self.assertTrue(data in html_file_data)
|
self.assertTrue(data in html_file_data)
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
|
class TestCourseSurveyReport(TestReportMixin, InstructorTaskCourseTestCase):
|
||||||
|
"""
|
||||||
|
Tests that Course Survey report generation works.
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
super(TestCourseSurveyReport, self).setUp()
|
||||||
|
self.course = CourseFactory.create()
|
||||||
|
|
||||||
|
self.question1 = "question1"
|
||||||
|
self.question2 = "question2"
|
||||||
|
self.question3 = "question3"
|
||||||
|
self.answer1 = "answer1"
|
||||||
|
self.answer2 = "answer2"
|
||||||
|
self.answer3 = "answer3"
|
||||||
|
|
||||||
|
self.student1 = UserFactory()
|
||||||
|
self.student2 = UserFactory()
|
||||||
|
|
||||||
|
self.test_survey_name = 'TestSurvey'
|
||||||
|
self.test_form = '<input name="field1"></input>'
|
||||||
|
self.survey_form = SurveyForm.create(self.test_survey_name, self.test_form)
|
||||||
|
|
||||||
|
self.survey1 = SurveyAnswer.objects.create(user=self.student1, form=self.survey_form, course_key=self.course.id,
|
||||||
|
field_name=self.question1, field_value=self.answer1)
|
||||||
|
self.survey2 = SurveyAnswer.objects.create(user=self.student1, form=self.survey_form, course_key=self.course.id,
|
||||||
|
field_name=self.question2, field_value=self.answer2)
|
||||||
|
self.survey3 = SurveyAnswer.objects.create(user=self.student2, form=self.survey_form, course_key=self.course.id,
|
||||||
|
field_name=self.question1, field_value=self.answer3)
|
||||||
|
self.survey4 = SurveyAnswer.objects.create(user=self.student2, form=self.survey_form, course_key=self.course.id,
|
||||||
|
field_name=self.question2, field_value=self.answer2)
|
||||||
|
self.survey5 = SurveyAnswer.objects.create(user=self.student2, form=self.survey_form, course_key=self.course.id,
|
||||||
|
field_name=self.question3, field_value=self.answer1)
|
||||||
|
|
||||||
|
def test_successfully_generate_course_survey_report(self):
|
||||||
|
"""
|
||||||
|
Test that successfully generates the course survey report.
|
||||||
|
"""
|
||||||
|
task_input = {'features': []}
|
||||||
|
with patch('instructor_task.tasks_helper._get_current_task'):
|
||||||
|
result = upload_course_survey_report(
|
||||||
|
None, None, self.course.id,
|
||||||
|
task_input, 'generating course survey report'
|
||||||
|
)
|
||||||
|
self.assertDictContainsSubset({'attempted': 2, 'succeeded': 2, 'failed': 0}, result)
|
||||||
|
|
||||||
|
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
|
||||||
|
def test_generate_course_survey_report(self):
|
||||||
|
"""
|
||||||
|
test to generate course survey report
|
||||||
|
and then test the report authenticity.
|
||||||
|
"""
|
||||||
|
|
||||||
|
task_input = {'features': []}
|
||||||
|
with patch('instructor_task.tasks_helper._get_current_task'):
|
||||||
|
result = upload_course_survey_report(
|
||||||
|
None, None, self.course.id,
|
||||||
|
task_input, 'generating course survey report'
|
||||||
|
)
|
||||||
|
|
||||||
|
report_store = ReportStore.from_config(config_name='GRADES_DOWNLOAD')
|
||||||
|
header_row = ",".join(['User ID', 'User Name', 'Email', self.question1, self.question2, self.question3])
|
||||||
|
student1_row = ",".join([
|
||||||
|
str(self.student1.id), # pylint: disable=no-member
|
||||||
|
self.student1.username,
|
||||||
|
self.student1.email,
|
||||||
|
self.answer1,
|
||||||
|
self.answer2
|
||||||
|
])
|
||||||
|
student2_row = ",".join([
|
||||||
|
str(self.student2.id), # pylint: disable=no-member
|
||||||
|
self.student2.username,
|
||||||
|
self.student2.email,
|
||||||
|
self.answer3,
|
||||||
|
self.answer2,
|
||||||
|
self.answer1
|
||||||
|
])
|
||||||
|
expected_data = [header_row, student1_row, student2_row]
|
||||||
|
|
||||||
|
self.assertDictContainsSubset({'attempted': 2, 'succeeded': 2, 'failed': 0}, result)
|
||||||
|
self._verify_csv_file_report(report_store, expected_data)
|
||||||
|
|
||||||
|
def _verify_csv_file_report(self, report_store, expected_data):
|
||||||
|
"""
|
||||||
|
Verify course survey data.
|
||||||
|
"""
|
||||||
|
report_csv_filename = report_store.links_for(self.course.id)[0][0]
|
||||||
|
with open(report_store.path_to(self.course.id, report_csv_filename)) as csv_file:
|
||||||
|
csv_file_data = csv_file.read()
|
||||||
|
for data in expected_data:
|
||||||
|
self.assertIn(data, csv_file_data)
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
class TestStudentReport(TestReportMixin, InstructorTaskCourseTestCase):
|
class TestStudentReport(TestReportMixin, InstructorTaskCourseTestCase):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ class DataDownload
|
|||||||
@$list_studs_btn = @$section.find("input[name='list-profiles']'")
|
@$list_studs_btn = @$section.find("input[name='list-profiles']'")
|
||||||
@$list_studs_csv_btn = @$section.find("input[name='list-profiles-csv']'")
|
@$list_studs_csv_btn = @$section.find("input[name='list-profiles-csv']'")
|
||||||
@$list_proctored_exam_results_csv_btn = @$section.find("input[name='proctored-exam-results-report']'")
|
@$list_proctored_exam_results_csv_btn = @$section.find("input[name='proctored-exam-results-report']'")
|
||||||
|
@$survey_results_csv_btn = @$section.find("input[name='survey-results-report']'")
|
||||||
@$list_may_enroll_csv_btn = @$section.find("input[name='list-may-enroll-csv']")
|
@$list_may_enroll_csv_btn = @$section.find("input[name='list-may-enroll-csv']")
|
||||||
@$list_problem_responses_csv_input = @$section.find("input[name='problem-location']")
|
@$list_problem_responses_csv_input = @$section.find("input[name='problem-location']")
|
||||||
@$list_problem_responses_csv_btn = @$section.find("input[name='list-problem-responses-csv']")
|
@$list_problem_responses_csv_btn = @$section.find("input[name='list-problem-responses-csv']")
|
||||||
@@ -121,6 +122,25 @@ class DataDownload
|
|||||||
@$reports_request_response.text data['status']
|
@$reports_request_response.text data['status']
|
||||||
$(".msg-confirm").css({"display":"block"})
|
$(".msg-confirm").css({"display":"block"})
|
||||||
|
|
||||||
|
# attach click handlers
|
||||||
|
# The list_proctored_exam_results case is always CSV
|
||||||
|
@$survey_results_csv_btn.click (e) =>
|
||||||
|
url = @$survey_results_csv_btn.data 'endpoint'
|
||||||
|
# display html from survey results config endpoint
|
||||||
|
$.ajax
|
||||||
|
dataType: 'json'
|
||||||
|
url: url
|
||||||
|
error: (std_ajax_err) =>
|
||||||
|
@clear_display()
|
||||||
|
@$reports_request_response_error.text gettext(
|
||||||
|
"Error generating survey results. Please try again."
|
||||||
|
)
|
||||||
|
$(".msg-error").css({"display":"block"})
|
||||||
|
success: (data) =>
|
||||||
|
@clear_display()
|
||||||
|
@$reports_request_response.text data['status']
|
||||||
|
$(".msg-confirm").css({"display":"block"})
|
||||||
|
|
||||||
# this handler binds to both the download
|
# this handler binds to both the download
|
||||||
# and the csv button
|
# and the csv button
|
||||||
@$list_studs_csv_btn.click (e) =>
|
@$list_studs_csv_btn.click (e) =>
|
||||||
|
|||||||
@@ -40,6 +40,11 @@
|
|||||||
<p><input type="button" name="proctored-exam-results-report" value="${_("Generate Proctored Exam Results Report")}" data-endpoint="${ section_data['list_proctored_results_url'] }"/></p>
|
<p><input type="button" name="proctored-exam-results-report" value="${_("Generate Proctored Exam Results Report")}" data-endpoint="${ section_data['list_proctored_results_url'] }"/></p>
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
|
%if section_data['course_has_survey']:
|
||||||
|
<p>${_("Click to generate a CSV file of survey results for this course.")}</p>
|
||||||
|
<p><input type="button" name="survey-results-report" value="${_("Generate Survey Results Report")}" data-endpoint="${ section_data['course_survey_results_url'] }"/></p>
|
||||||
|
%endif
|
||||||
|
|
||||||
<p>${_("To generate a CSV file that lists all student answers to a given problem, enter the location of the problem (from its Staff Debug Info).")}</p>
|
<p>${_("To generate a CSV file that lists all student answers to a given problem, enter the location of the problem (from its Staff Debug Info).")}</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
Reference in New Issue
Block a user