feat: implement import page (#587)
This commit is contained in:
committed by
GitHub
parent
c47c800cfa
commit
2ea876ae4f
1
.env
1
.env
@@ -35,7 +35,6 @@ ENABLE_NEW_COURSE_OUTLINE_PAGE = false
|
||||
ENABLE_NEW_VIDEO_UPLOAD_PAGE = false
|
||||
ENABLE_NEW_GRADING_PAGE = false
|
||||
ENABLE_NEW_COURSE_TEAM_PAGE = false
|
||||
ENABLE_NEW_IMPORT_PAGE = false
|
||||
ENABLE_UNIT_PAGE = false
|
||||
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN = false
|
||||
BBB_LEARN_MORE_URL=''
|
||||
|
||||
@@ -36,7 +36,6 @@ ENABLE_NEW_COURSE_OUTLINE_PAGE = false
|
||||
ENABLE_NEW_VIDEO_UPLOAD_PAGE = false
|
||||
ENABLE_NEW_GRADING_PAGE = false
|
||||
ENABLE_NEW_COURSE_TEAM_PAGE = false
|
||||
ENABLE_NEW_IMPORT_PAGE = false
|
||||
ENABLE_UNIT_PAGE = false
|
||||
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN = false
|
||||
BBB_LEARN_MORE_URL=''
|
||||
|
||||
@@ -33,7 +33,6 @@ ENABLE_NEW_COURSE_OUTLINE_PAGE = true
|
||||
ENABLE_NEW_VIDEO_UPLOAD_PAGE = true
|
||||
ENABLE_NEW_GRADING_PAGE = true
|
||||
ENABLE_NEW_COURSE_TEAM_PAGE = true
|
||||
ENABLE_NEW_IMPORT_PAGE = true
|
||||
ENABLE_UNIT_PAGE = true
|
||||
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN = true
|
||||
BBB_LEARN_MORE_URL=''
|
||||
|
||||
@@ -16,6 +16,7 @@ import { GradingSettings } from './grading-settings';
|
||||
import CourseTeam from './course-team/CourseTeam';
|
||||
import { CourseUpdates } from './course-updates';
|
||||
import CourseExportPage from './export-page/CourseExportPage';
|
||||
import CourseImportPage from './import-page/CourseImportPage';
|
||||
|
||||
/**
|
||||
* As of this writing, these routes are mounted at a path prefixed with the following:
|
||||
@@ -100,10 +101,7 @@ const CourseAuthoringRoutes = ({ courseId }) => {
|
||||
<AdvancedSettings courseId={courseId} />
|
||||
</PageRoute>
|
||||
<PageRoute path={`${path}/import`}>
|
||||
{process.env.ENABLE_NEW_IMPORT_PAGE === 'true'
|
||||
&& (
|
||||
<Placeholder />
|
||||
)}
|
||||
<CourseImportPage courseId={courseId} />
|
||||
</PageRoute>
|
||||
<PageRoute path={`${path}/export`}>
|
||||
<CourseExportPage courseId={courseId} />
|
||||
|
||||
@@ -836,5 +836,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -837,5 +837,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -837,5 +837,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -837,5 +837,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -837,5 +837,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -837,5 +837,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -837,5 +837,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -837,5 +837,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -837,5 +837,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -837,5 +837,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -837,5 +837,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -873,5 +873,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -837,5 +837,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
@@ -837,5 +837,39 @@
|
||||
"course-authoring.course-rerun.sidebar.section-2.description": "The new course has the same course outline and content as the original course. All problems, videos, announcements, and other files are duplicated to the new course.",
|
||||
"course-authoring.course-rerun.sidebar.section-3.title": "What does not transfer from the original course?",
|
||||
"course-authoring.course-rerun.sidebar.section-3.description": "You are the only member of the new course's staff. No students are enrolled in the course, and there is no student data. There is no content in the discussion topics or wiki.",
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs"
|
||||
"course-authoring.course-rerun.sidebar.section-4.link": "Learn more about course re-runs",
|
||||
"course-authoring.import.file-section.title": "Select a .tar.gz file to replace your course content",
|
||||
"course-authoring.import.file-section.chosen-file": "File chosen: {fileName}",
|
||||
"course-authoring.import.sidebar.title1": "Why import a course?",
|
||||
"course-authoring.import.sidebar.description1": "You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.",
|
||||
"course-authoring.import.sidebar.importedContent": "What content is imported?",
|
||||
"course-authoring.import.sidebar.importedContentHeading": "The following content is imported.",
|
||||
"course-authoring.import.sidebar.content1": "Course content and structure",
|
||||
"course-authoring.import.sidebar.content2": "Course dates",
|
||||
"course-authoring.import.sidebar.content3": "Grading policy",
|
||||
"course-authoring.import.sidebar.content4": "Any group configurations",
|
||||
"course-authoring.import.sidebar.content5": "Settings on the advanced settings page, including MATLAB API keys and LTI passports",
|
||||
"course-authoring.import.sidebar.notImportedContent": "The following content is not exported.",
|
||||
"course-authoring.import.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data",
|
||||
"course-authoring.import.sidebar.content7": "The course team",
|
||||
"course-authoring.import.sidebar.warningTitle": "Warning: importing while a course is running",
|
||||
"course-authoring.import.sidebar.warningDescription": "If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students' problem scores.",
|
||||
"course-authoring.import.sidebar.learnMoreButtonTitle": "Learn more about importing a course",
|
||||
"course-authoring.import.stepper.title.uploading": "Uploading",
|
||||
"course-authoring.import.stepper.title.unpacking": "Unpacking",
|
||||
"course-authoring.import.stepper.title.verifying": "Verifying",
|
||||
"course-authoring.import.stepper.title.updating": "Updating сourse",
|
||||
"course-authoring.import.stepper.title.success": "Success",
|
||||
"course-authoring.import.stepper.description.uploading": "Transferring your file to our servers",
|
||||
"course-authoring.import.stepper.description.unpacking": "Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)",
|
||||
"course-authoring.import.stepper.description.verifying": "Reviewing semantics, syntax, and required data",
|
||||
"course-authoring.import.stepper.description.updating": "Integrating your imported content into this course. This process might take longer with larger courses.",
|
||||
"course-authoring.import.stepper.description.success": "Your imported content has now been integrated into this course",
|
||||
"course-authoring.import.stepper.button.outline": "View updated outline",
|
||||
"course-authoring.import.stepper.error.default": "Error importing course",
|
||||
"course-authoring.import.heading.title": "Course import",
|
||||
"course-authoring.import.heading.subtitle": "Tools",
|
||||
"course-authoring.import.description1": "Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.",
|
||||
"course-authoring.import.description2": "The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.",
|
||||
"course-authoring.import.description3": "The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don't make important changes to your course until the import operation has completed."
|
||||
}
|
||||
|
||||
103
src/import-page/CourseImportPage.jsx
Normal file
103
src/import-page/CourseImportPage.jsx
Normal file
@@ -0,0 +1,103 @@
|
||||
/* eslint-disable max-len */
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Container, Layout,
|
||||
} from '@edx/paragon';
|
||||
import Cookies from 'universal-cookie';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import SubHeader from '../generic/sub-header/SubHeader';
|
||||
import InternetConnectionAlert from '../generic/internet-connection-alert';
|
||||
import { RequestStatus } from '../data/constants';
|
||||
import { useModel } from '../generic/model-store';
|
||||
import {
|
||||
updateFileName, updateImportTriggered, updateSavingStatus, updateSuccessDate,
|
||||
} from './data/slice';
|
||||
import ImportStepper from './import-stepper/ImportStepper';
|
||||
import { getImportTriggered, getLoadingStatus, getSavingStatus } from './data/selectors';
|
||||
import { LAST_IMPORT_COOKIE_NAME } from './data/constants';
|
||||
import ImportSidebar from './import-sidebar/ImportSidebar';
|
||||
import FileSection from './file-section/FileSection';
|
||||
import messages from './messages';
|
||||
|
||||
const CourseImportPage = ({ intl, courseId }) => {
|
||||
const dispatch = useDispatch();
|
||||
const cookies = new Cookies();
|
||||
const courseDetails = useModel('courseDetails', courseId);
|
||||
const importTriggered = useSelector(getImportTriggered);
|
||||
const savingStatus = useSelector(getSavingStatus);
|
||||
const loadingStatus = useSelector(getLoadingStatus);
|
||||
const anyRequestFailed = savingStatus === RequestStatus.FAILED || loadingStatus === RequestStatus.FAILED;
|
||||
const anyRequestInProgress = savingStatus === RequestStatus.PENDING || loadingStatus === RequestStatus.IN_PROGRESS;
|
||||
|
||||
useEffect(() => {
|
||||
const cookieData = cookies.get(LAST_IMPORT_COOKIE_NAME);
|
||||
if (cookieData) {
|
||||
dispatch(updateSavingStatus(RequestStatus.SUCCESSFUL));
|
||||
dispatch(updateImportTriggered(true));
|
||||
dispatch(updateFileName(cookieData.fileName));
|
||||
dispatch(updateSuccessDate(cookieData.date));
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>
|
||||
{intl.formatMessage(messages.pageTitle, {
|
||||
headingTitle: intl.formatMessage(messages.headingTitle),
|
||||
courseName: courseDetails?.name,
|
||||
siteName: process.env.SITE_NAME,
|
||||
})}
|
||||
</title>
|
||||
</Helmet>
|
||||
<Container size="xl" className="m-4 import">
|
||||
<section className="setting-items mb-4">
|
||||
<Layout
|
||||
lg={[{ span: 9 }, { span: 3 }]}
|
||||
md={[{ span: 9 }, { span: 3 }]}
|
||||
sm={[{ span: 9 }, { span: 3 }]}
|
||||
xs={[{ span: 9 }, { span: 3 }]}
|
||||
xl={[{ span: 9 }, { span: 3 }]}
|
||||
>
|
||||
<Layout.Element>
|
||||
<article>
|
||||
<SubHeader
|
||||
title={intl.formatMessage(messages.headingTitle)}
|
||||
subtitle={intl.formatMessage(messages.headingSubtitle)}
|
||||
/>
|
||||
<p>{intl.formatMessage(messages.description1)}</p>
|
||||
<p>{intl.formatMessage(messages.description2)}</p>
|
||||
<p>{intl.formatMessage(messages.description3)}</p>
|
||||
<FileSection courseId={courseId} />
|
||||
{importTriggered && <ImportStepper courseId={courseId} />}
|
||||
</article>
|
||||
</Layout.Element>
|
||||
<Layout.Element>
|
||||
<ImportSidebar courseId={courseId} />
|
||||
</Layout.Element>
|
||||
</Layout>
|
||||
</section>
|
||||
</Container>
|
||||
<div className="alert-toast">
|
||||
<InternetConnectionAlert
|
||||
isFailed={anyRequestFailed}
|
||||
isQueryPending={anyRequestInProgress}
|
||||
onInternetConnectionFailed={() => null}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CourseImportPage.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
courseId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
CourseImportPage.defaultProps = {};
|
||||
|
||||
export default injectIntl(CourseImportPage);
|
||||
7
src/import-page/CourseImportPage.scss
Normal file
7
src/import-page/CourseImportPage.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
@import "./import-sidebar/ImportSidebar";
|
||||
|
||||
.import {
|
||||
.help-sidebar {
|
||||
margin-top: 7.188rem;
|
||||
}
|
||||
}
|
||||
111
src/import-page/CourseImportPage.test.jsx
Normal file
111
src/import-page/CourseImportPage.test.jsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import React from 'react';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import Cookies from 'universal-cookie';
|
||||
import initializeStore from '../store';
|
||||
import messages from './messages';
|
||||
import CourseImportPage from './CourseImportPage';
|
||||
import { getImportStatusApiUrl } from './data/api';
|
||||
import { IMPORT_STAGES } from './data/constants';
|
||||
import stepperMessages from './import-stepper/messages';
|
||||
|
||||
let store;
|
||||
let axiosMock;
|
||||
let cookies;
|
||||
const courseId = '123';
|
||||
const courseName = 'About Node JS';
|
||||
|
||||
jest.mock('../generic/model-store', () => ({
|
||||
useModel: jest.fn().mockReturnValue({
|
||||
name: courseName,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('universal-cookie', () => {
|
||||
const Cookie = {
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
};
|
||||
return jest.fn(() => Cookie);
|
||||
});
|
||||
|
||||
const RootWrapper = () => (
|
||||
<AppProvider store={store}>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<CourseImportPage intl={injectIntl} courseId={courseId} />
|
||||
</IntlProvider>
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
describe('<CourseImportPage />', () => {
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore();
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
axiosMock
|
||||
.onGet(getImportStatusApiUrl(courseId, 'testFileName.test'))
|
||||
.reply(200, { importStatus: 1, message: '' });
|
||||
cookies = new Cookies();
|
||||
cookies.get.mockReturnValue(null);
|
||||
});
|
||||
it('should render page title correctly', async () => {
|
||||
render(<RootWrapper />);
|
||||
await waitFor(() => {
|
||||
const helmet = Helmet.peek();
|
||||
expect(helmet.title).toEqual(
|
||||
`${messages.headingTitle.defaultMessage} | ${courseName} | ${process.env.SITE_NAME}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
it('should render without errors', async () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
await waitFor(() => {
|
||||
expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument();
|
||||
const importPageElement = getByText(messages.headingTitle.defaultMessage, {
|
||||
selector: 'h2.sub-header-title',
|
||||
});
|
||||
expect(importPageElement).toBeInTheDocument();
|
||||
expect(getByText(messages.description1.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.description2.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.description3.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
it('should fetch status without clicking when cookies has', async () => {
|
||||
cookies.get.mockReturnValue({ date: 1679787000, completed: false, fileName: 'testFileName.test' });
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
expect(getByText(stepperMessages.stepperUnpackingDescription.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
it('should show error', async () => {
|
||||
axiosMock
|
||||
.onGet(getImportStatusApiUrl(courseId, 'testFileName.tar.gz'))
|
||||
.reply(200, { importStatus: -IMPORT_STAGES.UPDATING, message: '' });
|
||||
cookies.get.mockReturnValue({ date: 1679787000, completed: false, fileName: 'testFileName.tar.gz' });
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
// eslint-disable-next-line no-promise-executor-return
|
||||
await new Promise((r) => setTimeout(r, 3500));
|
||||
expect(getByText(stepperMessages.defaultErrorMessage.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
it('should show success button', async () => {
|
||||
axiosMock
|
||||
.onGet(getImportStatusApiUrl(courseId, 'testFileName.tar.gz'))
|
||||
.reply(200, { importStatus: IMPORT_STAGES.SUCCESS, message: '' });
|
||||
cookies.get.mockReturnValue({ date: 1679787000, completed: false, fileName: 'testFileName.tar.gz' });
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
// eslint-disable-next-line no-promise-executor-return
|
||||
await new Promise((r) => setTimeout(r, 3500));
|
||||
expect(getByText(stepperMessages.viewOutlineButton.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
32
src/import-page/data/api.js
Normal file
32
src/import-page/data/api.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
|
||||
export const postImportCourseApiUrl = (courseId) => `${getApiBaseUrl()}/import/${courseId}`;
|
||||
export const getImportStatusApiUrl = (courseId, fileName) => `${getApiBaseUrl()}/import_status/${courseId}/${fileName}`;
|
||||
|
||||
/**
|
||||
* Start import course.
|
||||
* @param {string} courseId
|
||||
* @param {Object} fileData
|
||||
* @param {Object} requestConfig
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function startCourseImporting(courseId, fileData, requestConfig) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(postImportCourseApiUrl(courseId), { 'course-data': fileData }, { headers: { 'content-type': 'multipart/form-data' }, ...requestConfig });
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get import status.
|
||||
* @param {string} courseId
|
||||
* @param {string} fileName
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function getImportStatus(courseId, fileName) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(getImportStatusApiUrl(courseId, fileName));
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
48
src/import-page/data/api.test.jsx
Normal file
48
src/import-page/data/api.test.jsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { initializeMockApp, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
import { getImportStatus, postImportCourseApiUrl, startCourseImporting } from './api';
|
||||
|
||||
let axiosMock;
|
||||
const courseId = 'course-123';
|
||||
|
||||
describe('API Functions', () => {
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should fetch status on start importing', async () => {
|
||||
const data = { importStatus: 1 };
|
||||
axiosMock.onPost(postImportCourseApiUrl(courseId)).reply(200, data);
|
||||
|
||||
const result = await startCourseImporting(courseId);
|
||||
|
||||
expect(axiosMock.history.post[0].url).toEqual(postImportCourseApiUrl(courseId));
|
||||
expect(result).toEqual(data);
|
||||
});
|
||||
|
||||
it('should fetch on get import status', async () => {
|
||||
const data = { importStatus: 2 };
|
||||
const fileName = 'testFileName.test';
|
||||
const queryUrl = new URL(`import_status/${courseId}/${fileName}`, getConfig().STUDIO_BASE_URL).href;
|
||||
axiosMock.onGet(queryUrl).reply(200, data);
|
||||
|
||||
const result = await getImportStatus(courseId, fileName);
|
||||
|
||||
expect(axiosMock.history.get[0].url).toEqual(queryUrl);
|
||||
expect(result).toEqual(data);
|
||||
});
|
||||
});
|
||||
8
src/import-page/data/constants.js
Normal file
8
src/import-page/data/constants.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export const LAST_IMPORT_COOKIE_NAME = 'lastimport';
|
||||
export const IMPORT_STAGES = {
|
||||
UPLOADING: 0,
|
||||
UNPACKING: 1,
|
||||
VERIFYING: 2,
|
||||
UPDATING: 3,
|
||||
SUCCESS: 4,
|
||||
};
|
||||
8
src/import-page/data/selectors.js
Normal file
8
src/import-page/data/selectors.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export const getProgress = (state) => state.courseImport.progress;
|
||||
export const getCurrentStage = (state) => state.courseImport.currentStage;
|
||||
export const getImportTriggered = (state) => state.courseImport.importTriggered;
|
||||
export const getFileName = (state) => state.courseImport.fileName;
|
||||
export const getError = (state) => state.courseImport.error;
|
||||
export const getLoadingStatus = (state) => state.courseImport.loadingStatus;
|
||||
export const getSavingStatus = (state) => state.courseImport.savingStatus;
|
||||
export const getSuccessDate = (state) => state.courseImport.successDate;
|
||||
63
src/import-page/data/slice.js
Normal file
63
src/import-page/data/slice.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
currentStage: 0,
|
||||
error: { hasError: false, message: '' },
|
||||
progress: 0,
|
||||
importTriggered: false,
|
||||
fileName: null,
|
||||
loadingStatus: '',
|
||||
savingStatus: '',
|
||||
successDate: null,
|
||||
};
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'importPage',
|
||||
initialState,
|
||||
reducers: {
|
||||
updateCurrentStage: (state, { payload }) => {
|
||||
if (payload >= state.currentStage) {
|
||||
state.currentStage = payload;
|
||||
}
|
||||
},
|
||||
updateError: (state, { payload }) => {
|
||||
state.error = { ...state.error, ...payload };
|
||||
},
|
||||
updateProgress: (state, { payload }) => {
|
||||
state.progress = payload;
|
||||
},
|
||||
updateImportTriggered: (state, { payload }) => {
|
||||
state.importTriggered = payload;
|
||||
},
|
||||
updateFileName: (state, { payload }) => {
|
||||
state.fileName = payload;
|
||||
},
|
||||
reset: () => initialState,
|
||||
updateLoadingStatus: (state, { payload }) => {
|
||||
state.loadingStatus = payload;
|
||||
},
|
||||
updateSavingStatus: (state, { payload }) => {
|
||||
state.savingStatus = payload;
|
||||
},
|
||||
updateSuccessDate: (state, { payload }) => {
|
||||
state.successDate = payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
updateCurrentStage,
|
||||
updateError,
|
||||
updateProgress,
|
||||
updateImportTriggered,
|
||||
updateFileName,
|
||||
reset,
|
||||
updateLoadingStatus,
|
||||
updateSavingStatus,
|
||||
updateSuccessDate,
|
||||
} = slice.actions;
|
||||
|
||||
export const {
|
||||
reducer,
|
||||
} = slice;
|
||||
60
src/import-page/data/thunks.js
Normal file
60
src/import-page/data/thunks.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
import Cookies from 'universal-cookie';
|
||||
import moment from 'moment';
|
||||
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import { setImportCookie } from '../utils';
|
||||
import { getImportStatus, startCourseImporting } from './api';
|
||||
import {
|
||||
reset, updateCurrentStage, updateError, updateFileName,
|
||||
updateImportTriggered, updateLoadingStatus, updateSavingStatus, updateSuccessDate,
|
||||
} from './slice';
|
||||
import { IMPORT_STAGES, LAST_IMPORT_COOKIE_NAME } from './constants';
|
||||
|
||||
export function fetchImportStatus(courseId, fileName) {
|
||||
return async (dispatch) => {
|
||||
try {
|
||||
dispatch(updateLoadingStatus(RequestStatus.IN_PROGRESS));
|
||||
const { importStatus, message } = await getImportStatus(courseId, fileName);
|
||||
dispatch(updateCurrentStage(Math.abs(importStatus)));
|
||||
const cookies = new Cookies();
|
||||
const cookieData = cookies.get(LAST_IMPORT_COOKIE_NAME);
|
||||
|
||||
if (importStatus < 0) {
|
||||
dispatch(updateError({ hasError: true, message }));
|
||||
} else if (importStatus === IMPORT_STAGES.SUCCESS && !cookieData?.completed) {
|
||||
dispatch(updateSuccessDate(moment().valueOf()));
|
||||
}
|
||||
|
||||
if (!cookieData?.completed) {
|
||||
setImportCookie(moment().valueOf(), importStatus === IMPORT_STAGES.SUCCESS, fileName);
|
||||
}
|
||||
dispatch(updateLoadingStatus(RequestStatus.SUCCESSFUL));
|
||||
return true;
|
||||
} catch (error) {
|
||||
dispatch(updateLoadingStatus(RequestStatus.FAILED));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function handleProcessUpload(courseId, fileData, requestConfig, handleError) {
|
||||
return async (dispatch) => {
|
||||
try {
|
||||
const file = fileData.get('file');
|
||||
dispatch(reset());
|
||||
dispatch(updateSavingStatus(RequestStatus.PENDING));
|
||||
dispatch(updateImportTriggered(true));
|
||||
dispatch(updateFileName(file.name));
|
||||
const { importStatus } = await startCourseImporting(courseId, file, requestConfig);
|
||||
dispatch(updateCurrentStage(importStatus));
|
||||
setImportCookie(moment().valueOf(), importStatus === IMPORT_STAGES.SUCCESS, file.name);
|
||||
dispatch(updateSavingStatus(RequestStatus.SUCCESSFUL));
|
||||
return true;
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
dispatch(updateSavingStatus(RequestStatus.FAILED));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
64
src/import-page/file-section/FileSection.jsx
Normal file
64
src/import-page/file-section/FileSection.jsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
injectIntl,
|
||||
intlShape,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Card, Dropzone } from '@edx/paragon';
|
||||
|
||||
import { IMPORT_STAGES } from '../data/constants';
|
||||
import {
|
||||
getCurrentStage, getError, getFileName, getImportTriggered,
|
||||
} from '../data/selectors';
|
||||
import { updateProgress } from '../data/slice';
|
||||
import messages from './messages';
|
||||
import { handleProcessUpload } from '../data/thunks';
|
||||
|
||||
const FileSection = ({ intl, courseId }) => {
|
||||
const dispatch = useDispatch();
|
||||
const importTriggered = useSelector(getImportTriggered);
|
||||
const currentStage = useSelector(getCurrentStage);
|
||||
const fileName = useSelector(getFileName);
|
||||
const { hasError } = useSelector(getError);
|
||||
const isShowedDropzone = !importTriggered || currentStage === IMPORT_STAGES.SUCCESS || hasError;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Card.Header
|
||||
className="h3 px-3 text-black"
|
||||
title={intl.formatMessage(messages.headingTitle)}
|
||||
/>
|
||||
<Card.Section className="px-3 py-1">
|
||||
{isShowedDropzone
|
||||
&& (
|
||||
<Dropzone
|
||||
onProcessUpload={
|
||||
({ fileData, requestConfig, handleError }) => dispatch(handleProcessUpload(
|
||||
courseId,
|
||||
fileData,
|
||||
requestConfig,
|
||||
handleError,
|
||||
))
|
||||
}
|
||||
onUploadProgress={(percent) => dispatch(updateProgress(percent))}
|
||||
accept={{ 'application/gzip': ['.tar.gz'] }}
|
||||
data-testid="dropzone"
|
||||
/>
|
||||
)}
|
||||
</Card.Section>
|
||||
{fileName && (
|
||||
<div className="px-3 py-1">
|
||||
{intl.formatMessage(messages.fileChosen, { fileName })}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
FileSection.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
courseId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(FileSection);
|
||||
61
src/import-page/file-section/FileSection.test.jsx
Normal file
61
src/import-page/file-section/FileSection.test.jsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { fireEvent, render, waitFor } from '@testing-library/react';
|
||||
|
||||
import initializeStore from '../../store';
|
||||
import messages from './messages';
|
||||
import FileSection from './FileSection';
|
||||
|
||||
let store;
|
||||
const courseId = '123';
|
||||
|
||||
const RootWrapper = () => (
|
||||
<AppProvider store={store}>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<FileSection intl={injectIntl} courseId={courseId} />
|
||||
</IntlProvider>
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
describe('<FileSection />', () => {
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore();
|
||||
});
|
||||
it('should render without errors', async () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
await waitFor(() => {
|
||||
expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
it('should displays Dropzone when import is not triggered or in success stage or has an error', async () => {
|
||||
const { getByTestId } = render(<RootWrapper />);
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('dropzone')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
it('should work Dropzone', async () => {
|
||||
const {
|
||||
getByText, getByTestId, queryByTestId, container,
|
||||
} = render(<RootWrapper />);
|
||||
|
||||
const dropzoneElement = getByTestId('dropzone');
|
||||
|
||||
const file = new File(['file contents'], 'example.tar.gz', { type: 'application/gzip' });
|
||||
fireEvent.drop(dropzoneElement, { dataTransfer: { files: [file], types: ['Files'] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('File chosen: example.tar.gz')).toBeInTheDocument();
|
||||
expect(queryByTestId(container, 'dropzone')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
14
src/import-page/file-section/messages.js
Normal file
14
src/import-page/file-section/messages.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
headingTitle: {
|
||||
id: 'course-authoring.import.file-section.title',
|
||||
defaultMessage: 'Select a .tar.gz file to replace your course content',
|
||||
},
|
||||
fileChosen: {
|
||||
id: 'course-authoring.import.file-section.chosen-file',
|
||||
defaultMessage: 'File chosen: {fileName}',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
57
src/import-page/import-sidebar/ImportSidebar.jsx
Normal file
57
src/import-page/import-sidebar/ImportSidebar.jsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
injectIntl,
|
||||
intlShape,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from '@edx/paragon';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import { HelpSidebar } from '../../generic/help-sidebar';
|
||||
import { useHelpUrls } from '../../help-urls/hooks';
|
||||
import messages from './messages';
|
||||
|
||||
const ImportSidebar = ({ intl, courseId }) => {
|
||||
const { importCourse: importLearnMoreUrl } = useHelpUrls(['importCourse']);
|
||||
return (
|
||||
<HelpSidebar courseId={courseId}>
|
||||
<h4 className="help-sidebar-about-title">{intl.formatMessage(messages.title1)}</h4>
|
||||
<p className="help-sidebar-about-descriptions">
|
||||
{intl.formatMessage(messages.description1, { studioShortName: getConfig().STUDIO_SHORT_NAME })}
|
||||
</p>
|
||||
<hr />
|
||||
<h4 className="help-sidebar-about-title">{intl.formatMessage(messages.importedContent)}</h4>
|
||||
<p className="help-sidebar-about-descriptions">{intl.formatMessage(messages.importedContentHeading)}</p>
|
||||
<ul className="import-sidebar-list">
|
||||
<li className="help-sidebar-about-descriptions">{intl.formatMessage(messages.content1)}</li>
|
||||
<li className="help-sidebar-about-descriptions">{intl.formatMessage(messages.content2)}</li>
|
||||
<li className="help-sidebar-about-descriptions">{intl.formatMessage(messages.content3)}</li>
|
||||
<li className="help-sidebar-about-descriptions">{intl.formatMessage(messages.content4)}</li>
|
||||
<li className="help-sidebar-about-descriptions">{intl.formatMessage(messages.content5)}</li>
|
||||
</ul>
|
||||
<p className="help-sidebar-about-descriptions">{intl.formatMessage(messages.notImportedContent)}</p>
|
||||
<ul className="import-sidebar-list">
|
||||
<li className="help-sidebar-about-descriptions">{intl.formatMessage(messages.content6)}</li>
|
||||
<li className="help-sidebar-about-descriptions">{intl.formatMessage(messages.content7)}</li>
|
||||
</ul>
|
||||
<hr />
|
||||
<h4 className="help-sidebar-about-title">{intl.formatMessage(messages.warningTitle)}</h4>
|
||||
<p className="help-sidebar-about-descriptions">{intl.formatMessage(messages.warningDescription)}</p>
|
||||
<hr />
|
||||
<Button
|
||||
href={importLearnMoreUrl}
|
||||
target="_blank"
|
||||
variant="outline-primary"
|
||||
>
|
||||
{intl.formatMessage(messages.learnMoreButtonTitle)}
|
||||
</Button>
|
||||
</HelpSidebar>
|
||||
);
|
||||
};
|
||||
|
||||
ImportSidebar.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
courseId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(ImportSidebar);
|
||||
4
src/import-page/import-sidebar/ImportSidebar.scss
Normal file
4
src/import-page/import-sidebar/ImportSidebar.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
.import-sidebar-list {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
39
src/import-page/import-sidebar/ImportSidebar.test.jsx
Normal file
39
src/import-page/import-sidebar/ImportSidebar.test.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
|
||||
import initializeStore from '../../store';
|
||||
import messages from './messages';
|
||||
import ImportSidebar from './ImportSidebar';
|
||||
|
||||
const courseId = 'course-123';
|
||||
let store;
|
||||
|
||||
const RootWrapper = () => (
|
||||
<AppProvider store={store}>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<ImportSidebar intl={{ formatMessage: jest.fn() }} courseId={courseId} />
|
||||
</IntlProvider>
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
describe('<ImportSidebar />', () => {
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore();
|
||||
});
|
||||
it('render sidebar correctly', () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
expect(getByText(messages.title1.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.importedContentHeading.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
66
src/import-page/import-sidebar/messages.js
Normal file
66
src/import-page/import-sidebar/messages.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
title1: {
|
||||
id: 'course-authoring.import.sidebar.title1',
|
||||
defaultMessage: 'Why import a course?',
|
||||
},
|
||||
description1: {
|
||||
id: 'course-authoring.import.sidebar.description1',
|
||||
defaultMessage: 'You may want to run a new version of an existing course, or replace an existing course altogether. Or, you may have developed a course outside {studioShortName}.',
|
||||
},
|
||||
importedContent: {
|
||||
id: 'course-authoring.import.sidebar.importedContent',
|
||||
defaultMessage: 'What content is imported?',
|
||||
},
|
||||
importedContentHeading: {
|
||||
id: 'course-authoring.import.sidebar.importedContentHeading',
|
||||
defaultMessage: 'The following content is imported.',
|
||||
},
|
||||
content1: {
|
||||
id: 'course-authoring.import.sidebar.content1',
|
||||
defaultMessage: 'Course content and structure',
|
||||
},
|
||||
content2: {
|
||||
id: 'course-authoring.import.sidebar.content2',
|
||||
defaultMessage: 'Course dates',
|
||||
},
|
||||
content3: {
|
||||
id: 'course-authoring.import.sidebar.content3',
|
||||
defaultMessage: 'Grading policy',
|
||||
},
|
||||
content4: {
|
||||
id: 'course-authoring.import.sidebar.content4',
|
||||
defaultMessage: 'Any group configurations',
|
||||
},
|
||||
content5: {
|
||||
id: 'course-authoring.import.sidebar.content5',
|
||||
defaultMessage: 'Settings on the advanced settings page, including MATLAB API keys and LTI passports',
|
||||
},
|
||||
notImportedContent: {
|
||||
id: 'course-authoring.import.sidebar.notImportedContent',
|
||||
defaultMessage: 'The following content is not exported.',
|
||||
},
|
||||
content6: {
|
||||
id: 'course-authoring.import.sidebar.content6',
|
||||
defaultMessage: 'Learner-specific content, such as learner grades and discussion forum data',
|
||||
},
|
||||
content7: {
|
||||
id: 'course-authoring.import.sidebar.content7',
|
||||
defaultMessage: 'The course team',
|
||||
},
|
||||
warningTitle: {
|
||||
id: 'course-authoring.import.sidebar.warningTitle',
|
||||
defaultMessage: 'Warning: importing while a course is running',
|
||||
},
|
||||
warningDescription: {
|
||||
id: 'course-authoring.import.sidebar.warningDescription',
|
||||
defaultMessage: 'If you perform an import while your course is running, and you change the URL names (or url_name nodes) of any problem components, the student data associated with those problem components may be lost. This data includes students\' problem scores.',
|
||||
},
|
||||
learnMoreButtonTitle: {
|
||||
id: 'course-authoring.import.sidebar.learnMoreButtonTitle',
|
||||
defaultMessage: 'Learn more about importing a course',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
103
src/import-page/import-stepper/ImportStepper.jsx
Normal file
103
src/import-page/import-stepper/ImportStepper.jsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
injectIntl,
|
||||
intlShape,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Button } from '@edx/paragon';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import { getFormattedSuccessDate } from '../../export-page/utils';
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import CourseStepper from '../../generic/course-stepper';
|
||||
import { IMPORT_STAGES } from '../data/constants';
|
||||
import { fetchImportStatus } from '../data/thunks';
|
||||
import {
|
||||
getCurrentStage, getError, getFileName, getLoadingStatus, getProgress, getSavingStatus, getSuccessDate,
|
||||
} from '../data/selectors';
|
||||
import messages from './messages';
|
||||
|
||||
const ImportStepper = ({ intl, courseId }) => {
|
||||
const currentStage = useSelector(getCurrentStage);
|
||||
const fileName = useSelector(getFileName);
|
||||
const { hasError, message: errorMessage } = useSelector(getError);
|
||||
const progress = useSelector(getProgress);
|
||||
const dispatch = useDispatch();
|
||||
const loadingStatus = useSelector(getLoadingStatus);
|
||||
const savingStatus = useSelector(getSavingStatus);
|
||||
const successDate = useSelector(getSuccessDate);
|
||||
const isStopFetching = currentStage === IMPORT_STAGES.SUCCESS
|
||||
|| loadingStatus === RequestStatus.FAILED
|
||||
|| savingStatus === RequestStatus.FAILED
|
||||
|| hasError;
|
||||
const formattedErrorMessage = hasError ? errorMessage || intl.formatMessage(messages.defaultErrorMessage) : '';
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => {
|
||||
if (isStopFetching) {
|
||||
clearInterval(id);
|
||||
} else if (fileName) {
|
||||
dispatch(fetchImportStatus(courseId, fileName));
|
||||
}
|
||||
}, 3000);
|
||||
return () => clearInterval(id);
|
||||
});
|
||||
|
||||
let successTitle = intl.formatMessage(messages.stepperSuccessTitle);
|
||||
const formattedSuccessDate = getFormattedSuccessDate(successDate);
|
||||
if (currentStage === IMPORT_STAGES.SUCCESS && formattedSuccessDate) {
|
||||
successTitle += formattedSuccessDate;
|
||||
}
|
||||
|
||||
const handleRedirectCourseOutline = () => window.location.replace(`${getConfig().STUDIO_BASE_URL}/course/${courseId}`);
|
||||
|
||||
const steps = [
|
||||
{
|
||||
title: intl.formatMessage(messages.stepperUploadingTitle),
|
||||
description: intl.formatMessage(messages.stepperUploadingDescription),
|
||||
key: IMPORT_STAGES.UPLOADING,
|
||||
}, {
|
||||
title: intl.formatMessage(messages.stepperUnpackingTitle),
|
||||
description: intl.formatMessage(messages.stepperUnpackingDescription),
|
||||
key: IMPORT_STAGES.UNPACKING,
|
||||
}, {
|
||||
title: intl.formatMessage(messages.stepperVerifyingTitle),
|
||||
description: intl.formatMessage(messages.stepperVerifyingDescription),
|
||||
key: IMPORT_STAGES.VERIFYING,
|
||||
}, {
|
||||
title: intl.formatMessage(messages.stepperUpdatingTitle),
|
||||
description: intl.formatMessage(messages.stepperUpdatingDescription),
|
||||
key: IMPORT_STAGES.UPDATING,
|
||||
}, {
|
||||
title: successTitle,
|
||||
description: intl.formatMessage(messages.stepperSuccessDescription),
|
||||
key: IMPORT_STAGES.SUCCESS,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h3 className="mt-4">{intl.formatMessage(messages.stepperHeaderTitle)}</h3>
|
||||
<hr />
|
||||
<CourseStepper
|
||||
courseId={courseId}
|
||||
percent={progress}
|
||||
steps={steps}
|
||||
activeKey={currentStage}
|
||||
hasError={hasError}
|
||||
errorMessage={formattedErrorMessage}
|
||||
/>
|
||||
{currentStage === IMPORT_STAGES.SUCCESS && (
|
||||
<Button onClick={handleRedirectCourseOutline}>{intl.formatMessage(messages.viewOutlineButton)}</Button>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
ImportStepper.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
courseId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(ImportStepper);
|
||||
38
src/import-page/import-stepper/ImportStepper.test.jsx
Normal file
38
src/import-page/import-stepper/ImportStepper.test.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
|
||||
import initializeStore from '../../store';
|
||||
import messages from './messages';
|
||||
import ImportStepper from './ImportStepper';
|
||||
|
||||
const courseId = 'course-123';
|
||||
let store;
|
||||
|
||||
const RootWrapper = () => (
|
||||
<AppProvider store={store}>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<ImportStepper intl={{ formatMessage: jest.fn() }} courseId={courseId} />
|
||||
</IntlProvider>
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
describe('<ImportStepper />', () => {
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore();
|
||||
});
|
||||
it('render stepper correctly', () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
expect(getByText(messages.stepperHeaderTitle.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
58
src/import-page/import-stepper/messages.js
Normal file
58
src/import-page/import-stepper/messages.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
stepperUploadingTitle: {
|
||||
id: 'course-authoring.import.stepper.title.uploading',
|
||||
defaultMessage: 'Uploading',
|
||||
},
|
||||
stepperUnpackingTitle: {
|
||||
id: 'course-authoring.import.stepper.title.unpacking',
|
||||
defaultMessage: 'Unpacking',
|
||||
},
|
||||
stepperVerifyingTitle: {
|
||||
id: 'course-authoring.import.stepper.title.verifying',
|
||||
defaultMessage: 'Verifying',
|
||||
},
|
||||
stepperUpdatingTitle: {
|
||||
id: 'course-authoring.import.stepper.title.updating',
|
||||
defaultMessage: 'Updating сourse',
|
||||
},
|
||||
stepperSuccessTitle: {
|
||||
id: 'course-authoring.import.stepper.title.success',
|
||||
defaultMessage: 'Success',
|
||||
},
|
||||
stepperUploadingDescription: {
|
||||
id: 'course-authoring.import.stepper.description.uploading',
|
||||
defaultMessage: 'Transferring your file to our servers',
|
||||
},
|
||||
stepperUnpackingDescription: {
|
||||
id: 'course-authoring.import.stepper.description.unpacking',
|
||||
defaultMessage: 'Expanding and preparing folder/file structure (You can now leave this page safely, but avoid making drastic changes to content until this import is complete)',
|
||||
},
|
||||
stepperVerifyingDescription: {
|
||||
id: 'course-authoring.import.stepper.description.verifying',
|
||||
defaultMessage: 'Reviewing semantics, syntax, and required data',
|
||||
},
|
||||
stepperUpdatingDescription: {
|
||||
id: 'course-authoring.import.stepper.description.updating',
|
||||
defaultMessage: 'Integrating your imported content into this course. This process might take longer with larger courses.',
|
||||
},
|
||||
stepperSuccessDescription: {
|
||||
id: 'course-authoring.import.stepper.description.success',
|
||||
defaultMessage: 'Your imported content has now been integrated into this course',
|
||||
},
|
||||
viewOutlineButton: {
|
||||
id: 'course-authoring.import.stepper.button.outline',
|
||||
defaultMessage: 'View updated outline',
|
||||
},
|
||||
defaultErrorMessage: {
|
||||
id: 'course-authoring.import.stepper.error.default',
|
||||
defaultMessage: 'Error importing course',
|
||||
},
|
||||
stepperHeaderTitle: {
|
||||
id: 'course-authoring.export.stepper.header.title',
|
||||
defaultMessage: 'Course import status',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
30
src/import-page/messages.js
Normal file
30
src/import-page/messages.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
pageTitle: {
|
||||
id: 'course-authoring.import.page.title',
|
||||
defaultMessage: '{headingTitle} | {courseName} | {siteName}',
|
||||
},
|
||||
headingTitle: {
|
||||
id: 'course-authoring.import.heading.title',
|
||||
defaultMessage: 'Course import',
|
||||
},
|
||||
headingSubtitle: {
|
||||
id: 'course-authoring.import.heading.subtitle',
|
||||
defaultMessage: 'Tools',
|
||||
},
|
||||
description1: {
|
||||
id: 'course-authoring.import.description1',
|
||||
defaultMessage: 'Be sure you want to import a course before continuing. The contents of the imported course will replace the contents of the existing course. You cannot undo a course import. Before you proceed, we recommend that you export the current course, so that you have a backup copy of it.',
|
||||
},
|
||||
description2: {
|
||||
id: 'course-authoring.import.description2',
|
||||
defaultMessage: 'The course that you import must be in a .tar.gz file (that is, a .tar file compressed with GNU Zip). This .tar.gz file must contain a course.xml file. It may also contain other files.',
|
||||
},
|
||||
description3: {
|
||||
id: 'course-authoring.import.description3',
|
||||
defaultMessage: 'The import process has five stages. During the first two stages, you must stay on this page. You can leave this page after the unpacking stage has completed. We recommend, however, that you don\'t make important changes to your course until the import operation has completed.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
17
src/import-page/utils.js
Normal file
17
src/import-page/utils.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import Cookies from 'universal-cookie';
|
||||
|
||||
import { LAST_IMPORT_COOKIE_NAME } from './data/constants';
|
||||
|
||||
/**
|
||||
* Sets an import-related cookie with the provided information.
|
||||
*
|
||||
* @param {Date} date - Date of import.
|
||||
* @param {boolean} completed - Indicates if import was completed successfully.
|
||||
* @param {string} fileName - File name.
|
||||
* @returns {void}
|
||||
*/
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const setImportCookie = (date, completed, fileName) => {
|
||||
const cookies = new Cookies();
|
||||
cookies.set(LAST_IMPORT_COOKIE_NAME, { date, completed, fileName }, { path: window.location.pathname });
|
||||
};
|
||||
29
src/import-page/utils.test.js
Normal file
29
src/import-page/utils.test.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import Cookies from 'universal-cookie';
|
||||
|
||||
import { LAST_IMPORT_COOKIE_NAME } from './data/constants';
|
||||
import { setImportCookie } from './utils';
|
||||
|
||||
global.window = Object.create(window);
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
pathname: '/some-path',
|
||||
},
|
||||
});
|
||||
|
||||
describe('setImportCookie', () => {
|
||||
it('should set the import cookie with the provided data', () => {
|
||||
const cookiesSetMock = jest.spyOn(Cookies.prototype, 'set');
|
||||
const date = '2023-07-24';
|
||||
const completed = true;
|
||||
const fileName = 'testFileName.test';
|
||||
setImportCookie(date, completed, fileName);
|
||||
|
||||
expect(cookiesSetMock).toHaveBeenCalledWith(
|
||||
LAST_IMPORT_COOKIE_NAME,
|
||||
{ date, completed, fileName },
|
||||
{ path: '/some-path' },
|
||||
);
|
||||
|
||||
cookiesSetMock.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -19,3 +19,4 @@
|
||||
@import "course-team/CourseTeam";
|
||||
@import "course-updates/CourseUpdates";
|
||||
@import "export-page/CourseExportPage";
|
||||
@import "import-page/CourseImportPage";
|
||||
|
||||
@@ -17,6 +17,7 @@ import { reducer as processingNotificationReducer } from './generic/processing-n
|
||||
import { reducer as helpUrlsReducer } from './help-urls/data/slice';
|
||||
import { reducer as courseExportReducer } from './export-page/data/slice';
|
||||
import { reducer as genericReducer } from './generic/data/slice';
|
||||
import { reducer as courseImportReducer } from './import-page/data/slice';
|
||||
|
||||
export default function initializeStore(preloadedState = undefined) {
|
||||
return configureStore({
|
||||
@@ -38,6 +39,7 @@ export default function initializeStore(preloadedState = undefined) {
|
||||
helpUrls: helpUrlsReducer,
|
||||
courseExport: courseExportReducer,
|
||||
generic: genericReducer,
|
||||
courseImport: courseImportReducer,
|
||||
},
|
||||
preloadedState,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user