diff --git a/lms/djangoapps/instructor/static/instructor/ProblemBrowser/components/Main/Main.jsx b/lms/djangoapps/instructor/static/instructor/ProblemBrowser/components/Main/Main.jsx index c9194386653a860244270807725046495eb9b875..a9c9c5c61dd93daf9f08b2da5ab73470cf160e2c 100644 --- a/lms/djangoapps/instructor/static/instructor/ProblemBrowser/components/Main/Main.jsx +++ b/lms/djangoapps/instructor/static/instructor/ProblemBrowser/components/Main/Main.jsx @@ -1,13 +1,15 @@ /* global gettext */ -import { Button, Icon } from '@edx/paragon'; -import { BlockBrowser } from 'BlockBrowser'; +import { Button } from '@edx/paragon'; +import BlockBrowserContainer from 'BlockBrowser/components/BlockBrowser/BlockBrowserContainer'; 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, }; @@ -22,31 +24,39 @@ 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"> - {/* 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 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> ); } @@ -54,13 +64,17 @@ 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: null, + selectedBlock: '', + timeout: null, }; diff --git a/lms/djangoapps/instructor/static/instructor/ProblemBrowser/components/Main/Main.test.jsx b/lms/djangoapps/instructor/static/instructor/ProblemBrowser/components/Main/Main.test.jsx index c25973868b154f3f00ddb5b95544bcf926c81fcb..85fb8c72d605c937971297c98f68b726fcd26a84 100644 --- a/lms/djangoapps/instructor/static/instructor/ProblemBrowser/components/Main/Main.test.jsx +++ b/lms/djangoapps/instructor/static/instructor/ProblemBrowser/components/Main/Main.test.jsx @@ -1,25 +1,34 @@ /* global jest,test,describe,expect */ import { Button } from '@edx/paragon'; -import { BlockBrowser } from 'BlockBrowser'; +import BlockBrowserContainer from 'BlockBrowser/components/BlockBrowser/BlockBrowserContainer'; +import { Provider } from 'react-redux'; 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( - <Main - courseId={courseId} - excludeBlockTypes={excludedBlockTypes} - fetchCourseBlocks={jest.fn()} - onSelectBlock={jest.fn()} - selectedBlock={null} - />, + <Provider store={store}> + <Main + courseId={courseId} + createProblemResponsesReportTask={jest.fn()} + excludeBlockTypes={excludedBlockTypes} + fetchCourseBlocks={jest.fn()} + problemResponsesEndpoint={problemResponsesEndpoint} + onSelectBlock={jest.fn()} + selectedBlock={null} + taskStatusEndpoint={taskStatusEndpoint} + /> + </Provider>, ); const tree = component.toJSON(); expect(tree).toMatchSnapshot(); @@ -27,13 +36,18 @@ describe('ProblemBrowser Main component', () => { test('render with selected block', () => { const component = renderer.create( - <Main - courseId={courseId} - excludeBlockTypes={excludedBlockTypes} - fetchCourseBlocks={jest.fn()} - onSelectBlock={jest.fn()} - selectedBlock={'some-selected-block'} - />, + <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>, ); const tree = component.toJSON(); expect(tree).toMatchSnapshot(); @@ -42,15 +56,20 @@ describe('ProblemBrowser Main component', () => { test('fetch course block on toggling dropdown', () => { const fetchCourseBlocksMock = jest.fn(); const component = renderer.create( - <Main - courseId={courseId} - excludeBlockTypes={excludedBlockTypes} - fetchCourseBlocks={fetchCourseBlocksMock} - onSelectBlock={jest.fn()} - selectedBlock={'some-selected-block'} - />, + <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>, ); - const instance = component.getInstance(); + const instance = component.root.children[0].instance; instance.handleToggleDropdown(); expect(fetchCourseBlocksMock.mock.calls.length).toBe(1); }); @@ -59,13 +78,17 @@ 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} />, ); - component.find('.problem-selector').simulate('click'); - expect(component.find(BlockBrowser)).toBeTruthy(); + expect(component.find(BlockBrowserContainer).length).toBeFalsy(); + component.find(Button).find({ label: 'Select a section or problem' }).simulate('click'); + expect(component.find(BlockBrowserContainer).length).toBeTruthy(); }); }); diff --git a/lms/djangoapps/instructor/static/instructor/ProblemBrowser/components/Main/__snapshots__/Main.test.jsx.snap b/lms/djangoapps/instructor/static/instructor/ProblemBrowser/components/Main/__snapshots__/Main.test.jsx.snap index c6bbea41e60aba72d1ee1da7e9863a09c31ddcd4..115d5838925978f777beca16bbfa8cb94fb00496 100644 --- a/lms/djangoapps/instructor/static/instructor/ProblemBrowser/components/Main/__snapshots__/Main.test.jsx.snap +++ b/lms/djangoapps/instructor/static/instructor/ProblemBrowser/components/Main/__snapshots__/Main.test.jsx.snap @@ -2,90 +2,80 @@ exports[`ProblemBrowser Main component render with basic parameters 1`] = ` <div - className="problem-browser" + className="problem-browser-container" > - <span - className={ - Array [ - "problem-selector", - ] - } - onClick={[Function]} + <div + className="problem-browser" > - <span> + <button + className="btn" + onBlur={[Function]} + onClick={[Function]} + onKeyDown={[Function]} + type="button" + > Select a section or problem - </span> - <span - className={ - Array [ - "pull-right", - ] - } + </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" > - <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} + Create a report of problem responses + </button> + </div> + <div + aria-live="polite" + className="report-generation-status" /> </div> `; exports[`ProblemBrowser Main component render with selected block 1`] = ` <div - className="problem-browser" + className="problem-browser-container" > - <span - className={ - Array [ - "problem-selector", - ] - } - onClick={[Function]} + <div + className="problem-browser" > - <span> - some-selected-block - </span> - <span - className={ - Array [ - "pull-right", - ] - } + <button + className="btn" + onBlur={[Function]} + onClick={[Function]} + onKeyDown={[Function]} + type="button" + > + 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" > - <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" + Create a report of problem responses + </button> + </div> + <div + aria-live="polite" + className="report-generation-status" /> </div> `; diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py index b221f8b7340e8a6e779cbb227e09045ea7f0b557..3a140a057b2b69001127df2ef14c5701081708c1 100644 --- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py +++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py @@ -26,7 +26,6 @@ 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 @@ -137,39 +136,31 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT self.assertTrue(has_instructor_tab(org_researcher, self.course)) @ddt.data( - ('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), + ('staff', False), + ('instructor', False), + ('data_researcher', True), + ('global_staff', True), ) @ddt.unpack - def test_data_download(self, access_role, can_access, waffle_status): + def test_data_download(self, access_role, can_access): """ Verify that the Data Download tab only shows up for certain roles """ - 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) + 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) @override_settings(ANALYTICS_DASHBOARD_URL='http://example.com') @override_settings(ANALYTICS_DASHBOARD_NAME='Example') diff --git a/lms/djangoapps/instructor/toggles.py b/lms/djangoapps/instructor/toggles.py deleted file mode 100644 index 3e4e58a42399f71a20eb0bf4b7a7a088cc637f38..0000000000000000000000000000000000000000 --- a/lms/djangoapps/instructor/toggles.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -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() diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index ac587233f39873b73f27683cfca4f8f37594fe0f..7b39156a8a244e82b86c6a825b467e6ca989a49d 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -65,7 +65,6 @@ 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__) @@ -601,9 +600,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': section_key, + 'section_key': 'data_download', 'section_display_name': _('Data Download'), 'access': access, 'show_generate_proctored_exam_report_button': show_proctored_report_button, diff --git a/lms/static/js/fixtures/instructor_dashboard/data_download.html b/lms/static/js/fixtures/instructor_dashboard/data_download.html index 500ba6d31a8ac54aceabcbc9e9b612cbc6ba9328..ac50e51355b6703660b28e05936f60af6b83d847 100644 --- a/lms/static/js/fixtures/instructor_dashboard/data_download.html +++ b/lms/static/js/fixtures/instructor_dashboard/data_download.html @@ -1,224 +1,9 @@ -<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> - +<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> \ No newline at end of file diff --git a/lms/static/js/instructor_dashboard/data_download_2.js b/lms/static/js/instructor_dashboard/data_download_2.js deleted file mode 100644 index 8416f090ba612d98fd3d13d4aa4a59ef452b9d4f..0000000000000000000000000000000000000000 --- a/lms/static/js/instructor_dashboard/data_download_2.js +++ /dev/null @@ -1,257 +0,0 @@ -/* 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); diff --git a/lms/static/js/instructor_dashboard/instructor_dashboard.js b/lms/static/js/instructor_dashboard/instructor_dashboard.js index b617bb8144bd5f26c146ca251a276e18e8915b53..c58e7610e98b11a8e9f8ee05694b8263e468982c 100644 --- a/lms/static/js/instructor_dashboard/instructor_dashboard.js +++ b/lms/static/js/instructor_dashboard/instructor_dashboard.js @@ -163,9 +163,6 @@ 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') diff --git a/lms/static/js/spec/instructor_dashboard/data_download_spec.js b/lms/static/js/spec/instructor_dashboard/data_download_spec.js index ad4c2bcbbf2b548a4cb9e8a6fee6b8380b49347e..1fd8234de899bf6fc80edf8d8f16427f3749598e 100644 --- a/lms/static/js/spec/instructor_dashboard/data_download_spec.js +++ b/lms/static/js/spec/instructor_dashboard/data_download_spec.js @@ -1,162 +1,70 @@ -/* global define, DataDownload */ +/* 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; -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; + 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); + }); - beforeEach(function() { - loadFixtures('js/fixtures/instructor_dashboard/data_download.html'); + 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'} + }; - 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'; - }); + dataDownloadCertificates.$list_issued_certificate_table_btn.click(); + AjaxHelpers.expectJsonRequest(requests, 'POST', url); - it('renders success message properly', function() { - dataDownload.downloadCSV($selected, errorMessage); + // 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); + }); - AjaxHelper.expectRequest(requests, 'POST', url); - AjaxHelper.respondWithJson(requests, { - status: 'Request is succeeded' - }); - expect(dataDownload.$reports_request_response.text()).toContain('Request is succeeded'); - }); + it('show error on failure callback', function() { + // Spy on AJAX requests + var requests = AjaxHelpers.requests(this); + 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('renders grading config returned by the server in case of successful request ', function() { - dataDownload.downloadCSV($selected, errorMessage); + it('error should be clear from UI on success callback', function() { + var requests = AjaxHelpers.requests(this); + dataDownloadCertificates.$list_issued_certificate_table_btn.click(); - 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 error response from the server + AjaxHelpers.respondWithError(requests); + expect(dataDownloadCertificates.$certificates_request_response_error.text()) + .toEqual('Error getting issued certificates list.'); - 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); - }); + // Simulate a success response from the server + dataDownloadCertificates.$list_issued_certificate_table_btn.click(); + AjaxHelpers.expectJsonRequest(requests, 'POST', url); - - 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(); - }); - }); - }); + expect(dataDownloadCertificates.$certificates_request_response_error.text()) + .not.toEqual('Error getting issued certificates list'); + }); + }); + }); diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js index cb93e8bb0c5716829c87482acd3127d70724b857..7c0cdb2c12666e10854634ef85d87123324bd877 100644 --- a/lms/static/lms/js/spec/main.js +++ b/lms/static/lms/js/spec/main.js @@ -757,7 +757,6 @@ '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', diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss index 8c509acaaee3b81230b000fccf4c0c0fabed9374..1a319abf9f65d9bebb22552ea470a642e48c6fa7 100644 --- a/lms/static/sass/course/instructor/_instructor_2.scss +++ b/lms/static/sass/course/instructor/_instructor_2.scss @@ -313,9 +313,28 @@ } } } - .data-download-nav { - @extend .instructor-nav + + .report-generation-status { + .msg { + display: inherit; + + &.error { + color: $error-color; + } + + > div { + display: inline-block; + } + + a { + margin: 0 1rem; + + & > div { + display: inline-block; + } + } } + } } // elements - general @@ -1487,106 +1506,6 @@ // 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; @@ -1612,6 +1531,10 @@ } } + #react-problem-report { + margin: $baseline 0; + } + .block-browser { .header { display: flex; @@ -3047,41 +2970,3 @@ 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; -} diff --git a/lms/templates/instructor/instructor_dashboard_2/data_download_2.html b/lms/templates/instructor/instructor_dashboard_2/data_download_2.html deleted file mode 100644 index fe3e12cece006e223a0c14c9c2dfdfce34840fd0..0000000000000000000000000000000000000000 --- a/lms/templates/instructor/instructor_dashboard_2/data_download_2.html +++ /dev/null @@ -1,93 +0,0 @@ -<%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 - diff --git a/lms/templates/instructor/instructor_dashboard_2/data_download_2/certificates.html b/lms/templates/instructor/instructor_dashboard_2/data_download_2/certificates.html deleted file mode 100644 index ea36196553984ff9c41333881149e1c1de24baea..0000000000000000000000000000000000000000 --- a/lms/templates/instructor/instructor_dashboard_2/data_download_2/certificates.html +++ /dev/null @@ -1,33 +0,0 @@ -<%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> diff --git a/lms/templates/instructor/instructor_dashboard_2/data_download_2/grading.html b/lms/templates/instructor/instructor_dashboard_2/data_download_2/grading.html deleted file mode 100644 index 5cd84df4a67046a52473406f6eeac4fb2cd84425..0000000000000000000000000000000000000000 --- a/lms/templates/instructor/instructor_dashboard_2/data_download_2/grading.html +++ /dev/null @@ -1,28 +0,0 @@ -<%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 diff --git a/lms/templates/instructor/instructor_dashboard_2/data_download_2/problem_report.html b/lms/templates/instructor/instructor_dashboard_2/data_download_2/problem_report.html deleted file mode 100644 index 3299f6860cc632ad2f75f13e2de03bc7204c8d1f..0000000000000000000000000000000000000000 --- a/lms/templates/instructor/instructor_dashboard_2/data_download_2/problem_report.html +++ /dev/null @@ -1,44 +0,0 @@ -<%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> diff --git a/lms/templates/instructor/instructor_dashboard_2/data_download_2/reports.html b/lms/templates/instructor/instructor_dashboard_2/data_download_2/reports.html deleted file mode 100644 index 71a3c2f45a4e8c09258302f3233f3bd7fbb5361a..0000000000000000000000000000000000000000 --- a/lms/templates/instructor/instructor_dashboard_2/data_download_2/reports.html +++ /dev/null @@ -1,109 +0,0 @@ -<%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>