New vs Old Data-Download UI. (#24094)
updated css temp fixex Updated js code for data download updated js hooks for new UI fixed ui and navigation reset paver file Removed unused changes Initial tests added Initial tests added fixed style issues Created new tests for data download Fixed A11y and quality issues Updated test file and removed new fixed Accesibility issues fixed code style in spec removed old data download file Moved problem grade report Updated html to fix accessiblity issue Fixed accessiblity issues Created waffle flag for data download added doc strign in doc renamed waffles file Break down Html and fixed tests Removed extra js and updated comments Removed extra js and updated comments renamed var fixed styling fixed js test fail Fixed styling issues updated description texts Updated problem selector UI Fixed Jest test for react component removed depricated default param added class instead of style updated snapshot Co-authored-by: Awais Jibran <awaisdar001@gmail.com>
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
/* global gettext */
|
||||
import { Button } from '@edx/paragon';
|
||||
import BlockBrowserContainer from 'BlockBrowser/components/BlockBrowser/BlockBrowserContainer';
|
||||
import { Button, Icon } from '@edx/paragon';
|
||||
import { BlockBrowser } from 'BlockBrowser';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import * as React from 'react';
|
||||
import { ReportStatusContainer } from '../ReportStatus/ReportStatusContainer';
|
||||
|
||||
export default class Main extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleToggleDropdown = this.handleToggleDropdown.bind(this);
|
||||
this.initiateReportGeneration = this.initiateReportGeneration.bind(this);
|
||||
this.state = {
|
||||
showDropdown: false,
|
||||
};
|
||||
@@ -24,39 +22,31 @@ export default class Main extends React.Component {
|
||||
this.setState({ showDropdown: false });
|
||||
}
|
||||
|
||||
initiateReportGeneration() {
|
||||
this.props.createProblemResponsesReportTask(
|
||||
this.props.problemResponsesEndpoint,
|
||||
this.props.taskStatusEndpoint,
|
||||
this.props.selectedBlock,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedBlock, onSelectBlock } = this.props;
|
||||
|
||||
return (
|
||||
<div className="problem-browser-container">
|
||||
<div className="problem-browser">
|
||||
<Button
|
||||
onClick={this.handleToggleDropdown}
|
||||
label={gettext('Select a section or problem')}
|
||||
/>
|
||||
<input type="text" name="problem-location" value={selectedBlock} disabled />
|
||||
{this.state.showDropdown &&
|
||||
<BlockBrowserContainer
|
||||
onSelectBlock={(blockId) => {
|
||||
this.hideDropdown();
|
||||
onSelectBlock(blockId);
|
||||
}}
|
||||
/>}
|
||||
<Button
|
||||
onClick={this.initiateReportGeneration}
|
||||
name="list-problem-responses-csv"
|
||||
label={gettext('Create a report of problem responses')}
|
||||
/>
|
||||
</div>
|
||||
<ReportStatusContainer />
|
||||
<div className="problem-browser">
|
||||
{/* 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>
|
||||
|
||||
<input type="text" name="problem-location" value={selectedBlock} disabled style={{ display: 'none' }} />
|
||||
{this.state.showDropdown &&
|
||||
<BlockBrowser onSelectBlock={(blockId) => {
|
||||
this.hideDropdown();
|
||||
onSelectBlock(blockId);
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -64,17 +54,13 @@ export default class Main extends React.Component {
|
||||
|
||||
Main.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
createProblemResponsesReportTask: PropTypes.func.isRequired,
|
||||
excludeBlockTypes: PropTypes.arrayOf(PropTypes.string),
|
||||
fetchCourseBlocks: PropTypes.func.isRequired,
|
||||
problemResponsesEndpoint: PropTypes.string.isRequired,
|
||||
onSelectBlock: PropTypes.func.isRequired,
|
||||
selectedBlock: PropTypes.string,
|
||||
taskStatusEndpoint: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
Main.defaultProps = {
|
||||
excludeBlockTypes: null,
|
||||
selectedBlock: '',
|
||||
timeout: null,
|
||||
selectedBlock: null,
|
||||
};
|
||||
|
||||
@@ -1,34 +1,25 @@
|
||||
/* global jest,test,describe,expect */
|
||||
import { Button } from '@edx/paragon';
|
||||
import BlockBrowserContainer from 'BlockBrowser/components/BlockBrowser/BlockBrowserContainer';
|
||||
import { Provider } from 'react-redux';
|
||||
import { BlockBrowser } from 'BlockBrowser';
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import store from '../../data/store';
|
||||
|
||||
import Main from './Main';
|
||||
|
||||
describe('ProblemBrowser Main component', () => {
|
||||
const courseId = 'testcourse';
|
||||
const problemResponsesEndpoint = '/api/problem_responses/';
|
||||
const taskStatusEndpoint = '/api/task_status/';
|
||||
const excludedBlockTypes = [];
|
||||
|
||||
test('render with basic parameters', () => {
|
||||
const component = renderer.create(
|
||||
<Provider store={store}>
|
||||
<Main
|
||||
courseId={courseId}
|
||||
createProblemResponsesReportTask={jest.fn()}
|
||||
excludeBlockTypes={excludedBlockTypes}
|
||||
fetchCourseBlocks={jest.fn()}
|
||||
problemResponsesEndpoint={problemResponsesEndpoint}
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={null}
|
||||
taskStatusEndpoint={taskStatusEndpoint}
|
||||
/>
|
||||
</Provider>,
|
||||
<Main
|
||||
courseId={courseId}
|
||||
excludeBlockTypes={excludedBlockTypes}
|
||||
fetchCourseBlocks={jest.fn()}
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={null}
|
||||
/>,
|
||||
);
|
||||
const tree = component.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
@@ -36,18 +27,13 @@ describe('ProblemBrowser Main component', () => {
|
||||
|
||||
test('render with selected block', () => {
|
||||
const component = renderer.create(
|
||||
<Provider store={store}>
|
||||
<Main
|
||||
courseId={courseId}
|
||||
createProblemResponsesReportTask={jest.fn()}
|
||||
excludeBlockTypes={excludedBlockTypes}
|
||||
fetchCourseBlocks={jest.fn()}
|
||||
problemResponsesEndpoint={problemResponsesEndpoint}
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={'some-selected-block'}
|
||||
taskStatusEndpoint={taskStatusEndpoint}
|
||||
/>
|
||||
</Provider>,
|
||||
<Main
|
||||
courseId={courseId}
|
||||
excludeBlockTypes={excludedBlockTypes}
|
||||
fetchCourseBlocks={jest.fn()}
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={'some-selected-block'}
|
||||
/>,
|
||||
);
|
||||
const tree = component.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
@@ -56,20 +42,15 @@ describe('ProblemBrowser Main component', () => {
|
||||
test('fetch course block on toggling dropdown', () => {
|
||||
const fetchCourseBlocksMock = jest.fn();
|
||||
const component = renderer.create(
|
||||
<Provider store={store}>
|
||||
<Main
|
||||
courseId={courseId}
|
||||
createProblemResponsesReportTask={jest.fn()}
|
||||
excludeBlockTypes={excludedBlockTypes}
|
||||
fetchCourseBlocks={fetchCourseBlocksMock}
|
||||
problemResponsesEndpoint={problemResponsesEndpoint}
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={'some-selected-block'}
|
||||
taskStatusEndpoint={taskStatusEndpoint}
|
||||
/>
|
||||
</Provider>,
|
||||
<Main
|
||||
courseId={courseId}
|
||||
excludeBlockTypes={excludedBlockTypes}
|
||||
fetchCourseBlocks={fetchCourseBlocksMock}
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={'some-selected-block'}
|
||||
/>,
|
||||
);
|
||||
const instance = component.root.children[0].instance;
|
||||
const instance = component.getInstance();
|
||||
instance.handleToggleDropdown();
|
||||
expect(fetchCourseBlocksMock.mock.calls.length).toBe(1);
|
||||
});
|
||||
@@ -78,17 +59,13 @@ describe('ProblemBrowser Main component', () => {
|
||||
const component = shallow(
|
||||
<Main
|
||||
courseId={courseId}
|
||||
createProblemResponsesReportTask={jest.fn()}
|
||||
excludeBlockTypes={excludedBlockTypes}
|
||||
fetchCourseBlocks={jest.fn()}
|
||||
problemResponsesEndpoint={problemResponsesEndpoint}
|
||||
onSelectBlock={jest.fn()}
|
||||
selectedBlock={'some-selected-block'}
|
||||
taskStatusEndpoint={taskStatusEndpoint}
|
||||
/>,
|
||||
);
|
||||
expect(component.find(BlockBrowserContainer).length).toBeFalsy();
|
||||
component.find(Button).find({ label: 'Select a section or problem' }).simulate('click');
|
||||
expect(component.find(BlockBrowserContainer).length).toBeTruthy();
|
||||
component.find('.problem-selector').simulate('click');
|
||||
expect(component.find(BlockBrowser)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,80 +2,90 @@
|
||||
|
||||
exports[`ProblemBrowser Main component render with basic parameters 1`] = `
|
||||
<div
|
||||
className="problem-browser-container"
|
||||
className="problem-browser"
|
||||
>
|
||||
<div
|
||||
className="problem-browser"
|
||||
<span
|
||||
className={
|
||||
Array [
|
||||
"problem-selector",
|
||||
]
|
||||
}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<button
|
||||
className="btn"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Select a section or problem
|
||||
</button>
|
||||
<input
|
||||
disabled={true}
|
||||
name="problem-location"
|
||||
type="text"
|
||||
value={null}
|
||||
/>
|
||||
<button
|
||||
className="btn"
|
||||
name="list-problem-responses-csv"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
</span>
|
||||
<span
|
||||
className={
|
||||
Array [
|
||||
"pull-right",
|
||||
]
|
||||
}
|
||||
>
|
||||
Create a report of problem responses
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="report-generation-status"
|
||||
<div>
|
||||
<span
|
||||
aria-hidden={true}
|
||||
className="fa fa-sort"
|
||||
id="Icon2"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<input
|
||||
disabled={true}
|
||||
name="problem-location"
|
||||
style={
|
||||
Object {
|
||||
"display": "none",
|
||||
}
|
||||
}
|
||||
type="text"
|
||||
value={null}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ProblemBrowser Main component render with selected block 1`] = `
|
||||
<div
|
||||
className="problem-browser-container"
|
||||
className="problem-browser"
|
||||
>
|
||||
<div
|
||||
className="problem-browser"
|
||||
<span
|
||||
className={
|
||||
Array [
|
||||
"problem-selector",
|
||||
]
|
||||
}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<button
|
||||
className="btn"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
<span>
|
||||
some-selected-block
|
||||
</span>
|
||||
<span
|
||||
className={
|
||||
Array [
|
||||
"pull-right",
|
||||
]
|
||||
}
|
||||
>
|
||||
Select a section or problem
|
||||
</button>
|
||||
<input
|
||||
disabled={true}
|
||||
name="problem-location"
|
||||
type="text"
|
||||
value="some-selected-block"
|
||||
/>
|
||||
<button
|
||||
className="btn"
|
||||
name="list-problem-responses-csv"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Create a report of problem responses
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="report-generation-status"
|
||||
<div>
|
||||
<span
|
||||
aria-hidden={true}
|
||||
className="fa fa-sort"
|
||||
id="Icon2"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<input
|
||||
disabled={true}
|
||||
name="problem-location"
|
||||
style={
|
||||
Object {
|
||||
"display": "none",
|
||||
}
|
||||
}
|
||||
type="text"
|
||||
value="some-selected-block"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -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, data_download_certificate;
|
||||
/* global define, DataDownload */
|
||||
|
||||
beforeEach(function() {
|
||||
loadFixtures('js/fixtures/instructor_dashboard/data_download.html');
|
||||
data_download_certificate = new window.DataDownload_Certificate($('.issued_certificates'));
|
||||
url = '/courses/PU/FSc/2014_T4/instructor/api/get_issued_certificates';
|
||||
data_download_certificate.$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');
|
||||
|
||||
data_download_certificate.$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(data_download_certificate.$certificate_display_table.html()
|
||||
.indexOf('Course ID') !== -1).toBe(true);
|
||||
expect(data_download_certificate.$certificate_display_table.html()
|
||||
.indexOf('Mode') !== -1).toBe(true);
|
||||
expect(data_download_certificate.$certificate_display_table.html()
|
||||
.indexOf('xyz_test') !== -1).toBe(true);
|
||||
expect(data_download_certificate.$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');
|
||||
});
|
||||
|
||||
data_download_certificate.$list_issued_certificate_table_btn.click();
|
||||
// Simulate a error response from the server
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
expect(data_download_certificate.$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);
|
||||
data_download_certificate.$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(data_download_certificate.$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
|
||||
data_download_certificate.$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(data_download_certificate.$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,28 +313,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.report-generation-status {
|
||||
.msg {
|
||||
display: inherit;
|
||||
|
||||
&.error {
|
||||
color: $error-color;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
a {
|
||||
margin: 0 1rem;
|
||||
|
||||
& > div {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.data-download-nav {
|
||||
@extend .instructor-nav
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// elements - general
|
||||
@@ -1506,8 +1487,33 @@
|
||||
|
||||
// view - data download
|
||||
// --------------------
|
||||
.instructor-dashboard-wrapper-2 section.idash-section#data_download {
|
||||
input {
|
||||
|
||||
|
||||
.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;
|
||||
}
|
||||
@@ -1531,8 +1537,79 @@
|
||||
}
|
||||
}
|
||||
|
||||
#react-problem-report {
|
||||
margin: $baseline 0;
|
||||
.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;
|
||||
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 {
|
||||
@@ -2970,3 +3047,41 @@ 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;
|
||||
min-width: 300px;
|
||||
max-width: 780px;
|
||||
border: 1px solid grey;
|
||||
display: block;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@@ -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,44 @@
|
||||
<%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-block-listing",
|
||||
props={
|
||||
"courseId": course.id,
|
||||
"excludeBlockTypes": ['html', 'video', 'discussion']
|
||||
}
|
||||
)}
|
||||
</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 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