diff --git a/.github/actions/unit-tests/action.yml b/.github/actions/unit-tests/action.yml index 71ca72a7da11..06f25a06f97e 100644 --- a/.github/actions/unit-tests/action.yml +++ b/.github/actions/unit-tests/action.yml @@ -27,8 +27,9 @@ runs: - name: save pytest warnings json file if: success() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: pytest-warnings-json path: | test_root/log/pytest_warnings*.json + overwrite: true diff --git a/.github/workflows/js-tests.yml b/.github/workflows/js-tests.yml index 4cf9b645564e..d5a04a58ee00 100644 --- a/.github/workflows/js-tests.yml +++ b/.github/workflows/js-tests.yml @@ -73,7 +73,7 @@ jobs: xvfb-run --auto-servernum ./scripts/all-tests.sh - name: Save Job Artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Build-Artifacts path: | @@ -81,3 +81,4 @@ jobs: test_root/log/*.png test_root/log/*.log **/TEST-*.xml + overwrite: true diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index 5ee54e43a709..48c90fabd0e8 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -70,10 +70,11 @@ jobs: ./scripts/all-tests.sh - name: Save Job Artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Build-Artifacts path: | **/reports/**/* test_root/log/**/*.log *.log + overwrite: true diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 58a96455cc97..1b0cb42fdbb4 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -77,7 +77,7 @@ jobs: run: sudo chown runner:runner -R .* - uses: actions/checkout@v2 - name: collect pytest warnings files - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: pytest-warnings-json path: test_root/log @@ -91,10 +91,11 @@ jobs: - name: save warning report if: success() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: pytest-warning-report-html path: | reports/pytest_warnings/warning_report_all.html + overwrite: true diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index bb5c36d3e9dd..e8a2c9fc1997 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -31,7 +31,7 @@ from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import BlockUsageLocator -from organizations.api import add_organization_course, ensure_organization +from organizations.api import add_organization_course, ensure_organization, get_course_organization, get_organization_institutions from organizations.exceptions import InvalidOrganizationException from rest_framework.exceptions import ValidationError @@ -1184,6 +1184,11 @@ def settings_handler(request, course_key_string): # lint-amnesty, pylint: disab 'upgrade_deadline': upgrade_deadline, 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_module.id), } + + course_org = get_course_organization(course_key) + institutions = get_organization_institutions(course_org) + settings_context.update({'possible_organization_institutions': list(institutions)}) + if is_prerequisite_courses_enabled(): courses, in_process_course_actions = get_courses_accessible_to_user(request) # exclude current course from the list of available courses diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js index 49531192787b..9b637ba92134 100644 --- a/cms/static/js/models/settings/course_details.js +++ b/cms/static/js/models/settings/course_details.js @@ -37,6 +37,7 @@ define(['backbone', 'underscore', 'gettext', 'js/models/validation_helpers', 'js learning_info: [], instructor_info: {}, self_paced: null, + course_institution: '', revision_number: '' }, diff --git a/cms/static/js/spec/views/settings/main_spec.js b/cms/static/js/spec/views/settings/main_spec.js index aa0360672cec..bb47d9eca732 100644 --- a/cms/static/js/spec/views/settings/main_spec.js +++ b/cms/static/js/spec/views/settings/main_spec.js @@ -51,6 +51,7 @@ define([ instructors: [{name: '', title: '', organization: '', image: '', bio: ''}] }, self_paced: false, + course_institution: '', revision_number: '' }, @@ -199,6 +200,19 @@ define([ ); }); + it('should save institution as part of course details', function() { + var requests = AjaxHelpers.requests(this); + var expectedJson = $.extend(true, {}, modelData, { + course_institution: '' + }); + $('#course-institution').val('').trigger('change'); + expect(this.model.get('course_institution')).toEqual(''); + this.view.saveView(); + AjaxHelpers.expectJsonRequest( + requests, 'POST', urlRoot, expectedJson + ); + }); + it('should not error if about_page_editable is False', function() { var requests = AjaxHelpers.requests(this); // if about_page_editable is false, there is no section.course_details diff --git a/cms/static/js/views/settings/main.js b/cms/static/js/views/settings/main.js index 9e795b2de71d..1acc8a9114c5 100644 --- a/cms/static/js/views/settings/main.js +++ b/cms/static/js/views/settings/main.js @@ -134,6 +134,8 @@ define(['js/views/validation', 'codemirror', 'underscore', 'jquery', 'jquery.ui' pre_requisite_courses = pre_requisite_courses.length > 0 ? pre_requisite_courses : ''; this.$el.find('#' + this.fieldToSelectorMap.pre_requisite_courses).val(pre_requisite_courses); + this.$el.find('#' + this.fieldToSelectorMap.course_institution).val(this.model.get('course_institution')); + if (this.model.get('entrance_exam_enabled') == 'true') { this.$('#' + this.fieldToSelectorMap.entrance_exam_enabled).attr('checked', this.model.get('entrance_exam_enabled')); this.$('.div-grade-requirements').show(); @@ -185,6 +187,7 @@ define(['js/views/validation', 'codemirror', 'underscore', 'jquery', 'jquery.ui' banner_image_asset_path: 'banner-image-url', video_thumbnail_image_asset_path: 'video-thumbnail-image-url', pre_requisite_courses: 'pre-requisite-course', + course_institution: 'course-institution', entrance_exam_enabled: 'entrance-exam-enabled', entrance_exam_minimum_score_pct: 'entrance-exam-minimum-score-pct', course_settings_learning_fields: 'course-settings-learning-fields', @@ -318,6 +321,7 @@ define(['js/views/validation', 'codemirror', 'underscore', 'jquery', 'jquery.ui' case 'course-version': case 'course-revision-number': case 'course-title': + case 'course-institution': case 'course-subtitle': case 'course-duration': case 'course-description': diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 2276a59847a2..ae6938ec0665 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -395,6 +395,17 @@

${_('Course Details')}

${_("Identify the course revision number here. This is used to assist with identifying what changes have been made for a course. Placeholder indicates a date format (YYYY.MM), however, we don't have to explicity tie this to a date for the number identification.")} +
  • + + + ${_("Institution that is hosting this course run")} + +
  • % endif diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index 92e4d393dbe7..fcec132f5163 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -561,6 +561,75 @@ } } + .program-survey { + box-shadow: 3px 3px 10px -3px grey; + + border: 1px solid theme-color("secondary"); + border-radius: 3px; + padding: 30px; + margin-bottom: 30px; + + .program-survey-header { + @extend %t-title4; + + @include padding-right($baseline/2); + } + + .program-details { + color: $gray; + margin-bottom: lh(); + line-height: 1.3em; + } + + .survey-details-wrapper { + margin-top: 30px; + column-count: 2; + column-width: auto; + + .survey-wrapper { + + margin-right: 50px; + + hr { + @extend .faded-hr-divider-medium; + } + + .survey-title { + @extend %t-title5; + + font-weight: 600; + @include padding-right($baseline/2); + } + + .survey-instructions { + font: -apple-system-short-subheadline !important; + color: $gray-d1; + font-weight: 600; + + @extend %t-title7; + + display: inline-block; + } + + .survey-details { + @extend %t-title7; + + color: $gray-d1; + margin-bottom: lh(); + line-height: 1.3em; + } + + .provide-feedback-btn { + font: -apple-system-short-subheadline !important; + + @extend %btn-primary-blue; + + margin-top: $baseline; + } + } + } + } + // Responsive behavior @include media-breakpoint-down(md) { padding: $baseline; diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 8b0771698a8b..55c358f862aa 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -148,6 +148,9 @@
    + <%include file='dashboard/_dashboard_program_surveys.html' + args='user=user, course_enrollments=course_enrollments'/> +
    % if display_dashboard_courses: <%include file="learner_dashboard/_dashboard_navigation_courses.html"/> diff --git a/lms/templates/dashboard/_dashboard_program_surveys.html b/lms/templates/dashboard/_dashboard_program_surveys.html new file mode 100644 index 000000000000..2997f7038293 --- /dev/null +++ b/lms/templates/dashboard/_dashboard_program_surveys.html @@ -0,0 +1,83 @@ +<%page expression_filter="h"/> +<%page args='user, course_enrollments'/> +<%! +from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from organizations.models import OrganizationInstitution +from common.djangoapps.student.models import AnonymousUserId +from custom_reg_form.models import ExtraInfo +%> + +<%namespace name='static' file='../static_content.html'/> + + +<% +show_program_survey = False +course_institution = '' + +for enrollment in course_enrollments: + course_overview = CourseOverview.get_from_id(enrollment.course_id) + if course_overview.org == "CA": + show_program_survey = True + + if course_overview.course_institution: + course_institution = course_overview.course_institution + break +%> + + +% if show_program_survey: + <% + course_institution = OrganizationInstitution.get_by_shortname(short_name=course_overview.course_institution) + + if user.is_anonymous: + anonymous_user_id = user.id + else: + anonymous_user_id = AnonymousUserId.objects.filter(user=user, course_id=None).order_by('-id') + anonymous_user_id = anonymous_user_id[0].anonymous_user_id + + platform_anonymous_user_id_string = "platform_anonymous_user_id=" + (anonymous_user_id or "") + username_string = "platform_username=" + (user.username or "") + platform_fullname = "platform_fullname=" + (user.profile.name or "") + user_email_string = "platform_email=" + (user.email or "") + org_institution_name_string = "org_institution_name=" + (course_institution.name or "") + org_institution_shortname_string = "org_institution_short_name=" + (course_institution.short_name or "") + org_institution_city_string = "org_institution_city=" + (course_institution.city or "") + org_institution_state_string = "org_institution_state=" + (course_institution.state or "") + org_institution_zipcode_string = "org_institution_zipcode=" + (course_institution.zipcode or "") + try: + demographic_zipcode = "demographic_zipcode=" + (ExtraInfo.objects.get(user_id=user.id).zipcode or "") + except: + demographic_zipcode = "demographic_zipcode=" + demographic_country = "demographic_country=" + (str(user.profile.country) or "") + program_name = "program_name=" + "Aviation Mechanic General" + %> + +
    +

    + Aviation Mechanic General +

    +
    +

    This program of courses equips learners for entry into the FAA mechanic pathway. Upon completion, they gain foundational knowledge in mathematics, aircraft drawings, weight and balance, aircraft materials, processes and tools, physics, electricity, inspection, ground operations, and FAA regulations governing maintenance technician certification and work.

    +
    + +
    +
    +

    Pre-Program Survey

    +
    +

    Answer this survey prior to taking any course content

    +

    This will help us understand your career awareness and interest, gauge where you are with FAA General maintenance knowledge and skill confidence, career plans, and demographic information.

    + + Provide Feedback +
    + +
    +

    Post-Program Survey

    +
    +

    Answer this survey after you have completed most of this program's courses

    +

    This will help us understand your completion of FAA General maintenance program, career awareness and interest, kwowledge and skill confidence, and demographic information.

    + + Provide Feedback +
    +
    +
    +% endif diff --git a/openedx/core/djangoapps/content/course_overviews/migrations/0027_auto_20240909_1523.py b/openedx/core/djangoapps/content/course_overviews/migrations/0027_auto_20240909_1523.py new file mode 100644 index 000000000000..9058b4deae71 --- /dev/null +++ b/openedx/core/djangoapps/content/course_overviews/migrations/0027_auto_20240909_1523.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.13 on 2024-09-09 15:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('course_overviews', '0026_auto_20230525_1917'), + ] + + operations = [ + migrations.AddField( + model_name='courseoverview', + name='course_institution', + field=models.TextField(null=True), + ), + migrations.AddField( + model_name='historicalcourseoverview', + name='course_institution', + field=models.TextField(null=True), + ), + ] diff --git a/openedx/core/djangoapps/content/course_overviews/models.py b/openedx/core/djangoapps/content/course_overviews/models.py index d16e3982abe0..20681b52d340 100644 --- a/openedx/core/djangoapps/content/course_overviews/models.py +++ b/openedx/core/djangoapps/content/course_overviews/models.py @@ -77,6 +77,7 @@ class Meta: display_name = TextField(null=True) display_number_with_default = TextField() display_org_with_default = TextField() + course_institution = TextField(null=True) start = DateTimeField(null=True) end = DateTimeField(null=True) @@ -204,6 +205,7 @@ def _create_or_update(cls, course): # lint-amnesty, pylint: disable=too-many-st course_overview.display_name = display_name course_overview.display_number_with_default = course.display_number_with_default course_overview.display_org_with_default = course.display_org_with_default + course_overview.course_institution = CourseDetails.fetch_about_attribute(course.id, 'course_institution') course_overview.start = start course_overview.end = end diff --git a/openedx/core/djangoapps/models/course_details.py b/openedx/core/djangoapps/models/course_details.py index 7db0b195f9b4..5da6f8966ff2 100644 --- a/openedx/core/djangoapps/models/course_details.py +++ b/openedx/core/djangoapps/models/course_details.py @@ -31,6 +31,7 @@ 'entrance_exam_id', 'entrance_exam_minimum_score_pct', 'about_sidebar_html', + 'course_institution', 'revision_number', ] @@ -71,6 +72,7 @@ def __init__(self, org, course_id, run): self.video_thumbnail_image_name = "" self.video_thumbnail_image_asset_path = "" self.pre_requisite_courses = [] # pre-requisite courses + self.course_institution = "" # course institution self.entrance_exam_enabled = "" # is entrance exam enabled self.entrance_exam_id = "" # the content location for the entrance exam self.entrance_exam_minimum_score_pct = settings.FEATURES.get(