From 356d95bf1cd0620d9d7d50a445bd91b43498f156 Mon Sep 17 00:00:00 2001 From: AfaqShuaib09 Date: Wed, 22 Nov 2023 14:06:00 +0500 Subject: [PATCH] feat: add variant ID CourseRun form --- .../EditCoursePage/CollapsibleCourseRun.jsx | 33 +- .../CollapsibleCourseRun.test.jsx | 32 +- .../EditCoursePage/EditCoursePage.test.jsx | 7 + .../CollapsibleCourseRun.test.jsx.snap | 648 ++++++++++++++++++ .../EditCoursePage.test.jsx.snap | 18 + src/components/EditCoursePage/index.jsx | 2 + src/helpText.jsx | 10 + src/utils/index.js | 8 +- src/utils/utils.test.js | 18 + 9 files changed, 772 insertions(+), 4 deletions(-) diff --git a/src/components/EditCoursePage/CollapsibleCourseRun.jsx b/src/components/EditCoursePage/CollapsibleCourseRun.jsx index 2bd206cb0..3104479e8 100644 --- a/src/components/EditCoursePage/CollapsibleCourseRun.jsx +++ b/src/components/EditCoursePage/CollapsibleCourseRun.jsx @@ -18,7 +18,7 @@ import { courseSubmitRun } from '../../data/actions/courseSubmitInfo'; import FieldLabel from '../FieldLabel'; import { courseRunIsArchived, localTimeZone, formatDate, isSafari, getDateWithDashes, - getDateWithSlashes, isNonExemptChanged, isPristine, hasMastersTrack, jsonDeepEqual, utcTimeZone, + getDateWithSlashes, isNonExemptChanged, isPristine, hasMastersTrack, jsonDeepEqual, utcTimeZone, isExternalCourse, } from '../../utils'; import Pill from '../Pill'; import RenderInputTextField from '../RenderInputTextField'; @@ -33,7 +33,7 @@ import { PUBLISHED, DATE_INPUT_PATTERN, FORMAT_DATE_MATCHER, NORMALIZE_DATE_MATCHER, REVIEWED, } from '../../data/constants'; import { - dateEditHelp, runTypeHelp, pacingEditHelp, publishDateHelp, + dateEditHelp, runTypeHelp, pacingEditHelp, publishDateHelp, courseRunVariantIdHelp, } from '../../helpText'; import RichEditor from '../RichEditor'; import ListField from '../ListField'; @@ -248,6 +248,7 @@ class CollapsibleCourseRun extends React.Component { isOpen, onToggle, courseRunTypeOptions, + courseInfo, } = this.props; const { copied, hasExternalKey } = this.state; const { administrator } = getAuthenticatedUser(); @@ -273,6 +274,8 @@ class CollapsibleCourseRun extends React.Component { const pristine = initialValues && isPristine(initialValues, currentFormValues) && isPristine(initialValues, currentFormValues, courseRun.key); + const productSource = courseInfo?.data?.product_source?.slug; + const courseType = courseInfo?.data?.course_type; return ( All fields are required for publication unless otherwise specified. + {isExternalCourse(productSource, courseType) && ( + + )} + disabled={disabled} + optional + /> + )} {/* TODO this should be refactored when paragon supports safari */} {/* text inputs for safari */} {isSafari @@ -765,6 +783,16 @@ CollapsibleCourseRun.propTypes = { initialValues: PropTypes.shape({ course_runs: PropTypes.arrayOf(PropTypes.shape({})), }).isRequired, + courseInfo: PropTypes.shape({ + data: PropTypes.shape({ + product_source: PropTypes.shape({ + slug: PropTypes.string, + name: PropTypes.string, + description: PropTypes.string, + }), + course_type: PropTypes.string, + }), + }), }; CollapsibleCourseRun.defaultProps = { @@ -774,6 +802,7 @@ CollapsibleCourseRun.defaultProps = { isSubmittingRunReview: false, }, courseRunTypeOptions: {}, + courseInfo: {}, runTypeModes: {}, editable: false, isSubmittingForReview: false, diff --git a/src/components/EditCoursePage/CollapsibleCourseRun.test.jsx b/src/components/EditCoursePage/CollapsibleCourseRun.test.jsx index 2d6d93c16..edc7956e8 100644 --- a/src/components/EditCoursePage/CollapsibleCourseRun.test.jsx +++ b/src/components/EditCoursePage/CollapsibleCourseRun.test.jsx @@ -4,7 +4,9 @@ import { shallowToJson } from 'enzyme-to-json'; import CollapsibleCourseRun from './CollapsibleCourseRun'; import { courseSubmitRun } from '../../data/actions/courseSubmitInfo'; -import { AUDIT_TRACK, MASTERS_TRACK, VERIFIED_TRACK } from '../../data/constants'; +import { + AUDIT_TRACK, EXECUTIVE_EDUCATION_SLUG, MASTERS_TRACK, VERIFIED_TRACK, +} from '../../data/constants'; import store from '../../data/store'; @@ -109,6 +111,34 @@ describe('Collapsible Course Run', () => { expect(shallowToJson(component)).toMatchSnapshot(); }); + it('renders correctly variant_id field for external course\'s course run', () => { + const courseInfo = { + data: { + product_source: { + slug: 'test-source', + name: 'Test Source', + description: 'Test Source Description', + }, + course_type: EXECUTIVE_EDUCATION_SLUG, + }, + }; + const component = shallow(); + const variantIdField = component.find('Field[name="test-course.variant_id"]'); + expect(variantIdField.exists()).toBe(true); + expect(shallowToJson(component)).toMatchSnapshot(); + }); + it('renders correctly with external key field enabled', () => { const runTypeModes = { '00000000-0000-4000-0000-000000000000': [ diff --git a/src/components/EditCoursePage/EditCoursePage.test.jsx b/src/components/EditCoursePage/EditCoursePage.test.jsx index 9903bf7cc..ae5710204 100644 --- a/src/components/EditCoursePage/EditCoursePage.test.jsx +++ b/src/components/EditCoursePage/EditCoursePage.test.jsx @@ -26,6 +26,7 @@ describe('EditCoursePage', () => { const defaultPrice = '77'; const defaultEnd = '2019-08-14T00:00:00Z'; const defaultUpgradeDeadlineOverride = '2019-09-14T00:00:00Z'; + const variantId = '00000000-0000-0000-0000-000000000000'; const watchers = ['test@test.com']; const courseInfo = { @@ -62,6 +63,7 @@ describe('EditCoursePage', () => { start: '2019-05-14T00:00:00Z', end: defaultEnd, upgrade_deadline_override: '2019-05-10T00:00:00Z', + variant_id: null, expected_program_type: 'micromasters', expected_program_name: 'Test Program Name', go_live_date: '2019-05-06T00:00:00Z', @@ -86,6 +88,7 @@ describe('EditCoursePage', () => { start: '2019-05-14T00:00:00Z', end: defaultEnd, upgrade_deadline_override: '2019-05-10T00:00:00Z', + variant_id: null, expected_program_type: null, expected_program_name: '', go_live_date: '2019-05-06T00:00:00Z', @@ -322,6 +325,7 @@ describe('EditCoursePage', () => { start: '2019-05-14T00:00:00Z', end: defaultEnd, upgrade_deadline_override: defaultUpgradeDeadlineOverride, + variant_id: variantId, expected_program_type: null, expected_program_name: '', go_live_date: '2019-05-06T00:00:00Z', @@ -469,6 +473,7 @@ describe('EditCoursePage', () => { transcript_languages: ['en-us'], weeks_to_complete: '100', upgrade_deadline_override: defaultUpgradeDeadlineOverride, + variant_id: variantId, }, { content_language: 'en-us', @@ -490,6 +495,7 @@ describe('EditCoursePage', () => { transcript_languages: ['en-us'], weeks_to_complete: '100', upgrade_deadline_override: defaultUpgradeDeadlineOverride, + variant_id: variantId, }, ]; @@ -501,6 +507,7 @@ describe('EditCoursePage', () => { courseData.course_runs[0].end = defaultEnd; courseData.course_runs[0].status = UNPUBLISHED; courseData.course_runs[0].upgrade_deadline_override = defaultUpgradeDeadlineOverride; + courseData.course_runs[0].variant_id = variantId; courseData.prices = { verified: defaultPrice, }; diff --git a/src/components/EditCoursePage/__snapshots__/CollapsibleCourseRun.test.jsx.snap b/src/components/EditCoursePage/__snapshots__/CollapsibleCourseRun.test.jsx.snap index 628097f68..0b641dea9 100644 --- a/src/components/EditCoursePage/__snapshots__/CollapsibleCourseRun.test.jsx.snap +++ b/src/components/EditCoursePage/__snapshots__/CollapsibleCourseRun.test.jsx.snap @@ -1,5 +1,653 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Collapsible Course Run renders correctly variant_id field for external course's course run 1`] = ` + + + Course run starting on Jan 01, 2000 - Self Paced + + +
+ + Publish date is Dec 31, 1999 + +
+
+ + Studio URL -  + + edX101+DemoX + + + Copy course key + + } + placement="top" + popperConfig={Object {}} + trigger={ + Array [ + "hover", + "focus", + ] + } + > + + + + + +
+ + } +> +
+ + All fields are required for publication unless otherwise specified. + +
+ +

+ The identifier for a product variant. This is used to link a course run to a product variant for external LOBs (i.e; ExecEd & Bootcamps). +

+ + } + id="test-course.variant_id.label" + optional={false} + text="Variant Id" + /> + } + name="test-course.variant_id" + optional={true} + /> +
+ +

+ Required Format: yyyy/mm/dd +

+

+ The scheduled date for when the course run will be live and published. +

+

+ To publish as soon as possible, set the publish date to today. Please note that changes may take 48 hours to go live. +

+

+ If you don’t have a publish date yet, set to 1 year in the future. +

+
+ } + id="test-course.go_live_date.label" + optional={false} + text="Publish date" + /> + } + maxLength="10" + name="test-course.go_live_date" + normalize={[Function]} + pattern="20[1-9][0-9]/(0[1-9]|1[012])/(0[1-9]|[12][0-9]|3[01])" + placeholder="yyyy/mm/dd" + required={false} + type="text" + /> + +

+ Course run dates are editable in Studio. +

+

+ Please note that changes in Studio may take up to a business day to be reflected here. For questions, contact your project coordinator. +

+

+ + Edit dates. + + . +

+ + } + name="test-course.start" + timeLabel="Start time (GMT)" + type="text" + /> + +

+ Course run dates are editable in Studio. +

+

+ Please note that changes in Studio may take up to a business day to be reflected here. For questions, contact your project coordinator. +

+

+ + Edit dates. + + . +

+ + } + name="test-course.end" + timeLabel="End time (GMT)" + type="text" + /> + +

+ Course run dates are editable in Studio. +

+

+ Please note that changes in Studio may take up to a business day to be reflected here. For questions, contact your project coordinator. +

+

+ + Edit dates. + + . +

+ + } + name="test-course.upgrade_deadline_override" + timeLabel="Upgrade deadline override time (UTC)" + type="date" + utcTimeZone={true} + /> + +
+ +

+ The enrollment track determines whether a course run offers a paid certificate and what sort of verification is required. +

+

+ + Learn more. + +

+ + } + id="test-course.run_type.label" + optional={false} + text="Course run enrollment track" + /> + } + name="test-course.run_type" + options={ + Array [ + Object { + "label": "Select enrollment track", + "value": "", + }, + Object { + "label": "Verified and Audit", + "value": "4e260c57-24ef-46c1-9a0d-5ec3a30f6b0c", + }, + Object { + "label": "Audit Only", + "value": "cfacfc62-54bd-4e1b-939a-5a94f12fbd8d", + }, + Object { + "label": "Masters, Verified, and Audit", + "value": "00000000-0000-4000-0000-000000000000", + }, + ] + } + required={true} + /> + +

+ Course pacing is editable in Studio. +

+

+ Please note that changes in Studio may take up to a business day to be reflected here. For questions, contact your project coordinator. +

+

+ + Edit course pacing. + + . +

+ + } + id="test-course.pacing_type.label" + optional={false} + text="Course pacing" + /> + } + name="test-course.pacing_type" + options={ + Array [ + Object { + "label": "Self-paced", + "value": "self_paced", + }, + ] + } + type="text" + /> + +

+ The primary instructor or instructors for the course. +

+

+ The order that instructors are listed here is the same order they will be displayed on course pages. You can drag and drop to reorder instructors. +

+ + } + id="test-course.staff.label" + optional={true} + text="Staff" + /> + +
+
+ +

+ The minimum number of hours per week the learner should expect to spend on the course. +

+
+ } + id="test-course.min_effort.label" + optional={false} + text="Minimum effort" + /> + } + name="test-course.min_effort" + required={false} + type="number" + /> +
+
+ +

+ The maximum number of hours per week the learner should expect to spend on the course. +

+
+ } + id="test-course.max_effort.label" + optional={false} + text="Maximum effort" + /> + } + name="test-course.max_effort" + required={false} + type="number" + /> + + + +

+ The estimated number of weeks the learner should expect to spend on the course, rounded to the nearest whole number. +

+ + } + id="test-course.weeks_to_complete.label" + optional={false} + text="Length" + /> + } + name="test-course.weeks_to_complete" + required={false} + type="number" + /> + + } + name="test-course.content_language" + options={ + Array [ + Object { + "label": "Arabic - United Arab Emirates", + "value": "ar-ae", + }, + ] + } + required={false} + type="text" + /> + + + +

+ If this Course Run will potentially be part of a Program, please set the expected program type here. +

+ + } + id="test-course.expected_program_type.label" + optional={true} + text="Expected Program Type" + /> + } + name="test-course.expected_program_type" + type="text" + /> + +

+ If this Course Run will potentially be part of a Program, please set the expected program name here. +

+ + } + id="test-course.expected_program_name.label" + optional={true} + text="Expected Program Name" + /> + } + name="test-course.expected_program_name" + type="text" + /> +
+ +

+ Course embargo status for OFAC is managed internally, please contact support with questions. +

+
+ } + id="ofac-notice-label" + optional={false} + text="Course Embargo (OFAC) Restriction text added to the FAQ section" + /> +
+ No +
+ + +
+`; + exports[`Collapsible Course Run renders correctly when given a published course run 1`] = ` 0 ? getUpgradeDeadlineOverride(courseRun.seats) diff --git a/src/helpText.jsx b/src/helpText.jsx index 57b28a073..26ad396b5 100644 --- a/src/helpText.jsx +++ b/src/helpText.jsx @@ -21,6 +21,15 @@ const publishDateHelp = ( ); +const courseRunVariantIdHelp = ( +
+

+ The identifier for a product variant. This is used to link a course run to a product variant for external LOBs + (i.e; ExecEd & Bootcamps). +

+
+); + function dateEditHelp(courseRun) { return (
@@ -252,5 +261,6 @@ export { getUrlSlugHelp, oldUrlSlugExample, subdirectoryUrlSlugExample, + courseRunVariantIdHelp, keyHelp, }; diff --git a/src/utils/index.js b/src/utils/index.js index 524f6a3b2..41dbe470d 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -5,10 +5,11 @@ import qs from 'query-string'; import { COURSE_EXEMPT_FIELDS, COURSE_RUN_NON_EXEMPT_FIELDS, COURSE_URL_SLUG_PATTERN_OLD, - MASTERS_TRACK, COURSE_URL_SLUG_VALIDATION_MESSAGE, + MASTERS_TRACK, COURSE_URL_SLUG_VALIDATION_MESSAGE, EXECUTIVE_EDUCATION_SLUG, BOOTCAMP_SLUG, } from '../data/constants'; import DiscoveryDataApiService from '../data/services/DiscoveryDataApiService'; import { PAGE_SIZE } from '../data/constants/table'; +import { DEFAULT_PRODUCT_SOURCE } from '../data/constants/productSourceOptions'; const getDateWithDashes = date => (date ? moment(date).format('YYYY-MM-DD') : ''); const getDateWithSlashes = date => (date ? moment(date).format('YYYY/MM/DD') : ''); @@ -217,6 +218,10 @@ const isPristine = (initialValues, currentFormValues, runKey) => { }); }; +const isExternalCourse = (productSource, courseType) => ( + productSource !== DEFAULT_PRODUCT_SOURCE && [EXECUTIVE_EDUCATION_SLUG, BOOTCAMP_SLUG].includes(courseType) +); + const parseOptions = inChoices => inChoices.map(choice => ({ label: choice.display_name, value: choice.value })); const getOptionsData = (options) => { @@ -385,6 +390,7 @@ export { isSafari, isNonExemptChanged, isPristine, + isExternalCourse, parseOptions, getOptionsData, parseCourseTypeOptions, diff --git a/src/utils/utils.test.js b/src/utils/utils.test.js index aec9d3173..3a1ddf3ac 100644 --- a/src/utils/utils.test.js +++ b/src/utils/utils.test.js @@ -152,6 +152,24 @@ describe('getCourseUrlSlugPattern', () => { ); }); +describe('isExternalCourse', () => { + const EXTERNAL_COURSE_TYPES = [EXECUTIVE_EDUCATION_SLUG, BOOTCAMP_SLUG]; + it('returns true if the product source is other than edx and course type is in EXTERNAL_COURSE_TYPES', () => { + expect(utils.isExternalCourse('external-source', EXTERNAL_COURSE_TYPES[0])).toBe(true); + expect(utils.isExternalCourse('external-source', EXTERNAL_COURSE_TYPES[1])).toBe(true); + }); + it('returns false if the product source has a default value', () => { + expect(utils.isExternalCourse(DEFAULT_PRODUCT_SOURCE, EXTERNAL_COURSE_TYPES[0])).toBe(false); + expect(utils.isExternalCourse(DEFAULT_PRODUCT_SOURCE, EXTERNAL_COURSE_TYPES[1])).toBe(false); + }); + it( + 'returns false if the product source does not have a default value and course type is not in EXTERNAL_COURSE_TYPES', + () => { + expect(utils.isExternalCourse('external-source', 'audit')).toBe(false); + }, + ); +}); + describe('getCourseError', () => { it('returns a string from a string', () => { const testError = 'Test error message';