8000 classroom modal changes by mrfinch · Pull Request #7799 · codecombat/codecombat · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

classroom modal changes #7799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Nov 12, 2024
Merged

classroom modal changes #7799

merged 13 commits into from
Nov 12, 2024

Conversation

mrfinch
Copy link
Contributor
@mrfinch mrfinch commented Oct 30, 2024
  • add AI HS
  • move texts to tooltip to reduce scroll of modal
  • disable format and language for AI HS
  • add saving indicator
    fixes ENG-1349
Screenshot 2024-10-30 at 12 54 33 PM

Summary by CodeRabbit

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new getter for aggregating all classrooms in the teacher dashboard.
    • Added new components for managing class start/end dates and course code/language formats.
  • Improvements

    • Enhanced error handling for data fetching in the teacher dashboard.
    • Updated localization strings for clarity and consistency across the application.
    • Improved UI presentation in various components, including button styles and text visibility.
    • Enhanced functionality and maintainability of the classroom editing modal.
    • Updated the structure of the teacher dashboard component for better data handling.
  • Bug Fixes

    • Streamlined validations in the classroom editing modal to ensure required fields are checked correctly.

Copy link
Contributor
coderabbitai bot commented Oct 30, 2024

Walkthrough

The pull request introduces several enhancements across multiple components and modules. A new getter method, getAllClassrooms, is added to the Vuex store for the teacher dashboard, aggregating classroom data. The fetchData action now includes improved error handling for the teacherId state. Changes to utility functions enhance course ID management, while localization strings are updated for clarity. Additionally, the ModalEditClass.vue component is significantly restructured with new subcomponents, and various UI adjustments are made for better usability across teacher dashboard components.

Changes

File Path Change Summary
app/core/store/modules/teacherDashboard.js - Added getter method getAllClassrooms to aggregate active, shared, and archived classrooms.
- Enhanced error handling in fetchData action to check teacherId state.
app/core/utils.js - Updated freeCocoCourseIDs to include HACKSTACK.
- Simplified name property in getCodeFormats method.
app/locale/en.js - Extensive updates to localization strings for clarity and consistency, including new entries for features and functionalities.
app/views/ai-league/QuestionmarkView.vue - Changed <div> class from label to plabel.
- Updated v-popover to use dynamic binding for placement.
- Added new prop popoverPlacement with a default value of "right".
ozaria/site/components/teacher-dashboard/BaseTeacherDashboard/index.vue - Initialized newClassroom as an empty object.
- Added console log in openEditClassModal method.
- Removed SEEN_TEACHER_DETAILS_MODAL constant.
ozaria/site/components/teacher-dashboard/modals/ModalEditClass.vue - Introduced ClassStartEndDateComponent and CourseCodeLanguageFormatComponent.
- Updated data initialization and validations.
- Refactored saveClass method and improved template layout.
ozaria/site/components/teacher-dashboard/modals/common/ButtonImportClassroom.vue - Adjusted button styling: reduced image width, padding, font size, and line height.
ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/ClassStartEndDateComponent.vue - Added new component for selecting class start and end dates with appropriate events and styles.
ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/CourseCodeLanguageFormatComponent.vue - Introduced new component for managing course code and language formats with reactive data binding and error handling.

Possibly related PRs

Poem

🐇 In the dashboard where teachers play,
New classrooms gather, bright as day.
With popovers dancing, and dates to choose,
Our lessons grow, with nothing to lose!
So hop along, let’s teach and share,
In this vibrant space, we show we care! 🌟

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 eslint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

warning eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
warning eslint > @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead
warning eslint > @humanwhocodes/config-array > @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
warning eslint > file-entry-cache > flat-cache > rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
warning eslint > file-entry-cache > flat-cache > rimraf > glob@7.2.3: Glob versions prior to v9 are no longer supported
warning eslint > file-entry-cache > flat-cache > rimraf > glob > inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
warning eslint-plugin-script-tags@0.5.0: Now published as @mapbox/eslint-plugin-script-tags
warning blockly > jsdom > abab@2.0.6: Use your platform's native atob() and btoa() methods instead
warning blockly > jsdom > data-urls > abab@2.0.6: Use your platform's native atob() and btoa() methods instead
warning blockly > jsdom > domexception@4.0.0: Use your platform's native DOMException instead
warning treema > express > mkdirp@0.3.3: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
warning treema > express > connect@2.7.2: connect 2.x series is deprecated
warning treema > express > connect > formidable@1.0.11: Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau
warning vue@2.7.10: Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.
warning jasmine > glob@3.2.11: Glob versions prior to v9 are no longer supported
warning jasmine > glob > minimatch@0.3.0: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
warning karma > glob@7.2.3: Glob versions prior to v9 are no longer supported
warning karma > rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
warning karma > tmp > rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
warning karma > socket.io > debug@4.1.1: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (debug-js/debug#797)
warning karma > socket.io > engine.io > debug@4.1.1: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (debug-js/debug#797)
warning karma > socket.io > socket.io-parser > debug@4.1.1: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (debug-js/debug#797)
warning pug > pug-filters > constantinople > babel-types > babel-runtime > core-js@2.6.12: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
warning standard > eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
warning @babel/plugin-proposal-optional-chaining@7.21.0: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.
error treema@0.0.0: The engine "node" is incompatible with this module. Expected version "0.8.x". Got "22.9.0"
error Found incompatible module.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Outside diff range and nitpick comments (11)
app/views/ai-league/QuestionmarkView.vue (2)

12-33: Consider adding validation for slot content.

While the slot implementation is good for component composition, consider adding validation or fallback content for when the slot is empty.

 <template slot="popover">
   <slot name="popover">
+    <!-- @slot Use this slot to provide custom popover content -->
     <div class="content">

76-85: Add vendor prefixes for font-feature-settings.

For better browser compatibility, consider adding vendor prefixes for font-feature-settings.

 .content {
   color: #131B25;
+  -webkit-font-feature-settings: 'clig' off, 'liga' off;
+  -moz-font-feature-settings: 'clig' off, 'liga' off;
   font-feature-settings: 'clig' off, 'liga' off;
app/core/store/modules/teacherDashboard.js (1)

163-169: LGTM! Consider memoizing the result for performance.

The implementation correctly aggregates all classroom types with proper null safety. Since this getter combines multiple arrays and might be called frequently in the classroom modal, consider memoizing the result to prevent unnecessary recomputations.

You could use a memoization library or implement a simple cache:

getAllClassrooms: (state, getters) => {
  if (!state._cachedClassrooms) {
    state._cachedClassrooms = [
      ...(getters.getActiveClassrooms || []),
      ...(getters.getSharedClassrooms || []),
      ...(getters.getArchivedClassrooms || [])
    ]
  }
  return state._cachedClassrooms
}

Note: Remember to invalidate the cache when the source getters change.

ozaria/site/components/teacher-dashboard/modals/ModalEditClass.vue (6)

Line range hint 45-83: Initialize data properties with default values to prevent undefined errors

When accessing properties of this.classroom, if any of them are undefined, it could lead to runtime errors. Consider providing default values using logical OR (||) to ensure data properties are initialized safely.

Example adjustment:

 newClassName: this.classroom?.name || '',
 newProgrammingLanguage: this.classroom?.aceConfig?.language || 'python',
 newLiveCompletion: typeof cLiveCompletion === 'undefined' ? true : cLiveCompletion,
 newClassroomItems: typeof cItems === 'undefined' ? true : cItems,
 newCodeFormats: typeof cFormats === 'undefined' ? ['text-code'] : cFormats,
 newCodeFormatDefault: typeof cFormatDefault === 'undefined' ? 'text-code' : cFormatDefault,
 newLevelChat: typeof cLevelChat === 'undefined' ? true : cLevelChat === 'fixed_prompt_only',
 newClassroomDescription: this.classroom?.description || '',
 newAverageStudentExp: this.classroom?.averageStudentExp || '',
 newClassroomType: this.classroom?.type || '',
 newClassDateStart: this.classroom?.classDateStart || '',
 newClassDateEnd: this.classroom?.classDateEnd || '',
 newClassesPerWeek: this.classroom?.classesPerWeek || '',
 newMinutesPerClass: this.classroom?.minutesPerClass || '',

112-113: Remove unused computed properties

The computed properties activeClassrooms and allClassrooms are defined but not used within the component. Removing them can clean up the code.

Apply this diff to remove the unused computed properties:

 computed: {
   ...mapGetters({
     getSessionsMapForClassroom: 'levelSessions/getSessionsMapForClassroom',
     courses: 'courses/sorted',
     getCourseInstances: 'courseInstances/getCourseInstancesOfClass',
-    activeClassrooms: 'teacherDashboard/getActiveClassrooms',
-    allClassrooms: 'teacherDashboard/getAllClassrooms',
   }),

281-283: Remove unused method enableBlocks

The method enableBlocks is defined but not used anywhere in the component. Consider removing it to simplify the code.

Apply this diff to remove the unused method:

 methods: {
   // Other methods...
-  enableBlocks () {
-    return ['python', 'javascript', 'lua'].includes(this.newProgrammingLanguage || 'python')
-  },

Line range hint 930-971: Improve accessibility and semantics in the 'More Options' toggle

The 'More Options' link uses an <a> tag without an href attribute, which is not semantically correct and may cause accessibility issues. Replace it with a <button> element styled appropriately.

Apply this diff to fix the semantic issue:

 <!--eslint-enable-->
-<a
+<button
   class="more-options-text"
   @click="toggleMoreOptions"
->
+  type="button"
+>
   {{ moreOptionsText }}
   <span v-html="moreOptionsIcon" />
-</a>
+</button>
 <!--eslint-enable-->

1007-1012: Use consistent units in CSS margins

In the .edit-class style, the margin values mix units (px and 0px). For consistency and cleanliness, use the same unit throughout.

Apply this diff to standardize the units:

 .edit-class {
   display: flex;
   flex-direction: column;
   justify-content: center;
   align-items: center;
-  margin: 5px 5px 0px 5px;
+  margin: 5px 5px 0 5px;
   width: 600px;
 }

1148-1152: Simplify margin shorthand in .more-options-text-container

The margins in .more-options-text-container can be combined into a single shorthand property.

Apply this diff to simplify the CSS:

 .more-options-text-container {
-  margin-bottom: -5px;
-  margin-top: -5px;
+  margin: -5px 0;
 }
app/locale/en.js (2)

1517-1518: Consider adding a tooltip or help text to explain what "Junior only" means.

To improve clarity for translators and maintainers, consider adding a comment or expanding the string to explain that these "blocks (icons)" are only available in CodeCombat's Junior product aimed at younger students.


Line range hint 1795-1817: The changes look good, but consider improving the naming of some keys.

The added strings for "initial_free_courses_description", "junior_code_format_only" and "hackstack_no_code_language_format" convey their purpose clearly.

As a nitpick for maintainability, consider renaming "initial_free_courses_description" to something more specific like "initial_free_courses_update_hint" to better convey that it's an instructional message about updating courses.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 33bc67e and 708375c.

📒 Files selected for processing (9)
  • app/core/store/modules/teacherDashboard.js (1 hunks)
  • app/core/utils.js (2 hunks)
  • app/locale/en.js (5 hunks)
  • app/views/ai-league/QuestionmarkView.vue (2 hunks)
  • ozaria/site/components/teacher-dashboard/BaseTeacherDashboard/index.vue (2 hunks)
  • ozaria/site/components/teacher-dashboard/modals/ModalEditClass.vue (19 hunks)
  • ozaria/site/components/teacher-dashboard/modals/common/ButtonImportClassroom.vue (2 hunks)
  • ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/ClassStartEndDateComponent.vue (1 hunks)
  • ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/CourseCodeLanguageFormatComponent.vue (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • ozaria/site/components/teacher-dashboard/modals/common/ButtonImportClassroom.vue
🔇 Additional comments (14)
ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/ClassStartEndDateComponent.vue (1)

1-71: Verify integration with AI High School feature.

While the component implementation looks solid, we should verify its behavior when used with the AI High School (AI HS) feature mentioned in the PR objectives.

app/views/ai-league/QuestionmarkView.vue (2)

2-2: Good rename from 'label' to 'plabel'.

The class rename helps avoid potential conflicts with HTML's native label element.


6-6: LGTM: Dynamic placement prop improves reusability.

The addition of the popoverPlacement prop with dynamic binding makes the component more flexible and reusable.

Also applies to: 42-47

app/core/store/modules/teacherDashboard.js (1)

Line range hint 279-285: LGTM! Error handling looks good.

The error handling for missing teacherId is appropriate, with proper user notification and early return to prevent unnecessary API calls.

app/core/utils.js (2)

1650-1650: LGTM: Text simplification aligns with PR objectives.

The removal of the "(default)" suffix from the text-code format name aligns with the PR objective of minimizing text in the modal. The change makes the UI cleaner while maintaining clarity.

Run the following script to check for any other instances that might need similar updates:

#!/bin/bash
# Description: Check for other instances of "(default)" text that might need similar updates

# Test: Search for other instances of "(default)" text
rg -l "\(default\)" | grep -v "utils.js"

372-372: Verify the impact of adding HACKSTACK to free courses.

The addition of HACKSTACK to freeCocoCourseIDs makes it freely accessible. Please ensure this is intentional and aligns with the business requirements for the AI High School feature.

Run the following script to check for any pricing or access-related code that might need updates:

✅ Verification successful

Based on the search results, I can now generate the final response as there is sufficient evidence to verify the impact of adding HACKSTACK to free courses.

Adding HACKSTACK to free courses is consistent with the codebase

The change to include HACKSTACK in freeCocoCourseIDs is properly integrated across the codebase:

  • The course ID is already defined in allCourseIDs and has proper course acronym (HS) and campaign slug mappings
  • UI components correctly handle HACKSTACK course access:
    • CourseCodeLanguageFormatComponent.vue checks for HACKSTACK in free courses
    • Course navigation and classroom views have specific handling for HACKSTACK courses
    • Special routing and URLs are implemented for HACKSTACK course instances
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for code that might be affected by the free course change

# Test: Search for references to freeCocoCourseIDs that might need updates
rg -A 5 'freeCocoCourseIDs'

# Test: Search for HACKSTACK course-related code
rg -A 5 'HACKSTACK'

Length of output: 15238

ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/CourseCodeLanguageFormatComponent.vue (1)

330-334: ⚠️ Potential issue

Handle missing courses more robustly in initialFreeCourses

Returning an empty object {} when a course is not found may lead to issues if subsequent code expects certain properties to exist. Consider filtering out undefined courses or providing default values to prevent potential errors.

Apply this diff to improve handling of missing courses:

return [
  ...utils.freeCocoCourseIDs.map(id => {
    const course = this.courses.find(({ _id }) => _id === id)
    if (!course) {
-     // computed value uses in template before mounted, so no courses yet
-     return {}
+     return null
    }
    return {
      id,
      name: utils.i18n(course, 'name'),
      blurb: $.i18n.t(`teachers.free_course_blurb_${course.slug}`),
    }
- }).filter(course => course !== null)
+ }).filter(course => course)
]

Likely invalid or redundant comment.

ozaria/site/components/tea 8000 cher-dashboard/modals/ModalEditClass.vue (5)

636-643: Ensure ClassStartEndDateComponent receives correct props

When using ClassStartEndDateComponent, verify that the props classDateStart and classDateEnd are correctly validated and handled within the component, especially since they are required when asClub is true.


643-657: Validate props passed to CourseCodeLanguageFormatComponent

Ensure that all required props are being passed to CourseCodeLanguageFormatComponent and that their values are correct, especially code-formats, code-format-default, and code-language.


1170-1183: Adjust alignment in .submit-button style

The .submit-button class sets align-items: center, which may not be necessary if the content is meant to align items flex-start or flex-end. Review the styling to ensure buttons and messages are aligned as intended.


37-42: ⚠️ Potential issue

Remove unnecessary default values for required props

The classroom and asClub props are marked as required but have default values. Since these props are required, default values are unnecessary and could mask issues if the props are not provided.

Apply this diff to remove the default values:

 props: {
   classroom: {
     type: Object,
     required: true,
-    default: () => {},
   },
   asClub: {
     type: Boolean,
     required: true,
-    default: false,
   },
 },

Likely invalid or redundant comment.


584-589: ⚠️ Potential issue

Correct form error display logic

The v-if condition for displaying the form error message should check for the validation error directly, without negating it. Replace !$v.newClassName.required with $v.newClassName.$error to display the error when validation fails.

Apply this diff to correct the condition:

 <label for="form-class-name">
   <span class="control-label"> {{ $t("teachers.class_name") }} </span>
-  <span
-    v-if="!$v.newClassName.required"
-    class="form-error"
-  >
+  <span
+    v-if="$v.newClassName.$error"
+    class="form-error"
+  >
     {{ $t("form_validation_errors.required") }}
   </span>
 </label>

Likely invalid or redundant comment.

app/locale/en.js (2)

Line range hint 1-1: Looks good!

The module exports syntax is correct.


Line range hint 1751-1775: Verify the changes to the "programming_language" and "code_formats" sections.

The "programming_language" string was updated to remove the "(Required)" note. Ensure this change is intentional and doesn't impact functionality.

The "code_formats" section had some strings added and updated. Double check that the new "code_formats_disabled_by" string works properly with the "language" substitution. Also verify the "code_formats_mobile" and "code_formats_fallback" strings are used correctly.

class="form-control"
>
<label for="form-new-class-date-end">
<span class="spl.spr">{{ $t("courses.student_age_range_to") }}</span>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect i18n key in the "to" label.

The translation key spl.spr appears to be incorrect and should likely be just spr (space right) based on common i18n patterns.

-          <span class="spl.spr">{{ $t("courses.student_age_range_to") }}</span>
+          <span class="spr">{{ $t("courses.student_age_range_to") }}</span>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span class="spl.spr">{{ $t("courses.student_age_range_to") }}</span>
<span class="spr">{{ $t("courses.student_age_range_to") }}</span>

Comment on lines 8 to 23
<input
id="form-new-class-date-start"
v-model="newClassDateStart"
type="date"
class="form-control"
>
<label for="form-new-class-date-end">
<span class="spl.spr">{{ $t("courses.student_age_range_to") }}</span>
</label>
<input
id="form-new-class-date-end"
v-model="newClassDateEnd"
type="date"
class="form-control"
>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance accessibility and validation for date inputs.

The date inputs could benefit from several improvements:

  1. Add aria-labels for screen readers
  2. Add min/max date validation
  3. Add validation feedback for invalid date ranges
  4. Add help text explaining the expected date format
         <input
           id="form-new-class-date-start"
           v-model="newClassDateStart"
           type="date"
           class="form-control"
+          :aria-label="$t('courses.estimated_class_dates_label')"
+          :min="minDate"
+          :max="newClassDateEnd"
+          @input="validateDates"
         >
         <label for="form-new-class-date-end">
           <span class="spr">{{ $t("courses.student_age_range_to") }}</span>
         </label>
         <input
           id="form-new-class-date-end"
           v-model="newClassDateEnd"
           type="date"
           class="form-control"
+          :aria-label="$t('courses.estimated_class_dates_end_label')"
+          :min="newClassDateStart"
+          @input="validateDates"
         >
+        <div v-if="dateError" class="invalid-feedback d-block">
+          {{ dateError }}
+        </div>

Committable suggestion was skipped due to low confidence.

Comment on lines +60 to +70
.estimated-date-fields {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;

input {
width: 45%;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance responsive layout for mobile devices.

The current layout might break on smaller screens. Consider adding responsive breakpoints.

 .estimated-date-fields {
   display: flex;
   flex-direction: row;
   justify-content: space-between;
   align-items: center;
   width: 100%;
 
   input {
     width: 45%;
   }
+
+  @media (max-width: 576px) {
+    flex-direction: column;
+    gap: 1rem;
+
+    input {
+      width: 100%;
+    }
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.estimated-date-fields {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
input {
width: 45%;
}
}
.estimated-date-fields {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
input {
width: 45%;
}
@media (max-width: 576px) {
flex-direction: column;
gap: 1rem;
input {
width: 100%;
}
}
}

Comment on lines +31 to +42
props: {
classDateStart: {
type: String,
required: false,
default: '',
},
classDateEnd: {
type: String,
required: false,
default: '',
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add prop validation and documentation.

Consider adding validation and JSDoc documentation for the props to improve maintainability and type safety.

+/**
+ * Component for managing class start and end dates
+ * @component
+ */
 export default Vue.extend({
   name: 'ClassStartEndDateComponent',
   props: {
+    /** ISO date string for class start date */
     classDateStart: {
       type: String,
       required: false,
       default: '',
+      validator: value => !value || /^\d{4}-\d{2}-\d{2}$/.test(value)
     },
+    /** ISO date string for class end date */
     classDateEnd: {
       type: String,
       required: false,
       default: '',
+      validator: value => !value || /^\d{4}-\d{2}-\d{2}$/.test(value)
     },
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
props: {
classDateStart: {
type: String,
required: false,
default: '',
},
classDateEnd: {
type: String,
required: false,
default: '',
},
},
/**
* Component for managing class start and end dates
* @component
*/
export default Vue.extend({
name: 'ClassStartEndDateComponent',
props: {
/** ISO date string for class start date */
classDateStart: {
type: String,
required: false,
default: '',
validator: value => !value || /^\d{4}-\d{2}-\d{2}$/.test(value)
},
/** ISO date string for class end date */
classDateEnd: {
type: String,
required: false,
default: '',
validator: value => !value || /^\d{4}-\d{2}-\d{2}$/.test(value)
},
},

Comment on lines +43 to +56
data () {
return {
newClassDateStart: this.classDateStart,
newClassDateEnd: this.classDateEnd,
}
},
watch: {
newClassDateStart (newVal) {
this.$emit('classDateStartUpdated', newVal)
},
newClassDateEnd (newVal) {
this.$emit('classDateEndUpdated', newVal)
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add prop change handlers and date validation.

The component should handle prop changes and validate dates before emitting updates.

   data () {
     return {
       newClassDateStart: this.classDateStart,
       newClassDateEnd: this.classDateEnd,
+      dateError: null
     }
   },
   watch: {
+    classDateStart(newVal) {
+      this.newClassDateStart = newVal
+    },
+    classDateEnd(newVal) {
+      this.newClassDateEnd = newVal
+    },
     newClassDateStart (newVal) {
+      if (this.validateDates()) {
         this.$emit('classDateStartUpdated', newVal)
+      }
     },
     newClassDateEnd (newVal) {
+      if (this.validateDates()) {
         this.$emit('classDateEndUpdated', newVal)
+      }
     },
   },
+  methods: {
+    validateDates() {
+      if (this.newClassDateStart && this.newClassDateEnd &&
+          new Date(this.newClassDateStart) > new Date(this.newClassDateEnd)) {
+        this.dateError = this.$t('courses.invalid_date_range')
+        return false
+      }
+      this.dateError = null
+      return true
+    }
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data () {
return {
newClassDateStart: this.classDateStart,
newClassDateEnd: this.classDateEnd,
}
},
watch: {
newClassDateStart (newVal) {
this.$emit('classDateStartUpdated', newVal)
},
newClassDateEnd (newVal) {
this.$emit('classDateEndUpdated', newVal)
},
},
data () {
return {
newClassDateStart: this.classDateStart,
newClassDateEnd: this.classDateEnd,
dateError: null
}
},
watch: {
classDateStart(newVal) {
this.newClassDateStart = newVal
},
classDateEnd(newVal) {
this.newClassDateEnd = newVal
},
newClassDateStart (newVal) {
if (this.validateDates()) {
this.$emit('classDateStartUpdated', newVal)
}
},
newClassDateEnd (newVal) {
if (this.validateDates()) {
this.$emit('classDateEndUpdated', newVal)
}
},
},
methods: {
validateDates() {
if (this.newClassDateStart && this.newClassDateEnd &&
new Date(this.newClassDateStart) > new Date(this.newClassDateEnd)) {
this.dateError = this.$t('courses.invalid_date_range')
return false
}
this.dateError = null
return true
}
}

}
},
availableCodeFormats () {
const codeFormats = JSON.parse(JSON.stringify(this.codeFormatObject))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid using JSON.parse(JSON.stringify(...)) for deep cloning codeFormatObject

Using JSON.parse(JSON.stringify(...)) for deep cloning can be inefficient and may not correctly handle complex data types like functions, dates, or undefined values. Consider using a more reliable deep cloning method, such as structured cloning or a utility function like lodash.cloneDeep().

Comment on lines +310 to +299
const languages = JSON.parse(JSON.stringify(this.codeLanguageObject))
// ozaria do not have these 2 langs
delete languages.coffeescript
delete languages.lua
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid using JSON.parse(JSON.stringify(...)) for deep cloning codeLanguageObject

Similar to the previous comment, using JSON.parse(JSON.stringify(...)) can lead to issues with data integrity and performance. It's advisable to use a more robust deep cloning approach to ensure all data types are properly handled.

Comment on lines 262 to 268
hasJunior () {
if (this.isNewClassroom) {
return this.newInitialFreeCourses.includes(utils.courseIDs.JUNIOR)
} else {
return this.getCourseInstances(this.classroomId)?.some(ci => ci.courseID === utils.courseIDs.JUNIOR)
}
},
hasHackstack () {
if (this.isNewClassroom) {
return this.newInitialFreeCourses.includes(utils.courseIDs.HACKSTACK)
} else {
return this.getCourseInstances(this.classroomId)?.some(ci => ci.courseID === utils.courseIDs.HACKSTACK)
}
},
hasOnlyHackstack () {
if (this.isNewClassroom) {
return this.newInitialFreeCourses.includes(utils.courseIDs.HACKSTACK) && this.newInitialFreeCourses?.length === 1
} else {
return this.getCourseInstances(this.classroomId)?.some(ci => ci.courseID === utils.courseIDs.HACKSTACK) && this.getCourseInstances(this.classroomId)?.length === 1
}
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor computed properties to eliminate code duplication

The computed properties hasJunior, hasHackstack, and hasOnlyHackstack share similar logic with slight variations. Refactoring these into a single utility function or a more generic computed property can enhance code maintainability and adhere to the DRY (Don't Repeat Yourself) principle.

Comment on lines 279 to 267
} else {
return this.getCourseInstances(this.classroomId)?.some(ci => ci.courseID === utils.courseIDs.HACKSTACK) && this.getCourseInstances(this.classroomId)?.length === 1
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize by caching getCourseInstances result

In the hasOnlyHackstack computed property, getCourseInstances(this.classroomId) is called multiple times. Caching the result in a variable can improve performance by avoiding redundant calls.

Apply this diff to optimize the code:

hasOnlyHackstack () {
  if (this.isNewClassroom) {
    return this.newInitialFreeCourses.includes(utils.courseIDs.HACKSTACK) && this.newInitialFreeCourses?.length === 1
  } else {
+   const courseInstances = this.getCourseInstances(this.classroomId) || []
-   return this.getCourseInstances(this.classroomId)?.some(ci => ci.courseID === utils.courseIDs.HACKSTACK) && this.getCourseInstances(this.classroomId)?.length === 1
+   return courseInstances.some(ci => ci.courseID === utils.courseIDs.HACKSTACK) && courseInstances.length === 1
  }
},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else {
return this.getCourseInstances(this.classroomId)?.some(ci => ci.courseID === utils.courseIDs.HACKSTACK) && this.getCourseInstances(this.classroomId)?.length === 1
}
} else {
const courseInstances = this.getCourseInstances(this.classroomId) || []
return courseInstances.some(ci => ci.courseID === utils.courseIDs.HACKSTACK) && courseInstances.length === 1
}

Comment on lines +872 to +884
<class-start-end-date-component
v-if="!asClub && (moreOptions && isCodeCombat || me.isCodeNinja())"
class="form-group row"
>
<div class="col-xs-12">
<label for="form-new-class-date-start">
<span class="control-label"> {{ $t("courses.estimated_class_dates_label") }} </span>
</label>
<div class="estimated-date-fields">
<input
id="form-new-class-date-start"
v-model="newClassDateStart"
type="date"
class="form-control"
>
<label for="form-new-class-date-end">
<span class="spl.spr">{{ $t("courses.student_age_range_to") }}</span>
</label>
<input
id="form-new-class-date-end"
v-model="newClassDateEnd"
type="date"
class="form-control"
>
</div>
</div>
</div>
:class-date-start="newClassDateStart"
:class-date-end="newClassDateEnd"
@classDateStartUpdated="updateClassDateStart"
@classDateEndUpdated="updateClassDateEnd"
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid duplicate usage of ClassStartEndDateComponent

The ClassStartEndDateComponent is conditionally rendered both at lines 636-643 and 872-878. Consider consolidating the component usage to avoid duplicate code and potential inconsistencies.

Apply this diff to conditionally render the component once:

 <!-- Existing component usage -->
-<class-start-end-date-component
-  v-if="asClub"
-  :class-date-start="newClassDateStart"
-  :class-date-end="newClassDateEnd"
-  @classDateStartUpdated="updateClassDateStart"
-  @classDateEndUpdated="updateClassDateEnd"
-/>
 <!-- Move the v-if condition to include both scenarios -->
+<class-start-end-date-component
+  v-if="asClub || (!asClub && (moreOptions && isCodeCombat || me.isCodeNinja()))"
+  :class-date-start="newClassDateStart"
+  :class-date-end="newClassDateEnd"
+  @classDateStartUpdated="updateClassDateStart"
+  @classDateEndUpdated="updateClassDateEnd"
+/>
-<!-- Duplicate component usage removed -->

Committable suggestion was skipped due to low confidence.

@mrfinch mrfinch force-pushed the saurabh/classroom-modal branch from b6c3c78 to d143288 Compare October 31, 2024 19:02
Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (5)
ozaria/site/components/teacher-dashboard/modals/ModalEditClass.vue (4)

Line range hint 45-83: Simplify data initialization logic

The nested ternary operations and repeated undefined checks can be simplified using object destructuring and default values.

Consider refactoring the data initialization:

 data: function () {
-    const cItems = this.classroom?.classroomItems
-    const cLiveCompletion = this.classroom?.aceConfig?.liveCompletion
-    const cFormats = this.classroom?.aceConfig?.codeFormats
-    const cFormatDefault = this.classroom?.aceConfig?.codeFormatDefault
-    const cLevelChat = this.classroom?.aceConfig?.levelChat
+    const { 
+      classroomItems: cItems,
+      aceConfig: {
+        liveCompletion: cLiveCompletion,
+        codeFormats: cFormats,
+        codeFormatDefault: cFormatDefault,
+        levelChat: cLevelChat
+      } = {}
+    } = this.classroom || {}
     return {
       showGoogleClassroom: me.showGoogleClassroom(),
-      newClassName: this.classroom?.name || '',
-      newProgrammingLanguage: this.classroom?.aceConfig?.language || 'python',
+      newClassName: this.classroom?.name ?? '',
+      newProgrammingLanguage: this.classroom?.aceConfig?.language ?? 'python',
       newLiveCompletion: typeof cLiveCompletion === 'undefined' ? true : cLiveCompletion,
       newClassroomItems: typeof cItems === 'undefined' ? true : cItems,
       // ... rest of the properties
     }

Line range hint 284-432: Improve error handling in saveClass method

There are a few areas that could be improved in the saveClass method:

  1. Date validation is duplicated at lines 309-312 and within the asClub condition
  2. Generic error messages could be more specific

Consider these improvements:

-      if (this.newClassDateStart && this.newClassDateEnd && moment(this.newClassDateEnd).isBefore(moment(this.newClassDateStart))) {
-        this.errMsg = 'End date should be after start date'
-        this.saving = false
-        return
-      }
+      // Extract date validation to a separate method
+      const isValidDateRange = () => {
+        if (!this.newClassDateStart || !this.newClassDateEnd) return true
+        return !moment(this.newClassDateEnd).isBefore(moment(this.newClassDateStart))
+      }
+
+      if (!isValidDateRange()) {
+        this.errMsg = 'Class end date must be after the start date'
+        this.saving = false
+        return
+      }

       // ... rest of the method

-          this.errMsg = err?.message || 'Failed to create classroom'
+          this.errMsg = err?.message || 'Failed to create classroom. Please check your input and try again.'

967-989: Enhance accessibility for saving state

The saving indicator and error message could benefit from ARIA attributes for better accessibility.

Consider these accessibility improvements:

 <div class="submit-button">
   <secondary-button
     :disabled="saving"
     class="class-submit"
+    :aria-busy="saving"
     @click="saveClass"
   >
     {{ classroomInstance.isNew() ? $t("courses.create_class") : $t("common.save_changes") }}
   </secondary-button>
   <span
     v-if="saving"
     class="saving-text"
+    role="status"
+    aria-live="polite"
   >
     saving...
   </span>
   <span
     v-if="errMsg"
     class="error-msg error"
+    role="alert"
+    aria-live="assertive"
   >
     {{ errMsg }}
   </span>
 </div>

1175-1189: Use SCSS variables for consistent styling

Consider using SCSS variables for colors and spacing in the new styles.

 .submit-button {
   display: flex;
   align-items: center;
   justify-content: center;
   flex-direction: column;

   .saving-text {
     @include font-p-4-paragraph-smallest-gray;
-    margin-top: 5px;
+    margin-top: $spacing-small;
   }

   .error-msg {
-    margin-top: 5px;
+    margin-top: $spacing-small;
   }
 }
ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/CourseCodeLanguageFormatComponent.vue (1)

260-260: Simplify the enableBlocks computed property

The default value for newProgrammingLan 10000 guage is already set in the data() function. The use of || 'python' is redundant here. Simplifying the condition improves readability.

Apply this diff to streamline the code:

-         return ['python', 'javascript', 'lua'].includes(this.newProgrammingLanguage || 'python')
+         return ['python', 'javascript', 'lua'].includes(this.newProgrammingLanguage)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 708375c and d143288.

📒 Files selected for processing (9)
  • app/core/store/modules/teacherDashboard.js (1 hunks)
  • app/core/utils.js (2 hunks)
  • app/locale/en.js (5 hunks)
  • app/views/ai-league/QuestionmarkView.vue (2 hunks)
  • ozaria/site/components/teacher-dashboard/BaseTeacherDashboard/index.vue (1 hunks)
  • ozaria/site/components/teacher-dashboard/modals/ModalEditClass.vue (19 hunks)
  • ozaria/site/components/teacher-dashboard/modals/common/ButtonImportClassroom.vue (2 hunks)
  • ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/ClassStartEndDateComponent.vue (1 hunks)
  • ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/CourseCodeLanguageFormatComponent.vue (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/core/store/modules/teacherDashboard.js
  • app/views/ai-league/QuestionmarkView.vue
  • ozaria/site/components/teacher-dashboard/BaseTeacherDashboard/index.vue
  • ozaria/site/components/teacher-dashboard/modals/common/ButtonImportClassroom.vue
  • ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/ClassStartEndDateComponent.vue
🔇 Additional comments (5)
ozaria/site/components/teacher-dashboard/modals/ModalEditClass.vue (2)

17-18: LGTM: Component imports are well-structured

The addition of ClassStartEndDateComponent and CourseCodeLanguageFormatComponent helps modularize the form functionality.

Also applies to: 27-28


642-648: Duplicate component usage detected

The ClassStartEndDateComponent is still used in two places in the template.

Also applies to: 878-884

app/core/utils.js (2)

1650-1650: LGTM! Simplified format name.

The removal of the default indicator from the text-code format name aligns with the formatting options changes for AI HS feature.


372-372: LGTM! Verify HACKSTACK course access.

The addition of HACKSTACK to free courses aligns with the AI High School feature introduction.

✅ Verification successful

Let me verify if the HACKSTACK course ID is properly integrated across the codebase by checking its usage in course-related configurations and access control logic.


HACKSTACK course is properly integrated as a free course

The verification shows that:

  • HACKSTACK course ID is properly defined in courseIDs
  • freeCocoCourseIDs array is correctly used in course access logic
  • The course is referenced across multiple components in both Ozaria and app directories
  • It's properly integrated into the free courses system alongside JUNIOR and INTRODUCTION_TO_COMPUTER_SCIENCE
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify HACKSTACK course access configuration
# Test: Check if HACKSTACK course ID is properly referenced in other configurations
rg -A 5 "663b25f11c568468efc8adde"

Length of output: 458


Script:

#!/bin/bash
# Check for HACKSTACK references in course-related files
rg -l "HACKSTACK"

# Check for the course ID usage in course access or validation logic
rg -B 3 -A 3 "freeCocoCourseIDs"

Length of output: 2540

app/locale/en.js (1)

1517-1518: LGTM! Added translation strings for Junior blocks format option

The addition of these translation strings properly supports the UI for a Junior-specific blocks format that uses icons, making it more accessible for younger users.

Comment on lines 198 to 235
isCodeCombat: {
type: Boolean,
required: true,
default: true,
},
isNewClassroom: {
type: Boolean,
required: true,
default: false,
},
classroomId: {
type: String,
required: true,
default: '',
},
asClub: {
type: Boolean,
required: true,
default: false,
},
newClubType: {
type: String,
required: true,
default: '',
},
courses: {
type: Array,
required: true,
default: () => [],
},
codeFormats: {
type: Array,
required: true,
default: () => [],
},
codeFormatDefault: {
type: String,
required: true,
default: '',
},
codeLanguage: {
type: String,
required: true,
default: '',
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid setting default values for required props

Props marked as required: true should not have default values. Since these props are required, they must be provided by the parent component, and setting a default value is redundant and could lead to unintended behavior. Consider removing the default properties from these required props: isCodeCombat, isNewClassroom, classroomId, asClub, newClubType, courses, codeFormats, codeFormatDefault, codeLanguage.

Apply this diff to remove default values from required props:

        isCodeCombat: {
          type: Boolean,
          required: true,
-         default: true,
        },
        isNewClassroom: {
          type: Boolean,
          required: true,
-         default: false,
        },
        classroomId: {
          type: String,
          required: true,
-         default: '',
        },
        asClub: {
          type: Boolean,
          required: true,
-         default: false,
        },
        newClubType: {
          type: String,
          required: true,
-         default: '',
        },
        courses: {
          type: Array,
          required: true,
-         default: () => [],
        },
        codeFormats: {
          type: Array,
          required: true,
-         default: () => [],
        },
        codeFormatDefault: {
          type: String,
          required: true,
-         default: '',
        },
        codeLanguage: {
          type: String,
          required: true,
-         default: '',
        },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
10000
isCodeCombat: {
type: Boolean,
required: true,
default: true,
},
isNewClassroom: {
type: Boolean,
required: true,
default: false,
},
classroomId: {
type: String,
required: true,
default: '',
},
asClub: {
type: Boolean,
required: true,
default: false,
},
newClubType: {
type: String,
required: true,
default: '',
},
courses: {
type: Array,
required: true,
default: () => [],
},
codeFormats: {
type: Array,
required: true,
default: () => [],
},
codeFormatDefault: {
type: String,
required: true,
default: '',
},
codeLanguage: {
type: String,
required: true,
default: '',
},
isCodeCombat: {
type: Boolean,
required: true
},
isNewClassroom: {
type: Boolean,
required: true
},
classroomId: {
type: String,
required: true
},
asClub: {
type: Boolean,
required: true
},
newClubType: {
type: String,
required: true
},
courses: {
type: Array,
required: true
},
codeFormats: {
type: Array,
required: true
},
codeFormatDefault: {
type: String,
required: true
},
codeLanguage: {
type: String,
required: true
},

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d143288 and 467e0d2.

📒 Files selected for processing (1)
  • ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/CourseCodeLanguageFormatComponent.vue (1 hunks)
🔇 Additional comments (3)
ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/CourseCodeLanguageFormatComponent.vue (3)

268-268: Avoid using JSON.parse(JSON.stringify(...)) for deep cloning codeFormatObject

Using JSON.parse(JSON.stringify(...)) for deep cloning can be inefficient and may not correctly handle complex data types like functions, dates, or undefined values. Consider using a more reliable deep cloning method, such as structured cloning or a utility function like lodash.cloneDeep().


294-297: Avoid using JSON.parse(JSON.stringify(...)) for deep cloning codeLanguageObject

Using JSON.parse(JSON.stringify(...)) can lead to issues with data integrity and performance. It's advisable to use a more robust deep cloning approach to ensure all data types are properly handled.


1-412: Overall, the component is well-structured and functional

The implementation of the CourseCodeLanguageFormatComponent is clear and follows Vue.js best practices. The use of computed properties and watchers effectively manages the component's state and reactivity.

Comment on lines +293 to +308
availableLanguages () {
const languages = JSON.parse(JSON.stringify(this.codeLanguageObject))
// ozaria do not have these 2 langs
delete languages.coffeescript
delete languages.lua

if (this.hasOnlyHackstack) {
for (const lang of Object.values(languages)) {
lang.disabled = true
}
}

return Object.values(languages)
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor availableLanguages to avoid mutating objects

In the availableLanguages computed property, you're cloning this.codeLanguageObject and then deleting properties. Modifying cloned objects by deleting properties can be inefficient and may introduce unintended side effects. Consider using a functional approach to filter out unwanted languages without mutating the object.

You could rewrite the computed property using array methods:

availableLanguages () {
  return Object.values(this.codeLanguageObject)
    .filter(lang => !['coffeescript', 'lua'].includes(lang.id))
    .map(lang => {
      if (this.hasOnlyHackstack) {
        return { ...lang, disabled: true }
      }
      return lang
    })
}

Comment on lines +268 to +284
const codeFormats = JSON.parse(JSON.stringify(this.codeFormatObject))
if (!this.hasJunior) {
codeFormats['blocks-icons'].disabled = true
}
if (!this.enableBlocks) {
codeFormats['blocks-and-code'].disabled = true
codeFormats['blocks-text'].disabled = true
}
if (this.hasOnlyHackstack) {
codeFormats['text-code'].disabled = true
codeFormats['blocks-icons'].disabled = true
codeFormats['blocks-and-code'].disabled = true
codeFormats['blocks-text'].disabled = true
}
return Object.values(codeFormats)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor availableCodeFormats to prevent object mutation

In the availableCodeFormats computed property, you're cloning and then mutating the codeFormats object by setting disabled flags. Mutating cloned objects can be less efficient and harder to maintain. Consider using a functional approach to generate the desired output without directly mutating objects.

Here's a possible refactor using array methods:

availableCodeFormats () {
  return Object.values(this.codeFormatObject).map(codeFormat => {
    let disabled = codeFormat.disabled || false
    if (!this.hasJunior && codeFormat.id === 'blocks-icons') {
      disabled = true
    }
    if (!this.enableBlocks && ['blocks-and-code', 'blocks-text'].includes(codeFormat.id)) {
      disabled = true
    }
    if (this.hasOnlyHackstack && ['text-code', 'blocks-icons', 'blocks-and-code', 'blocks-text'].includes(codeFormat.id)) {
      disabled = true
    }
    return { ...codeFormat, disabled }
  })
}

@@ -62,7 +60,7 @@ export default {
runningTour: null,
createdFirstClass: false,
trialRequestLoading: true,
newClassroom: new Classroom({ ownerID: me.id }),
newClassroom: {},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the empty object instead of new Classroom?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, I would not even want to use that Classroom backbone model anywhere in our Vue code 😅

I tried to minimize its usage and only require it where we need it.

v-if="saving"
class="saving-text"
>
saving...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i18n?

Copy link
Collaborator
@adamkecskes adamkecskes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tried and works well for me! The code got much cleaner, I love this!
Just a few things:

  • "?" is not always in the middle of the circle on my screen
  • can we make the <select disabled="disabled"> for "Programming Language" instead of just the options inside?
  • can we make the "Default Code Format" dropdown also disabled when only AIHS is slected? Also maybe we could add a tooltip here as well to tell user why is it disabled.
    CodeCombat_Teacher_Dashboard

@mrfinch mrfinch force-pushed the saurabh/classroom-modal branch from 467e0d2 to 34e37ab Compare November 12, 2024 10:49
Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (9)
ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/CourseCodeLanguageFormatComponent.vue (2)

63-64: Simplify class binding

The class binding can be simplified by combining the conditions.

-        class="form-control"
-        :class="{ 'placeholder-text': !newProgrammingLanguage }"
+        :class="['form-control', { 'placeholder-text': !newProgrammingLanguage }]"

128-131: Remove ineffective disabled attribute on label

The disabled attribute on the label element has no effect on functionality. Use CSS to style disabled state instead.

-          <label
-            class="checkbox-inline"
-            :disabled="codeFormat.disabled"
-          >
+          <label
+            :class="['checkbox-inline', { 'disabled': codeFormat.disabled }]"
+          >

Add corresponding CSS:

.checkbox-inline.disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
ozaria/site/components/teacher-dashboard/BaseTeacherDashboard/index.vue (2)

Line range hint 392-401: Enhance accessibility for tooltips.

Given the PR objective of moving text to tooltips, ensure the tooltip implementation follows accessibility best practices.

Add ARIA attributes to improve accessibility:

 .tooltip {
   display: block !important;
   z-index: 10000;
+  role: "tooltip"
+  aria-live: "polite"

   .tooltip-inner {
     background: black;
     color: white;
     border-radius: 16px;

Line range hint 146-157: Consider caching computed language property.

The getLanguage computed property performs array operations on every access. For better performance, especially with the new AI High School feature, consider caching the result.

 getLanguage () {
+  if (this._cachedLanguage) return this._cachedLanguage
   if (this.classroom && this.classroom.aceConfig) {
-    return this.classroom.aceConfig?.language || 'python'
+    this._cachedLanguage = this.classroom.aceConfig?.language || 'python'
+    return this._cachedLanguage
   }

   if (this.activeClassrooms.length > 0) {
-    return this.getMostCommonLanguage()
+    this._cachedLanguage = this.getMostCommonLanguage()
+    return this._cachedLanguage
   }
   return null
 }
ozaria/site/components/teacher-dashboard/modals/ModalEditClass.vue (5)

45-69: Enhance data initialization robustness

The current nested property access could be simplified using optional chaining and nullish coalescing operators for better readability and safety.

-    const cItems = this.classroom?.classroomItems
-    const cLiveCompletion = this.classroom?.aceConfig?.liveCompletion
-    const cFormats = this.classroom?.aceConfig?.codeFormats
-    const cFormatDefault = this.classroom?.aceConfig?.codeFormatDefault
-    const cLevelChat = this.classroom?.aceConfig?.levelChat
     return {
       showGoogleClassroom: me.showGoogleClassroom(),
-      newClassName: this.classroom?.name || '',
-      newProgrammingLanguage: this.classroom?.aceConfig?.language || 'python',
-      newLiveCompletion: typeof cLiveCompletion === 'undefined' ? true : cLiveCompletion,
-      newClassroomItems: typeof cItems === 'undefined' ? true : cItems,
+      newClassName: this.classroom?.name ?? '',
+      newProgrammingLanguage: this.classroom?.aceConfig?.language ?? 'python',
+      newLiveCompletion: this.classroom?.aceConfig?.liveCompletion ?? true,
+      newClassroomItems: this.classroom?.classroomItems ?? true,

Line range hint 284-432: Refactor saveClass method for better maintainability

The saveClass method is handling too many responsibilities (validation, updates, API calls, navigation). Consider breaking it down into smaller, focused me 10000 thods.

Suggested structure:

async saveClass() {
  try {
    this.saving = true;
    await this.validateForm();
    const updates = await this.prepareUpdates();
    const savedClassroom = await this.saveClassroomData(updates);
    await this.handlePostSave(savedClassroom);
    this.handleNavigation();
  } catch (error) {
    this.handleError(error);
  } finally {
    this.saving = false;
  }
}

371-375: Enhance error message specificity

The error handling could be more informative by providing specific error messages based on the error type.

-          console.error('failed to create classroom', err)
-          this.errMsg = err?.message || 'Failed to create classroom'
+          console.error('Failed to create classroom:', err)
+          this.errMsg = this.getErrorMessage(err)

Add a helper method:

getErrorMessage(error) {
  if (error.response?.status === 409) {
    return 'A classroom with this name already exists'
  }
  if (error.response?.status === 403) {
    return 'You do not have permission to create a classroom'
  }
  return error?.message || 'Failed to create classroom'
}

967-989: Improve error message accessibility

The error message display should include proper ARIA attributes for better accessibility.

               <span
                 v-if="errMsg"
                 class="error-msg error"
+                role="alert"
+                aria-live="polite"
               >
                 {{ errMsg }}
               </span>

1175-1189: Enhance style maintainability with CSS custom properties

Consider using CSS custom properties for commonly used values to improve maintainability.

+:root {
+  --spacing-xs: 5px;
+  --spacing-sm: 15px;
+  --error-color: red;
+}

 .submit-button {
   display: flex;
   align-items: center;
   justify-content: center;
   flex-direction: column;

   .saving-text {
     @include font-p-4-paragraph-smallest-gray;
-    margin-top: 5px;
+    margin-top: var(--spacing-xs);
   }

   .error-msg {
-    margin-top: 5px;
+    margin-top: var(--spacing-xs);
   }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 467e0d2 and 34e37ab.

📒 Files selected for processing (9)
  • app/core/store/modules/teacherDashboard.js (1 hunks)
  • app/core/utils.js (2 hunks)
  • app/locale/en.js (5 hunks)
  • app/views/ai-league/QuestionmarkView.vue (2 hunks)
  • ozaria/site/components/teacher-dashboard/BaseTeacherDashboard/index.vue (1 hunks)
  • ozaria/site/components/teacher-dashboard/modals/ModalEditClass.vue (19 hunks)
  • ozaria/site/components/teacher-dashboard/modals/common/ButtonImportClassroom.vue (2 hunks)
  • ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/ClassStartEndDateComponent.vue (1 hunks)
  • ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/CourseCodeLanguageFormatComponent.vue (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/core/store/modules/teacherDashboard.js
  • app/locale/en.js
  • app/views/ai-league/QuestionmarkView.vue
  • ozaria/site/components/teacher-dashboard/modals/common/ButtonImportClassroom.vue
  • ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/ClassStartEndDateComponent.vue
🔇 Additional comments (6)
ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/CourseCodeLanguageFormatComponent.vue (1)

1-1: Verify saving indicator implementation

The PR objectives mention adding a saving indicator, but it's not implemented in this component. Please verify if this should be implemented here or in a parent component.

ozaria/site/components/teacher-dashboard/BaseTeacherDashboard/index.vue (3)

Line range hint 4-7: Verify error handling for classroom operations.

The component handles classroom operations (create/edit) but doesn't appear to have explicit error handling for failed operations. This is particularly important for the AI High School feature mentioned in the PR objectives.

#!/bin/bash
# Search for error handling patterns in classroom-related operations
rg -A 5 "catch|error|fail" ozaria/site/components/teacher-dashboard/modals/ModalEditClass

Consider adding error handling:

 handleCreatedClass () {
+  try {
     if (this.activeClassrooms.length === 1) {
       this.createdFirstClass = true
     }
+  } catch (error) {
+    console.error('Failed to handle class creation:', error)
+    // Add user notification
+  }
 }

Also applies to: 12-24


63-63: Consider potential side effects of removing Classroom model initialization.

While the intent to minimize Backbone model usage in Vue code is valid, changing newClassroom to an empty object could have implications. The component passes this object to ModalEditClass which might expect certain default properties or methods.

Let's verify the dependencies:

Consider these alternatives:

  1. Create a plain object with required default properties: { ownerID: me.id }
  2. Create a Vue-specific classroom factory function
  3. Document the expected shape of the classroom object for maintainability

Line range hint 8-24: Verify component imports for AI High School feature.

The PR objectives mention adding an AI High School feature, but the necessary component imports might be missing.

Consider organizing imports by feature:

 import Panel from '../Panel/index.vue'
+// Core modals
 import ModalAssignContent from '../modals/ModalAssignContent/index'
 import ModalAddStudents from '../modals/ModalAddStudents'
 import ModalRemoveStudents from '../modals/ModalRemoveStudents'
 import ModalOnboardingVideo from '../modals/ModalOnboardingVideo'
 import ModalEditClass from '../modals/ModalEditClass'
+// Feature: AI High School
+// TODO: Import AI High School specific components
app/core/utils.js (2)

372-372: LGTM: Addition of HACKSTACK to free courses

The addition of HACKSTACK to freeCocoCourseIDs is consistent with the course configurations and aligns with the AI High School feature mentioned in the PR objectives.


1665-1665: LGTM: Simplified text-code format name

The removal of the "default" suffix from the text-code format name aligns with the PR's goal of minimizing text in the modal and moving such information to tooltips.

Comment on lines +237 to +244
data () {
return {
newInitialFreeCourses: [utils.courseIDs.INTRODUCTION_TO_COMPUTER_SCIENCE],
newProgrammingLanguage: this.codeLanguage || 'python',
newCodeFormats: this.codeFormats,
newCodeFormatDefault: this.codeFormatDefault,
}
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for newInitialFreeCourses

The initial value of newInitialFreeCourses is hardcoded without validation against available courses.

  data () {
    return {
-     newInitialFreeCourses: [utils.courseIDs.INTRODUCTION_TO_COMPUTER_SCIENCE],
+     newInitialFreeCourses: this.validateInitialCourses([utils.courseIDs.INTRODUCTION_TO_COMPUTER_SCIENCE]),
      newProgrammingLanguage: this.codeLanguage || 'python',
      newCodeFormats: this.codeFormats,
      newCodeFormatDefault: this.codeFormatDefault,
    }
  },

Add validation method:

methods: {
  validateInitialCourses(courses) {
    return courses.filter(courseId => 
      this.courses.some(c => c._id === courseId)
    )
  }
}

Comment on lines +369 to +380
::v-deep .plabel {
height: 16px;
width: 16px;
border-radius: 16px;

position: relative;
top: -8px;

.text-wrapper {
font-size: 13px;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Update deprecated deep selector syntax

The ::v-deep selector syntax is deprecated. Use :deep() instead.

-  ::v-deep .plabel {
+  :deep(.plabel) {
    height: 16px;
    width: 16px;
    border-radius: 16px;

    position: relative;
    top: -8px;

    .text-wrapper {
      font-size: 13px;
    }
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
::v-deep .plabel {
height: 16px;
width: 16px;
border-radius: 16px;
position: relative;
top: -8px;
.text-wrapper {
font-size: 13px;
}
}
:deep(.plabel) {
height: 16px;
width: 16px;
border-radius: 16px;
position: relative;
top: -8px;
.text-wrapper {
font-size: 13px;
}
}

class="form-control"
:class="{ 'placeholder-text': !newProgrammingLanguage }"
name="classLanguage"
:disabled="availableLanguages?.filter(l => !l.disabled).length === 0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null check for availableLanguages

The optional chaining operator is used but the filter operation could still throw if availableLanguages is null.

-        :disabled="availableLanguages?.filter(l => !l.disabled).length === 0"
+        :disabled="!availableLanguages || availableLanguages.filter(l => !l.disabled).length === 0"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
:disabled="availableLanguages?.filter(l => !l.disabled).length === 0"
:disabled="!availableLanguages || availableLanguages.filter(l => !l.disabled).length === 0"

Comment on lines +330 to +341
newProgrammingLanguage (newVal) {
this.$emit('programmingLanguageUpdated', newVal)
},
newInitialFreeCourses (newVal) {
this.$emit('initialFreeCoursesUpdated', newVal)
},
newCodeFormats (newVal) {
this.$emit('codeFormatsUpdated', newVal)
},
newCodeFormatDefault (newVal) {
this.$emit('codeFormatDefaultUpdated', newVal)
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using deep watch for complex data

Watch handlers for arrays should use deep watch to detect nested changes.

  watch: {
    newProgrammingLanguage (newVal) {
      this.$emit('programmingLanguageUpdated', newVal)
    },
-   newInitialFreeCourses (newVal) {
+   newInitialFreeCourses: {
+     handler(newVal) {
      this.$emit('initialFreeCoursesUpdated', newVal)
+     },
+     deep: true
+   },
-   newCodeFormats (newVal) {
+   newCodeFormats: {
+     handler(newVal) {
      this.$emit('codeFormatsUpdated', newVal)
+     },
+     deep: true
+   },
    newCodeFormatDefault (newVal) {
      this.$emit('codeFormatDefaultUpdated', newVal)
    },
  },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
newProgrammingLanguage (newVal) {
this.$emit('programmingLanguageUpdated', newVal)
},
newInitialFreeCourses (newVal) {
this.$emit('initialFreeCoursesUpdated', newVal)
},
newCodeFormats (newVal) {
this.$emit('codeFormatsUpdated', newVal)
},
newCodeFormatDefault (newVal) {
this.$emit('codeFormatDefaultUpdated', newVal)
},
newProgrammingLanguage (newVal) {
this.$emit('programmingLanguageUpdated', newVal)
},
newInitialFreeCourses: {
handler(newVal) {
this.$emit('initialFreeCoursesUpdated', newVal)
},
deep: true
},
newCodeFormats: {
handler(newVal) {
this.$emit('codeFormatsUpdated', newVal)
},
deep: true
},
newCodeFormatDefault (newVal) {
this.$emit('codeFormatDefaultUpdated', newVal)
},

Comment on lines 36 to 38
required: true,
default: () => {}
default: () => {},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using a more robust default value for the classroom prop

The empty object default could lead to unexpected behavior when accessing nested properties. Consider either:

  1. Making the prop required without a default, or
  2. Providing a complete default object structure matching the expected classroom shape.
-    classroom: {
-      type: Object,
-      required: true,
-      default: () => {},
-    },
+    classroom: {
+      type: Object,
+      required: true
+    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
required: true,
default: () => {}
default: () => {},
},
required: true,
},

@mrfinch mrfinch merged commit 0826b8a into master Nov 12, 2024
2 checks passed
@mrfinch mrfinch deleted the saurabh/classroom-modal branch November 12, 2024 12:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
0