New vs Old Data-Download UI. (#24840)
Created new UI for Data download in instructors dashboard Co-authored-by: Awais Jibran <awaisdar001@gmail.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/* global gettext */
|
||||
import { Button } from '@edx/paragon';
|
||||
import { Button, Icon } from '@edx/paragon';
|
||||
import BlockBrowserContainer from 'BlockBrowser/components/BlockBrowser/BlockBrowserContainer';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import * as React from 'react';
|
||||
@@ -29,35 +29,56 @@ export default class Main extends React.Component {
|
||||
this.props.problemResponsesEndpoint,
|
||||
this.props.taskStatusEndpoint,
|
||||
this.props.reportDownloadEndpoint,
|
||||
this.props.selectedBlock,
|
||||
);
|
||||
this.props.selectedBlock);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const { selectedBlock, onSelectBlock } = this.props;
|
||||
let selectorType = <Button onClick={this.handleToggleDropdown} label={gettext('Select a section or problem')} />;
|
||||
if (this.props.showBtnUi === 'false') {
|
||||
selectorType =
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
(<span
|
||||
onClick={this.handleToggleDropdown}
|
||||
className={['problem-selector']}
|
||||
>
|
||||
<span>{selectedBlock || 'Select a section or problem'}</span>
|
||||
<span className={['pull-right']}>
|
||||
<Icon
|
||||
className={['fa', 'fa-sort']}
|
||||
/>
|
||||
</span>
|
||||
</span>);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="problem-browser-container">
|
||||
<div className="problem-browser">
|
||||
<Button
|
||||
onClick={this.handleToggleDropdown}
|
||||
label={gettext('Select a section or problem')}
|
||||
{selectorType}
|
||||
<input
|
||||
type="text"
|
||||
name="problem-location"
|
||||
value={selectedBlock}
|
||||
disabled
|
||||
hidden={this.props.showBtnUi === 'false'}
|
||||
/>
|
||||
<input type="text" name="problem-location" value={selectedBlock} disabled />
|
||||
{this.state.showDropdown &&
|
||||
<BlockBrowserContainer
|
||||
onSelectBlock={(blockId) => {
|
||||
this.hideDropdown();
|
||||
onSelectBlock(blockId);
|
||||
}}
|
||||
/>}
|
||||
<BlockBrowserContainer
|
||||
onSelectBlock={(blockId) => {
|
||||
this.hideDropdown();
|
||||
onSelectBlock(blockId);
|
||||
}}
|
||||
/>}
|
||||
<Button
|
||||
onClick={this.initiateReportGeneration}
|
||||
name="list-problem-responses-csv"
|
||||
label={gettext('Create a report of problem responses')}
|
||||
/>
|
||||
<ReportStatusContainer />
|
||||
</div>
|
||||
<ReportStatusContainer />
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -73,6 +94,7 @@ Main.propTypes = {
|
||||
selectedBlock: PropTypes.string,
|
||||
taskStatusEndpoint: PropTypes.string.isRequired,
|
||||
reportDownloadEndpoint: PropTypes.string.isRequired,
|
||||
ShowBtnUi: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
Main.defaultProps = {
|
||||
|
||||
@@ -18,6 +18,7 @@ exports[`ProblemBrowser Main component render with basic parameters 1`] = `
|
||||
</button>
|
||||
<input
|
||||
disabled={true}
|
||||
hidden={false}
|
||||
name="problem-location"
|
||||
type="text"
|
||||
value={null}
|
||||
@@ -32,11 +33,11 @@ exports[`ProblemBrowser Main component render with basic parameters 1`] = `
|
||||
>
|
||||
Create a report of problem responses
|
||||
</button>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="report-generation-status"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="report-generation-status"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -58,6 +59,7 @@ exports[`ProblemBrowser Main component render with selected block 1`] = `
|
||||
</button>
|
||||
<input
|
||||
disabled={true}
|
||||
hidden={false}
|
||||
name="problem-location"
|
||||
type="text"
|
||||
value="some-selected-block"
|
||||
@@ -72,10 +74,10 @@ exports[`ProblemBrowser Main component render with selected block 1`] = `
|
||||
>
|
||||
Create a report of problem responses
|
||||
</button>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="report-generation-status"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="report-generation-status"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -14,7 +14,7 @@ const ReportStatus = ({ error, succeeded, inProgress, reportPath }) => {
|
||||
|
||||
const successMessage = (
|
||||
<div className="msg success">
|
||||
{gettext('Your report has being successfully generated.')}
|
||||
{gettext('Your report has been successfully generated.')}
|
||||
{reportPath &&
|
||||
<a href={reportPath}>
|
||||
<Icon hidden className={['fa', 'fa-link']} />
|
||||
|
||||
@@ -42,7 +42,7 @@ exports[`ReportStatus component render success status 1`] = `
|
||||
<div
|
||||
className="msg success"
|
||||
>
|
||||
Your report has being successfully generated.
|
||||
Your report has been successfully generated.
|
||||
<a
|
||||
href="/some/report/path.csv"
|
||||
>
|
||||
|
||||
@@ -26,6 +26,7 @@ from lms.djangoapps.courseware.tests.factories import StaffFactory, StudentModul
|
||||
from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase
|
||||
from lms.djangoapps.grades.config.waffle import WRITABLE_GRADEBOOK, waffle_flags
|
||||
from lms.djangoapps.instructor.views.gradebook_api import calculate_page_info
|
||||
from lms.djangoapps.instructor.toggles import DATA_DOWNLOAD_V2
|
||||
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
|
||||
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
|
||||
from student.models import CourseEnrollment
|
||||
@@ -136,31 +137,39 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT
|
||||
self.assertTrue(has_instructor_tab(org_researcher, self.course))
|
||||
|
||||
@ddt.data(
|
||||
('staff', False),
|
||||
('instructor', False),
|
||||
('data_researcher', True),
|
||||
('global_staff', True),
|
||||
('staff', False, False),
|
||||
('instructor', False, False),
|
||||
('data_researcher', True, False),
|
||||
('global_staff', True, False),
|
||||
('staff', False, True),
|
||||
('instructor', False, True),
|
||||
('data_researcher', True, True),
|
||||
('global_staff', True, True),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_data_download(self, access_role, can_access):
|
||||
def test_data_download(self, access_role, can_access, waffle_status):
|
||||
"""
|
||||
Verify that the Data Download tab only shows up for certain roles
|
||||
"""
|
||||
download_section = '<li class="nav-item"><button type="button" class="btn-link data_download" '\
|
||||
'data-section="data_download">Data Download</button></li>'
|
||||
user = UserFactory.create(is_staff=access_role == 'global_staff')
|
||||
CourseAccessRoleFactory(
|
||||
course_id=self.course.id,
|
||||
user=user,
|
||||
role=access_role,
|
||||
org=self.course.id.org
|
||||
)
|
||||
self.client.login(username=user.username, password="test")
|
||||
response = self.client.get(self.url)
|
||||
if can_access:
|
||||
self.assertContains(response, download_section)
|
||||
else:
|
||||
self.assertNotContains(response, download_section)
|
||||
with override_waffle_flag(DATA_DOWNLOAD_V2, waffle_status):
|
||||
download_section = '<li class="nav-item"><button type="button" class="btn-link data_download" ' \
|
||||
'data-section="data_download">Data Download</button></li>'
|
||||
if waffle_status:
|
||||
download_section = '<li class="nav-item"><button type="button" class="btn-link data_download_2" '\
|
||||
'data-section="data_download_2">Data Download</button></li>'
|
||||
user = UserFactory.create(is_staff=access_role == 'global_staff')
|
||||
CourseAccessRoleFactory(
|
||||
course_id=self.course.id,
|
||||
user=user,
|
||||
role=access_role,
|
||||
org=self.course.id.org
|
||||
)
|
||||
self.client.login(username=user.username, password="test")
|
||||
response = self.client.get(self.url)
|
||||
if can_access:
|
||||
self.assertContains(response, download_section)
|
||||
else:
|
||||
self.assertNotContains(response, download_section)
|
||||
|
||||
@override_settings(ANALYTICS_DASHBOARD_URL='http://example.com')
|
||||
@override_settings(ANALYTICS_DASHBOARD_NAME='Example')
|
||||
|
||||
54
lms/djangoapps/instructor/toggles.py
Normal file
54
lms/djangoapps/instructor/toggles.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Waffle flags for instructor dashboard.
|
||||
"""
|
||||
|
||||
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace, WaffleFlag
|
||||
|
||||
WAFFLE_NAMESPACE = 'instructor'
|
||||
# Namespace for instructor waffle flags.
|
||||
WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name=WAFFLE_NAMESPACE)
|
||||
|
||||
# Waffle flag enable new data download UI on specific course.
|
||||
# .. toggle_name: instructor.enable_data_download_v2
|
||||
# .. toggle_implementation: WaffleFlag
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: instructor
|
||||
# .. toggle_category: Instructor dashboard
|
||||
# .. toggle_use_cases: incremental_release, open_edx
|
||||
# .. toggle_creation_date: 2020-07-8
|
||||
# .. toggle_expiration_date: ??
|
||||
# .. toggle_warnings: ??
|
||||
# .. toggle_tickets: PROD-1309
|
||||
# .. toggle_status: supported
|
||||
DATA_DOWNLOAD_V2 = CourseWaffleFlag(
|
||||
waffle_namespace=WaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix='instructor_dashboard: '),
|
||||
flag_name='enable_data_download_v2',
|
||||
)
|
||||
|
||||
# Waffle flag to use optimised is_small_course.
|
||||
# .. toggle_name: verify_student.optimised_is_small_course
|
||||
# .. toggle_implementation: WaffleFlag
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: Supports staged rollout to improved is_small_course method.
|
||||
# .. toggle_category: instructor
|
||||
# .. toggle_use_cases: incremental_release, open_edx
|
||||
# .. toggle_creation_date: 2020-07-02
|
||||
# .. toggle_expiration_date: n/a
|
||||
# .. toggle_warnings: n/a
|
||||
# .. toggle_tickets: PROD-1740
|
||||
# .. toggle_status: supported
|
||||
OPTIMISED_IS_SMALL_COURSE = WaffleFlag(
|
||||
waffle_namespace=WAFFLE_FLAG_NAMESPACE,
|
||||
flag_name='optimised_is_small_course',
|
||||
)
|
||||
|
||||
|
||||
def data_download_v2_is_enabled(course_key):
|
||||
"""
|
||||
check if data download v2 is enabled.
|
||||
"""
|
||||
return DATA_DOWNLOAD_V2.is_enabled(course_key)
|
||||
|
||||
|
||||
def use_optimised_is_small_course():
|
||||
return OPTIMISED_IS_SMALL_COURSE.is_enabled()
|
||||
@@ -65,6 +65,7 @@ from xmodule.tabs import CourseTab
|
||||
|
||||
from .tools import get_units_with_due_date, title_or_url
|
||||
from .. import permissions
|
||||
from ..toggles import data_download_v2_is_enabled
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -600,9 +601,9 @@ def _section_data_download(course, access):
|
||||
settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False) and
|
||||
course.enable_proctored_exams
|
||||
)
|
||||
|
||||
section_key = 'data_download_2' if data_download_v2_is_enabled(course_key) else 'data_download'
|
||||
section_data = {
|
||||
'section_key': 'data_download',
|
||||
'section_key': section_key,
|
||||
'section_display_name': _('Data Download'),
|
||||
'access': access,
|
||||
'show_generate_proctored_exam_report_button': show_proctored_report_button,
|
||||
|
||||
@@ -1,9 +1,224 @@
|
||||
<div class="issued_certificates">
|
||||
<p>${_("Click to list certificates that are issued for this course:")}</p>
|
||||
<span>
|
||||
<input type="button" name="issued-certificates-list" value="View Certificates Issued" >
|
||||
<input type="button" name="issued-certificates-csv" value="Download CSV of Certificates Issued" >
|
||||
</span>
|
||||
<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>
|
||||
<div class="data-download-container action-type-container">
|
||||
<ul class="data-download-nav">
|
||||
|
||||
<li class="nav-item ">
|
||||
<button type="button" class="btn-link reports active-section" data-section="reports">Reports</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button type="button" class="btn-link problem-report" data-section="problem">Problem Report</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button type="button" class="btn-link certificates" data-section="certificate">Certificates</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button type="button" class="btn-link grading" data-section="grading">Grading</button>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<section id="reports" class="idash-section tab-data" aria-labelledby="header-reports">
|
||||
<h6 class="mb-15" id="header-reports">
|
||||
<strong>${_("NOTE")}: </strong>
|
||||
Please select the report type and then click Download Report button
|
||||
</h6>
|
||||
<div class="mb-15">
|
||||
|
||||
<div class="mb-5">
|
||||
<select class="report-type selector">
|
||||
<option value="gradingConfiguration"
|
||||
data-endpoint="">
|
||||
Grading Configuration
|
||||
</option>
|
||||
<option value="listAnonymizeStudentIDs" data-endpoint=""
|
||||
class=""
|
||||
aria-disabled="">Anonymized Student IDs
|
||||
</option>
|
||||
<option value="profileInformation"
|
||||
data-endpoint=""
|
||||
data-csv="true">Profile Information
|
||||
</option>
|
||||
<option value="learnerWhoCanEnroll"
|
||||
data-endpoint="" data-csv="true">
|
||||
Learner
|
||||
who can enroll
|
||||
</option>
|
||||
<option value="listEnrolledPeople"
|
||||
data-endpoint="">
|
||||
List enrolled students profile information
|
||||
</option>
|
||||
<option value="proctoredExamResults"
|
||||
data-endpoint="">Proctored exam results
|
||||
</option>
|
||||
<option value="surveyResultReport"
|
||||
data-endpoint="">
|
||||
Survey Result report
|
||||
</option>
|
||||
<option value="ORADataReport" data-graderelated="true"
|
||||
data-endpoint="">ORA Data
|
||||
report
|
||||
</option>
|
||||
<option data-graderelated="true" value="problemGradeReport"
|
||||
data-endpoint="">Problem Grade report
|
||||
</option>
|
||||
</select>
|
||||
<button class="btn-brand download-report" type="button" value="download report">Download
|
||||
report
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="selectionInfo gradingConfiguration">${_("Click to display 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.")}</p>
|
||||
<p hidden="hidden" class="selectionInfo listAnonymizeStudentIDs">${_("Click to download a CSV of \
|
||||
anonymized student IDs:")}</p>
|
||||
|
||||
<p hidden="hidden" class="selectionInfo reports"> ${_("For large courses, generating some reports can take \
|
||||
several hours. When report generation is complete, a \
|
||||
link that includes the date and time of generation appears in the table below. These reports are \
|
||||
generated in the background, meaning it is OK to navigate away from this page while your report is \
|
||||
generating.")}</p>
|
||||
|
||||
<p hidden="hidden" class="selectionInfo reports">${_("Please be patient and do not click these buttons \
|
||||
multiple times. Clicking these buttons multiple times will significantly slow the generation \
|
||||
process.")}
|
||||
</p>
|
||||
<p hidden="hidden" class="selectionInfo listEnrolledPeople">${_("For smaller courses, click to list \
|
||||
profile information for enrolled students directly on this page:")}</p>
|
||||
<p hidden="hidden" class="selectionInfo reports profileInformation">${_("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 hidden="hidden" class="selectionInfo reports learnerWhoCanEnroll">${_("Click to generate a CSV file \
|
||||
that lists learners who can enroll in the course but have not yet done so.")}</p>
|
||||
|
||||
<p hidden="hidden" class="selectionInfo reports proctoredExamResults">${_("Click to generate a CSV file \
|
||||
of all proctored exam results in this course.")}</p>
|
||||
|
||||
<p hidden="hidden" class="selectionInfo reports surveyResultReport">${_("Click to generate a CSV file of \
|
||||
survey results for this course.")}</p>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<section id="certificate" class="idash-section tab-data" aria-labelledby="header-cert">
|
||||
<h6 class="mb-15" id="header-cert">
|
||||
<strong>${_("NOTE")}: </strong>
|
||||
Please select the report type and then click Download Report button
|
||||
</h6>
|
||||
|
||||
<select class="report-type selector">
|
||||
<option value="viewCertificates" data-csv="false"
|
||||
data-endpoint="">View certificates
|
||||
</option>
|
||||
<option value="downloadCertificates" data-csv="true"
|
||||
data-endpoint="">Download csv of
|
||||
certificates
|
||||
</option>
|
||||
</select>
|
||||
<button class="mb-20 btn-brand download-report" type="button" value="download report">Download
|
||||
report
|
||||
</button>
|
||||
<div>
|
||||
<p>${_("Click to list certificates that are issued for this course:")}</p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="problem" class="idash-section tab-data" aria-labelledby="header-problem">
|
||||
<h6 class="mb-20" id="header-problem">
|
||||
${_("Select a problem to generate a CSV \
|
||||
file that lists all student answers to the problem. You also select a section or chapter to include \
|
||||
results of all problems in that section or chapter.")}
|
||||
</h6>
|
||||
<div class="mb-15 problems">
|
||||
${static.renderReact(
|
||||
component="ProblemBrowser",
|
||||
id="react-block-listing",
|
||||
props={
|
||||
"courseId": course.id,
|
||||
"excludeBlockTypes": ['html', 'video', 'discussion']
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<button data-endpoint="" id="download-problem-report"
|
||||
class="btn-brand mb-20" type="button" value="download report">Download
|
||||
report
|
||||
</button>
|
||||
<p class="mb-15">
|
||||
<strong>${_("NOTE")}: </strong>
|
||||
${_("The generated report is limited to {max_entries} responses. If you expect more than {max_entries} "
|
||||
"responses, try generating the report on a chapter-by-chapter, or problem-by-problem basis, or contact "
|
||||
"your site administrator to increase the limit.").format(max_entries=max_entries)}
|
||||
</p>
|
||||
</section>
|
||||
<section id="grading" class="idash-section tab-data" aria-labelledby="header-grading">
|
||||
<h6 class="mb-15" id="header-grading">
|
||||
<strong>${_("NOTE")}: </strong>
|
||||
Please select the report type and then click Download Report button
|
||||
</h6>
|
||||
<br>
|
||||
|
||||
<p>Learner status</p>
|
||||
<select class="learner-status selector">
|
||||
<option value="true">Verified Learners Only</option>
|
||||
<option value="false">All Learners</option>
|
||||
</select>
|
||||
|
||||
<button class="mb-20 btn-brand grade-report-download" type="button"
|
||||
value="download report"
|
||||
data-endpoint="">Download Report
|
||||
</button>
|
||||
<div>
|
||||
<p>${_("Click to generate a CSV grade report for all currently enrolled students.")}</p>
|
||||
</div>
|
||||
</section>
|
||||
<div class="request-response message msg-confirm copy" id="report-request-response"></div>
|
||||
<div class="request-response-error message msg-error copy" id="report-request-response-error"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="reports-download-container action-type-container">
|
||||
<div class="data-display-text" id="data-grade-config-text"></div>
|
||||
<div class="data-display-table profile-data-display-table" id="data-student-profiles-table"></div>
|
||||
<div class="data-display-table data-display-table-holder" id="data-issued-certificates-table"></div>
|
||||
<hr>
|
||||
|
||||
<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>
|
||||
|
||||
<p>
|
||||
${_("The answer distribution report listed below is generated periodically by an automated background process. \
|
||||
The report is cumulative, so answers submitted after the process starts are included in a subsequent report. \
|
||||
The report is generated several times per day.")}
|
||||
</p>
|
||||
<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=""></div>
|
||||
|
||||
</div>
|
||||
<div id="data_download_2" class="running-tasks-container action-type-container">
|
||||
<hr>
|
||||
<h3 class="hd hd-3">${_("Pending Tasks")}</h3>
|
||||
<div class="running-tasks-section">
|
||||
<p>${_("The status for any active tasks appears in a table below.")} </p>
|
||||
<br/>
|
||||
<div class="running-tasks-table" data-endpoint=""></div>
|
||||
</div>
|
||||
<div class="no-pending-tasks-message"></div>
|
||||
</div>
|
||||
|
||||
|
||||
257
lms/static/js/instructor_dashboard/data_download_2.js
Normal file
257
lms/static/js/instructor_dashboard/data_download_2.js
Normal file
@@ -0,0 +1,257 @@
|
||||
/* globals _, DataDownloadV2, PendingInstructorTasks, ReportDownloads */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var DataDownloadV2, PendingInstructorTasks, ReportDownloads, statusAjaxError;
|
||||
|
||||
statusAjaxError = function() {
|
||||
return window.InstructorDashboard.util.statusAjaxError.apply(this, arguments);
|
||||
};
|
||||
|
||||
PendingInstructorTasks = function() {
|
||||
return window.InstructorDashboard.util.PendingInstructorTasks;
|
||||
};
|
||||
|
||||
ReportDownloads = function() {
|
||||
return window.InstructorDashboard.util.ReportDownloads;
|
||||
};
|
||||
|
||||
DataDownloadV2 = (function() {
|
||||
function InstructorDashboardDataDownload($section) {
|
||||
var dataDownloadObj = this;
|
||||
this.$section = $section;
|
||||
this.$section.data('wrapper', this);
|
||||
this.$list_problem_responses_csv_input = this.$section.find("input[name='problem-location']");
|
||||
this.$download_display_text = $('.data-display-text');
|
||||
this.$download_request_response_error = $('.request-response-error');
|
||||
this.$download_display_table = $('.profile-data-display-table');
|
||||
this.$reports_request_response = $('.request-response');
|
||||
this.$reports_request_response_error = $('.request-response-error');
|
||||
this.report_downloads = new (ReportDownloads())(this.$section);
|
||||
this.instructor_tasks = new (PendingInstructorTasks())(this.$section);
|
||||
this.$download_report = $('.download-report');
|
||||
this.$gradeReportDownload = $('.grade-report-download');
|
||||
this.$report_type_selector = $('.report-type');
|
||||
this.$selection_informations = $('.selectionInfo');
|
||||
this.$data_display_table = $('.data-display-table-holder');
|
||||
this.$downloadProblemReport = $('#download-problem-report');
|
||||
this.$tabSwitch = $('.data-download-nav .btn-link');
|
||||
this.$selectedSection = $('#' + this.$tabSwitch.first().attr('data-section'));
|
||||
this.$learnerStatus = $('.learner-status');
|
||||
|
||||
this.ERROR_MESSAGES = {
|
||||
ORADataReport: gettext('Error generating ORA data report. Please try again.'),
|
||||
problemGradeReport: gettext('Error generating problem grade report. Please try again.'),
|
||||
profileInformation: gettext('Error generating student profile information. Please try again.'),
|
||||
surveyResultReport: gettext('Error generating survey results. Please try again.'),
|
||||
proctoredExamResults: gettext('Error generating proctored exam results. Please try again.'),
|
||||
learnerWhoCanEnroll: gettext('Error generating list of students who may enroll. Please try again.'),
|
||||
viewCertificates: gettext('Error getting issued certificates list.')
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes text error/success messages and tables from UI
|
||||
*/
|
||||
this.clear_display = function() {
|
||||
this.$download_display_text.empty();
|
||||
this.$download_display_table.empty();
|
||||
this.$download_request_response_error.empty();
|
||||
this.$reports_request_response.empty();
|
||||
this.$reports_request_response_error.empty();
|
||||
this.$data_display_table.empty();
|
||||
$('.msg-confirm').css({
|
||||
display: 'none'
|
||||
});
|
||||
return $('.msg-error').css({
|
||||
display: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
this.clear_display();
|
||||
|
||||
/**
|
||||
* Show and hide selected tab data
|
||||
*/
|
||||
this.$tabSwitch.click(function(event) {
|
||||
var selectedSection = '#' + $(this).attr('data-section');
|
||||
event.preventDefault();
|
||||
$('.data-download-nav .btn-link').removeClass('active-section');
|
||||
$('section.tab-data').hide();
|
||||
$(selectedSection).show();
|
||||
$(this).addClass('active-section');
|
||||
|
||||
$(this).find('select').trigger('change');
|
||||
dataDownloadObj.$selectedSection = $(selectedSection);
|
||||
|
||||
dataDownloadObj.clear_display();
|
||||
});
|
||||
|
||||
this.$tabSwitch.first().click();
|
||||
|
||||
/**
|
||||
* on change of report select update show and hide related descriptions
|
||||
*/
|
||||
this.$report_type_selector.change(function() {
|
||||
var selectedOption = dataDownloadObj.$report_type_selector.val();
|
||||
dataDownloadObj.$selection_informations.each(function(index, elem) {
|
||||
if ($(elem).hasClass(selectedOption)) {
|
||||
$(elem).show();
|
||||
} else {
|
||||
$(elem).hide();
|
||||
}
|
||||
});
|
||||
dataDownloadObj.clear_display();
|
||||
});
|
||||
|
||||
this.selectedOption = function() {
|
||||
return dataDownloadObj.$selectedSection.find('select').find('option:selected');
|
||||
};
|
||||
|
||||
/**
|
||||
* On click download button get selected option and pass it to handler function.
|
||||
*/
|
||||
this.downloadReportClickHandler = function() {
|
||||
var selectedOption = dataDownloadObj.selectedOption();
|
||||
var errorMessage = dataDownloadObj.ERROR_MESSAGES[selectedOption.val()];
|
||||
|
||||
if (selectedOption.data('directdownload')) {
|
||||
location.href = selectedOption.data('endpoint') + '?csv=true';
|
||||
} else if (selectedOption.data('datatable')) {
|
||||
dataDownloadObj.renderDataTable(selectedOption);
|
||||
} else {
|
||||
dataDownloadObj.downloadCSV(selectedOption, errorMessage, false);
|
||||
}
|
||||
};
|
||||
this.$download_report.click(dataDownloadObj.downloadReportClickHandler);
|
||||
|
||||
/**
|
||||
* Call data endpoint and invoke buildDataTable to render Table UI.
|
||||
* @param selected option element from report selector to get data-endpoint.
|
||||
* @param errorMessage Error message in case endpoint call fail.
|
||||
*/
|
||||
this.renderDataTable = function(selected, errorMessage) {
|
||||
var url = selected.data('endpoint');
|
||||
dataDownloadObj.clear_display();
|
||||
dataDownloadObj.$data_display_table.text(gettext('Loading data...'));
|
||||
return $.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
error: function(error) {
|
||||
dataDownloadObj.OnError(error, errorMessage);
|
||||
},
|
||||
success: function(data) {
|
||||
dataDownloadObj.buildDataTable(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
this.$downloadProblemReport.click(function() {
|
||||
var data = {problem_location: dataDownloadObj.$list_problem_responses_csv_input.val()};
|
||||
dataDownloadObj.downloadCSV($(this), false, data);
|
||||
});
|
||||
|
||||
this.$gradeReportDownload.click(function() {
|
||||
var errorMessage = gettext('Error generating grades. Please try again.');
|
||||
var data = {verified_learners_only: dataDownloadObj.$learnerStatus.val()};
|
||||
dataDownloadObj.downloadCSV($(this), errorMessage, data);
|
||||
});
|
||||
|
||||
/**
|
||||
* Call data endpoint and render success/error message on dashboard UI.
|
||||
*/
|
||||
this.downloadCSV = function(selected, errorMessage, postData) {
|
||||
var url = selected.data('endpoint');
|
||||
dataDownloadObj.clear_display();
|
||||
return $.ajax({
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
url: url,
|
||||
data: postData,
|
||||
error: function(error) {
|
||||
dataDownloadObj.OnError(error, errorMessage);
|
||||
},
|
||||
success: function(data) {
|
||||
if (data.grading_config_summary) {
|
||||
edx.HtmlUtils.setHtml(
|
||||
dataDownloadObj.$download_display_text, edx.HtmlUtils.HTML(data.grading_config_summary));
|
||||
} else {
|
||||
dataDownloadObj.$reports_request_response.text(data.status);
|
||||
$('.msg-confirm').css({display: 'block'});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.OnError = function(error, errorMessage) {
|
||||
dataDownloadObj.clear_display();
|
||||
if (error.responseText) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
errorMessage = JSON.parse(error.responseText);
|
||||
}
|
||||
dataDownloadObj.$download_request_response_error.text(errorMessage);
|
||||
return dataDownloadObj.$download_request_response_error.css({
|
||||
display: 'block'
|
||||
});
|
||||
};
|
||||
/**
|
||||
* render data table on dashboard UI with given data.
|
||||
*/
|
||||
this.buildDataTable = function(data) {
|
||||
var $tablePlaceholder, columns, feature, gridData, options;
|
||||
dataDownloadObj.clear_display();
|
||||
options = {
|
||||
enableCellNavigation: true,
|
||||
enableColumnReorder: false,
|
||||
forceFitColumns: true,
|
||||
rowHeight: 35
|
||||
};
|
||||
columns = (function() {
|
||||
var i, len, ref, results;
|
||||
ref = data.queried_features;
|
||||
results = [];
|
||||
for (i = 0, len = ref.length; i < len; i++) {
|
||||
feature = ref[i];
|
||||
results.push({
|
||||
id: feature,
|
||||
field: feature,
|
||||
name: data.feature_names[feature]
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}());
|
||||
gridData = data.hasOwnProperty('students') ? data.students : data.certificates;
|
||||
$tablePlaceholder = $('<div/>', {
|
||||
class: 'slickgrid'
|
||||
});
|
||||
dataDownloadObj.$download_display_table.append($tablePlaceholder);
|
||||
return new window.Slick.Grid($tablePlaceholder, gridData, columns, options);
|
||||
};
|
||||
}
|
||||
|
||||
InstructorDashboardDataDownload.prototype.onClickTitle = function() {
|
||||
this.clear_display();
|
||||
this.instructor_tasks.task_poller.start();
|
||||
return this.report_downloads.downloads_poller.start();
|
||||
};
|
||||
|
||||
InstructorDashboardDataDownload.prototype.onExit = function() {
|
||||
this.instructor_tasks.task_poller.stop();
|
||||
return this.report_downloads.downloads_poller.stop();
|
||||
};
|
||||
return InstructorDashboardDataDownload;
|
||||
}());
|
||||
|
||||
_.defaults(window, {
|
||||
InstructorDashboard: {}
|
||||
});
|
||||
|
||||
_.defaults(window.InstructorDashboard, {
|
||||
sections: {}
|
||||
});
|
||||
|
||||
_.defaults(window.InstructorDashboard.sections, {
|
||||
DataDownloadV2: DataDownloadV2
|
||||
});
|
||||
}).call(this);
|
||||
@@ -163,6 +163,9 @@ such that the value can be defined later than this assignment (file load order).
|
||||
}, {
|
||||
constructor: window.InstructorDashboard.sections.DataDownload,
|
||||
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#data_download')
|
||||
}, {
|
||||
constructor: window.InstructorDashboard.sections.DataDownloadV2,
|
||||
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#data_download_2')
|
||||
}, {
|
||||
constructor: window.InstructorDashboard.sections.ECommerce,
|
||||
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#e-commerce')
|
||||
|
||||
@@ -1,70 +1,162 @@
|
||||
/* global define */
|
||||
define(['jquery',
|
||||
'js/instructor_dashboard/data_download',
|
||||
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
|
||||
'slick.grid'],
|
||||
function($, DataDownload, AjaxHelpers) {
|
||||
'use strict';
|
||||
describe('edx.instructor_dashboard.data_download.DataDownload_Certificate', function() {
|
||||
var url, dataDownloadCertificates;
|
||||
/* global define, DataDownload */
|
||||
|
||||
beforeEach(function() {
|
||||
loadFixtures('js/fixtures/instructor_dashboard/data_download.html');
|
||||
dataDownloadCertificates = new window.DataDownload_Certificate($('.issued_certificates'));
|
||||
url = '/courses/PU/FSc/2014_T4/instructor/api/get_issued_certificates';
|
||||
dataDownloadCertificates.$list_issued_certificate_table_btn.data('endpoint', url);
|
||||
});
|
||||
define([
|
||||
'jquery',
|
||||
'js/instructor_dashboard/data_download_2',
|
||||
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
|
||||
],
|
||||
function($, id, AjaxHelper) {
|
||||
'use strict';
|
||||
describe('edx.instructor_dashboard.data_download', function() {
|
||||
var requests, $selected, dataDownload, url, errorMessage;
|
||||
|
||||
it('show data on success callback', function() {
|
||||
// Spy on AJAX requests
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
var data = {
|
||||
certificates: [{course_id: 'xyz_test', mode: 'honor'}],
|
||||
queried_features: ['course_id', 'mode'],
|
||||
feature_names: {course_id: 'Course ID', mode: ' Mode'}
|
||||
};
|
||||
beforeEach(function() {
|
||||
loadFixtures('js/fixtures/instructor_dashboard/data_download.html');
|
||||
|
||||
dataDownloadCertificates.$list_issued_certificate_table_btn.click();
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', url);
|
||||
dataDownload = window.InstructorDashboard.sections;
|
||||
dataDownload.DataDownloadV2($('#data_download_2'));
|
||||
window.InstructorDashboard.util.PendingInstructorTasks = function() {
|
||||
return;
|
||||
};
|
||||
requests = AjaxHelper.requests(this);
|
||||
$selected = $('<option data-endpoint="api/url/fake"></option>');
|
||||
url = $selected.data('endpoint');
|
||||
errorMessage = 'An Error is occurred with request';
|
||||
});
|
||||
|
||||
// Simulate a success response from the server
|
||||
AjaxHelpers.respondWithJson(requests, data);
|
||||
expect(dataDownloadCertificates.$certificate_display_table.html()
|
||||
.indexOf('Course ID') !== -1).toBe(true);
|
||||
expect(dataDownloadCertificates.$certificate_display_table.html()
|
||||
.indexOf('Mode') !== -1).toBe(true);
|
||||
expect(dataDownloadCertificates.$certificate_display_table.html()
|
||||
.indexOf('xyz_test') !== -1).toBe(true);
|
||||
expect(dataDownloadCertificates.$certificate_display_table.html()
|
||||
.indexOf('honor') !== -1).toBe(true);
|
||||
});
|
||||
it('renders success message properly', function() {
|
||||
dataDownload.downloadCSV($selected, errorMessage);
|
||||
|
||||
it('show error on failure callback', function() {
|
||||
// Spy on AJAX requests
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
AjaxHelper.expectRequest(requests, 'POST', url);
|
||||
AjaxHelper.respondWithJson(requests, {
|
||||
status: 'Request is succeeded'
|
||||
});
|
||||
expect(dataDownload.$reports_request_response.text()).toContain('Request is succeeded');
|
||||
});
|
||||
|
||||
dataDownloadCertificates.$list_issued_certificate_table_btn.click();
|
||||
// Simulate a error response from the server
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
expect(dataDownloadCertificates.$certificates_request_response_error.text())
|
||||
.toEqual('Error getting issued certificates list.');
|
||||
});
|
||||
|
||||
it('error should be clear from UI on success callback', function() {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
dataDownloadCertificates.$list_issued_certificate_table_btn.click();
|
||||
it('renders grading config returned by the server in case of successful request ', function() {
|
||||
dataDownload.downloadCSV($selected, errorMessage);
|
||||
|
||||
// Simulate a error response from the server
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
expect(dataDownloadCertificates.$certificates_request_response_error.text())
|
||||
.toEqual('Error getting issued certificates list.');
|
||||
AjaxHelper.expectRequest(requests, 'POST', url);
|
||||
AjaxHelper.respondWithJson(requests, {
|
||||
grading_config_summary: 'This is grading config'
|
||||
});
|
||||
expect(dataDownload.$download_display_text.text()).toContain('This is grading config');
|
||||
});
|
||||
|
||||
// Simulate a success response from the server
|
||||
dataDownloadCertificates.$list_issued_certificate_table_btn.click();
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', url);
|
||||
it('renders enrolled student list in case of successful request ', function() {
|
||||
var data = {
|
||||
available_features: [
|
||||
'id',
|
||||
'username',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'is_staff',
|
||||
'email',
|
||||
'date_joined',
|
||||
'last_login',
|
||||
'name',
|
||||
'language',
|
||||
'location',
|
||||
'year_of_birth',
|
||||
'gender',
|
||||
'level_of_education',
|
||||
'mailing_address',
|
||||
'goals',
|
||||
'meta',
|
||||
'city',
|
||||
'country'
|
||||
],
|
||||
course_id: 'test_course_101',
|
||||
feature_names: {
|
||||
gender: 'Gender',
|
||||
goals: 'Goals',
|
||||
enrollment_mode: 'Enrollment Mode',
|
||||
email: 'Email',
|
||||
country: 'Country',
|
||||
id: 'User ID',
|
||||
mailing_address: 'Mailing Address',
|
||||
last_login: 'Last Login',
|
||||
date_joined: 'Date Joined',
|
||||
location: 'Location',
|
||||
city: 'City',
|
||||
verification_status: 'Verification Status',
|
||||
year_of_birth: 'Birth Year',
|
||||
name: 'Name',
|
||||
username: 'Username',
|
||||
level_of_education: 'Level of Education',
|
||||
language: 'Language'
|
||||
},
|
||||
students: [
|
||||
{
|
||||
gender: 'Male',
|
||||
goals: 'Goal',
|
||||
enrollment_mode: 'audit',
|
||||
email: 'test@example.com',
|
||||
country: 'PK',
|
||||
year_of_birth: 'None',
|
||||
id: '8',
|
||||
mailing_address: 'None',
|
||||
last_login: '2020-06-17T08:17:00.561Z',
|
||||
date_joined: '2019-09-25T20:06:17.564Z',
|
||||
location: 'None',
|
||||
verification_status: 'N/A',
|
||||
city: 'None',
|
||||
name: 'None',
|
||||
username: 'test',
|
||||
level_of_education: 'None',
|
||||
language: 'None'
|
||||
}
|
||||
],
|
||||
queried_features: [
|
||||
'id',
|
||||
'username',
|
||||
'name',
|
||||
'email',
|
||||
'language',
|
||||
'location',
|
||||
'year_of_birth',
|
||||
'gender',
|
||||
'level_of_education',
|
||||
'mailing_address',
|
||||
'goals',
|
||||
'enrollment_mode',
|
||||
'verification_status',
|
||||
'last_login',
|
||||
'date_joined',
|
||||
'city',
|
||||
'country'
|
||||
],
|
||||
students_count: 1
|
||||
};
|
||||
dataDownload.renderDataTable($selected, errorMessage);
|
||||
AjaxHelper.expectRequest(requests, 'POST', url);
|
||||
AjaxHelper.respondWithJson(requests, data);
|
||||
// eslint-disable-next-line vars-on-top
|
||||
var dataTable = dataDownload.$data_display_table.html();
|
||||
// eslint-disable-next-line vars-on-top
|
||||
var existInHtml = function(value) {
|
||||
expect(dataTable.indexOf(data.feature_names[value]) !== -1).toBe(false);
|
||||
expect(dataTable.indexOf(data.students[0][value]) !== -1).toBe(false);
|
||||
};
|
||||
data.queried_features.forEach(existInHtml);
|
||||
});
|
||||
|
||||
expect(dataDownloadCertificates.$certificates_request_response_error.text())
|
||||
.not.toEqual('Error getting issued certificates list');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('calls renderDataTable function if data-datatable is true', function() {
|
||||
$selected = $selected.attr('data-datatable', true);
|
||||
spyOn(dataDownload, 'selectedOption').and.returnValue($selected);
|
||||
spyOn(dataDownload, 'renderDataTable');
|
||||
dataDownload.downloadReportClickHandler();
|
||||
expect(dataDownload.renderDataTable).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls downloadCSV function if no other data type is specified', function() {
|
||||
spyOn(dataDownload, 'selectedOption').and.returnValue($selected);
|
||||
spyOn(dataDownload, 'downloadCSV');
|
||||
dataDownload.downloadReportClickHandler();
|
||||
expect(dataDownload.downloadCSV).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -757,6 +757,7 @@
|
||||
'js/spec/financial-assistance/financial_assistance_form_view_spec.js',
|
||||
'js/spec/groups/views/cohorts_spec.js',
|
||||
'js/spec/groups/views/discussions_spec.js',
|
||||
'js/spec/instructor_dashboard/data_download_spec.js',
|
||||
'js/spec/instructor_dashboard/certificates_bulk_exception_spec.js',
|
||||
'js/spec/instructor_dashboard/certificates_exception_spec.js',
|
||||
'js/spec/instructor_dashboard/certificates_invalidation_spec.js',
|
||||
|
||||
@@ -313,7 +313,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.data-download-nav {
|
||||
@extend .instructor-nav
|
||||
}
|
||||
.report-generation-status {
|
||||
.msg {
|
||||
display: inherit;
|
||||
@@ -1506,6 +1508,106 @@
|
||||
|
||||
// view - data download
|
||||
// --------------------
|
||||
|
||||
|
||||
.instructor-dashboard-wrapper-2 section.idash-section#data_download_2 {
|
||||
.data-download-grid-container {
|
||||
display:grid;
|
||||
grid-template-columns:repeat(auto-fit, minmax(20rem, 1fr));
|
||||
grid-auto-rows: minmax(250px, auto);
|
||||
div.card {
|
||||
border-right: 2px solid grey;
|
||||
border-bottom: 2px solid grey;
|
||||
padding: 1em;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 3fr;
|
||||
p.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
align-self: self-end;
|
||||
}
|
||||
.problem-browser {
|
||||
display: grid;
|
||||
grid-gap: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
input {
|
||||
margin-bottom: 1em;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
.reports-download-container {
|
||||
.data-display-table {
|
||||
.slickgrid {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.report-downloads-table {
|
||||
.slickgrid {
|
||||
height: 300px;
|
||||
padding: ($baseline/4);
|
||||
}
|
||||
// Disable horizontal scroll bar when grid only has 1 column. Remove this CSS class when more columns added.
|
||||
.slick-viewport {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.block-browser {
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin: 0.25em 0;
|
||||
|
||||
.block-name {
|
||||
flex-grow: 1;
|
||||
margin-right: 0.5em;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.problem-browser {
|
||||
.block-browser {
|
||||
position: absolute;
|
||||
background: white;
|
||||
padding: 5px;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
input {
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.instructor-dashboard-wrapper-2 section.idash-section#data_download {
|
||||
input {
|
||||
margin-bottom: 1em;
|
||||
@@ -2970,3 +3072,46 @@ div.staff_actions {
|
||||
color: theme-color("success");
|
||||
}
|
||||
}
|
||||
|
||||
.action-type-container p {
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.mb-15 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.mb-20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.download-report {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.selector {
|
||||
width: 315px;
|
||||
height: 34px
|
||||
}
|
||||
.data-download-container .message {
|
||||
border-radius: 1px;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.font-size-100 {
|
||||
font-size: 100%
|
||||
}
|
||||
|
||||
.problem-selector{
|
||||
height: 34px;
|
||||
width: 780px;
|
||||
border: 1px solid grey;
|
||||
display: block;
|
||||
padding: 8px;
|
||||
margin-right: 10px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.block-browser{
|
||||
margin-top: 34px;
|
||||
}
|
||||
|
||||
@@ -7,4 +7,3 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
|
||||
<p>${Text(_("We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible."))}</p>
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
</p>
|
||||
%endif
|
||||
|
||||
<div class="problems">
|
||||
<div class="mb-15 problems">
|
||||
${static.renderReact(
|
||||
component="ProblemBrowser",
|
||||
id="react-problem-report",
|
||||
@@ -67,7 +67,8 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
"excludeBlockTypes": ['html', 'video', 'discussion'],
|
||||
"problemResponsesEndpoint": section_data['get_problem_responses_url'],
|
||||
"taskStatusEndpoint": "/instructor_task_status",
|
||||
"reportDownloadEndpoint": section_data['list_report_downloads_url']
|
||||
"reportDownloadEndpoint": section_data['list_report_downloads_url'],
|
||||
"ShowBtnUi": "true",
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<%page args="section_data" expression_filter="h"/>
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
|
||||
<div class="data-download-container action-type-container">
|
||||
<ul class="data-download-nav">
|
||||
|
||||
<li class="nav-item ">
|
||||
<button type="button" class="btn-link reports active-section" data-section="reports">Reports</button>
|
||||
</li>
|
||||
%if settings.FEATURES.get('ENABLE_GRADE_DOWNLOADS'):
|
||||
<li class="nav-item">
|
||||
<button type="button" class="btn-link problem-report" data-section="problem">Problem Report</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button type="button" class="btn-link certificates" data-section="certificate">Certificates</button>
|
||||
</li>
|
||||
%if settings.FEATURES.get('ALLOW_COURSE_STAFF_GRADE_DOWNLOADS') or section_data['access']['admin']:
|
||||
<li class="nav-item">
|
||||
<button type="button" class="btn-link grading" data-section="grading">Grading</button>
|
||||
</li>
|
||||
%endif
|
||||
%endif
|
||||
|
||||
</ul>
|
||||
<%include file="./data_download_2/reports.html" args="section_data=section_data, **context.kwargs" />
|
||||
|
||||
<%include file="./data_download_2/grading.html" args="section_data=section_data, **context.kwargs" />
|
||||
|
||||
%if settings.FEATURES.get('ENABLE_GRADE_DOWNLOADS'):
|
||||
<%include file="./data_download_2/certificates.html" args="section_data=section_data, **context.kwargs" />
|
||||
<%include file="./data_download_2/problem_report.html" args="section_data=section_data, **context.kwargs" />
|
||||
%endif
|
||||
<div class="request-response message msg-confirm copy" id="report-request-response"></div>
|
||||
<div class="request-response-error message msg-error copy" id="report-request-response-error"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="reports-download-container action-type-container">
|
||||
<div class="data-display-text" id="data-grade-config-text"></div>
|
||||
<div class="data-display-table profile-data-display-table" id="data-student-profiles-table"></div>
|
||||
<div class="data-display-table data-display-table-holder" id="data-issued-certificates-table"></div>
|
||||
<hr>
|
||||
|
||||
<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>
|
||||
|
||||
%if settings.FEATURES.get('ENABLE_ASYNC_ANSWER_DISTRIBUTION'):
|
||||
<p>
|
||||
${_("The answer distribution report listed below is generated periodically by an automated background process. \
|
||||
The report is cumulative, so answers submitted after the process starts are included in a subsequent report. \
|
||||
The report is generated several times per day.")}
|
||||
</p>
|
||||
%endif
|
||||
|
||||
## 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'):
|
||||
<div class="running-tasks-container action-type-container">
|
||||
<hr>
|
||||
<h3 class="hd hd-3">${_("Pending Tasks")}</h3>
|
||||
<div class="running-tasks-section">
|
||||
<p>${_("The status for any active tasks appears in a table below.")} </p>
|
||||
<br/>
|
||||
<div class="running-tasks-table" data-endpoint="${ section_data['list_instructor_tasks_url'] }"></div>
|
||||
</div>
|
||||
<div class="no-pending-tasks-message"></div>
|
||||
</div>
|
||||
%endif
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<%page args="section_data" expression_filter="h"/>
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
|
||||
<section id="certificate" class="idash-section tab-data" aria-labelledby="header-cert">
|
||||
<h6 class="mb-15 font-size-100" id="header-cert">
|
||||
<strong>${_("Note")}: </strong>
|
||||
Please certificate report type option and then click Download Report button.
|
||||
</h6>
|
||||
|
||||
<select class="report-type selector">
|
||||
<option value="viewCertificates" data-csv="false"
|
||||
data-datatable="true"
|
||||
data-endpoint="${ section_data['get_issued_certificates_url'] }">View certificates
|
||||
</option>
|
||||
<option value="downloadCertificates"
|
||||
data-csv="true"
|
||||
data-directdownload="true"
|
||||
data-endpoint="${ section_data['get_issued_certificates_url'] }">Download csv of
|
||||
certificates
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<input type="button"
|
||||
value="Download Report"
|
||||
class="mb-20 download-report">
|
||||
<div>
|
||||
<p>${_("Click to list certificates that are issued for this course:")}</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,28 @@
|
||||
<%page args="section_data" expression_filter="h"/>
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
|
||||
%if settings.FEATURES.get('ALLOW_COURSE_STAFF_GRADE_DOWNLOADS') or section_data['access']['admin']:
|
||||
<section id="grading" class="idash-section tab-data" aria-labelledby="header-grading">
|
||||
<h6 class="mb-15 font-size-100" id="header-grading">
|
||||
<strong>${_("Note")}: </strong>
|
||||
Please select learner status and then click "Download Course Grade Report" button.
|
||||
</h6>
|
||||
<p>Learner status</p>
|
||||
<select class="learner-status selector">
|
||||
<option value="true">Verified Learners Only</option>
|
||||
<option value="false">All Learners</option>
|
||||
</select>
|
||||
|
||||
<input data-endpoint="${ section_data['calculate_grades_csv_url'] }"
|
||||
type="button"
|
||||
value="Download Course Grade Report"
|
||||
class="mb-20 grade-report-download">
|
||||
<div>
|
||||
<p>${_("Click to generate a CSV grade report for all currently enrolled students.")}</p>
|
||||
</div>
|
||||
</section>
|
||||
%endif
|
||||
@@ -0,0 +1,48 @@
|
||||
<%page args="section_data" expression_filter="h"/>
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
|
||||
<section id="problem" class="idash-section tab-data" aria-labelledby="header-problem">
|
||||
<h6 class="mb-20 font-size-100" id="header-problem">
|
||||
${_("Select a problem to generate a CSV \
|
||||
file that lists all student answers to the problem. You also select a section or chapter to include \
|
||||
results of all problems in that section or chapter.")}
|
||||
</h6>
|
||||
<div class="mb-15 problems">
|
||||
${static.renderReact(
|
||||
component="ProblemBrowser",
|
||||
id="react-problem-report",
|
||||
props={
|
||||
"courseId": course.id,
|
||||
"excludeBlockTypes": ['html', 'video', 'discussion'],
|
||||
"problemResponsesEndpoint": section_data['get_problem_responses_url'],
|
||||
"taskStatusEndpoint": "/instructor_task_status",
|
||||
"reportDownloadEndpoint": section_data['list_report_downloads_url'],
|
||||
"showBtnUi": "false"
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<!-- <button data-endpoint="${ section_data['get_problem_responses_url'] }" id="download-problem-report"-->
|
||||
<!-- class="btn-brand mb-20" type="button" value="download report">Download-->
|
||||
<!-- report-->
|
||||
<!-- </button>-->
|
||||
<input hidden data-endpoint="${ section_data['get_problem_responses_url'] }"
|
||||
type="button"
|
||||
value="Download Report"
|
||||
id="download-problem-report"
|
||||
class="download-report mb-20"
|
||||
style="margin-left: 0">
|
||||
|
||||
<% max_entries = settings.FEATURES.get('MAX_PROBLEM_RESPONSES_COUNT') %>
|
||||
%if max_entries is not None:
|
||||
<p class="mb-15">
|
||||
<strong>${_("NOTE")}: </strong>
|
||||
${_("The generated report is limited to {max_entries} responses. If you expect more than {max_entries} "
|
||||
"responses, try generating the report on a chapter-by-chapter, or problem-by-problem basis, or contact "
|
||||
"your site administrator to increase the limit.").format(max_entries=max_entries)}
|
||||
</p>
|
||||
%endif
|
||||
</section>
|
||||
@@ -0,0 +1,109 @@
|
||||
<%page args="section_data" expression_filter="h"/>
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
|
||||
<section id="reports" class="idash-section tab-data" aria-labelledby="header-reports">
|
||||
<h6 class="mb-15 font-size-100" id="header-reports">
|
||||
<strong>${_("Note")}: </strong>
|
||||
Please select the report type and then click "Download Report" button
|
||||
</h6>
|
||||
<div class="mb-15">
|
||||
|
||||
<div class="">
|
||||
<select class="report-type selector">
|
||||
<option value="gradingConfiguration"
|
||||
data-endpoint="${ section_data['get_grading_config_url'] }">
|
||||
Grading Configuration
|
||||
</option>
|
||||
<option value="listAnonymizeStudentIDs"
|
||||
data-endpoint="${ section_data['get_anon_ids_url'] }"
|
||||
data-directdownload="true"
|
||||
class="${'is-disabled' if disable_buttons else ''}"
|
||||
aria-disabled="${'true' if disable_buttons else 'false'}">Anonymized Student IDs
|
||||
</option>
|
||||
%if settings.FEATURES.get('ENABLE_GRADE_DOWNLOADS'):
|
||||
<option value="profileInformation"
|
||||
data-endpoint="${ section_data['get_students_features_url'] + '/csv' }"
|
||||
data-csv="true">Profile Information
|
||||
</option>
|
||||
<option value="learnerWhoCanEnroll"
|
||||
data-endpoint="${ section_data['get_students_who_may_enroll_url'] }" data-csv="true">
|
||||
Learner
|
||||
who can enroll
|
||||
</option>
|
||||
<option value="listEnrolledPeople"
|
||||
data-endpoint="${ section_data['get_students_features_url'] }"
|
||||
data-datatable="true">
|
||||
List enrolled students profile information
|
||||
</option>
|
||||
%if section_data['show_generate_proctored_exam_report_button']:
|
||||
<option value="proctoredExamResults"
|
||||
data-endpoint="${ section_data['list_proctored_results_url'] }">Proctored exam results
|
||||
</option>
|
||||
%endif
|
||||
%if section_data['course_has_survey']:
|
||||
<option value="surveyResultReport"
|
||||
data-endpoint="${ section_data['course_survey_results_url'] }">
|
||||
Survey Result report
|
||||
</option>
|
||||
%endif
|
||||
%if settings.FEATURES.get('ALLOW_COURSE_STAFF_GRADE_DOWNLOADS') or section_data['access']['admin']:
|
||||
<option value="ORADataReport" data-graderelated="true"
|
||||
data-endpoint="${ section_data['export_ora2_data_url'] }">ORA Data
|
||||
report
|
||||
</option>
|
||||
<option data-graderelated="true" value="problemGradeReport"
|
||||
data-endpoint="${ section_data['problem_grade_report_url'] }">Problem Grade report
|
||||
</option>
|
||||
%endif
|
||||
%endif
|
||||
</select>
|
||||
<input type="button" value="Download Report" class="download-report ml-10">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="selectionInfo gradingConfiguration">${_("Click to display 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.")}</p>
|
||||
<p hidden="hidden" class="selectionInfo listAnonymizeStudentIDs">${_("Click to download a CSV of \
|
||||
anonymized student IDs:")}</p>
|
||||
|
||||
<p hidden="hidden" class="selectionInfo reports"> ${_("For large courses, generating some reports can take \
|
||||
several hours. When report generation is complete, a \
|
||||
link that includes the date and time of generation appears in the table below. These reports are \
|
||||
generated in the background, meaning it is OK to navigate away from this page while your report is \
|
||||
generating.")}</p>
|
||||
|
||||
<p hidden="hidden" class="selectionInfo reports">${_("Please be patient and do not click these buttons \
|
||||
multiple times. Clicking these buttons multiple times will significantly slow the generation \
|
||||
process.")}
|
||||
</p>
|
||||
% if not disable_buttons:
|
||||
<p hidden="hidden" class="selectionInfo listEnrolledPeople">${_("For smaller courses, click to list \
|
||||
profile information for enrolled students directly on this page:")}</p>
|
||||
%endif
|
||||
<p hidden="hidden" class="selectionInfo reports profileInformation">${_("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 hidden="hidden" class="selectionInfo reports learnerWhoCanEnroll">${_("Click to generate a CSV file \
|
||||
that lists learners who can enroll in the course but have not yet done so.")}</p>
|
||||
|
||||
<p hidden="hidden" class="selectionInfo reports proctoredExamResults">${_("Click to generate a CSV file \
|
||||
of all proctored exam results in this course.")}</p>
|
||||
|
||||
<p hidden="hidden" class="selectionInfo reports surveyResultReport">${_("Click to generate a CSV file of \
|
||||
survey results for this course.")}</p>
|
||||
<p hidden="hidden" class="selectionInfo reports ORADataReport">${_("Click to generate a CSV \
|
||||
ORA grade report for all currently enrolled students.")}</p>
|
||||
<p hidden="hidden" class="selectionInfo reports problemGradeReport">${_("Click to generate a CSV \
|
||||
problem grade report for all currently enrolled students.")}</p>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
Reference in New Issue
Block a user