-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
classroom modal changes #7799
Conversation
WalkthroughThe pull request introduces several enhancements across multiple components and modules. A new getter method, Changes
Possibly related PRs
Poem
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
warning eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options. 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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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 errorsWhen accessing properties of
this.classroom
, if any of them areundefined
, 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 propertiesThe computed properties
activeClassrooms
andallClassrooms
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 methodenableBlocks
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' toggleThe 'More Options' link uses an
<a>
tag without anhref
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 marginsIn the
.edit-class
style, the margin values mix units (px
and0px
). 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
📒 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
:
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
:
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
:
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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
<span class="spl.spr">{{ $t("courses.student_age_range_to") }}</span> | |
<span class="spr">{{ $t("courses.student_age_range_to") }}</span> |
<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" | ||
10000 td> | type="date" | |
class="form-control" | ||
> | ||
</div> |
There was a problem hiding this comment.
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:
- Add aria-labels for screen readers
- Add min/max date validation
- Add validation feedback for invalid date ranges
- 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.
.estimated-date-fields { | ||
display: flex; | ||
flex-direction: row; | ||
justify-content: space-between; | ||
align-items: center; | ||
width: 100%; | ||
|
||
input { | ||
width: 45%; | ||
} | ||
} |
There was a problem hiding this comment.
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.
.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%; | |
} | |
} | |
} |
props: { | ||
classDateStart: { | ||
type: String, | ||
required: false, | ||
default: '', | ||
}, | ||
classDateEnd: { | ||
type: String, | ||
required: false, | ||
default: '', | ||
}, | ||
}, |
There was a problem hiding this comment.
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.
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) | |
}, | |
}, |
data () { | ||
return { | ||
newClassDateStart: this.classDateStart, | ||
newClassDateEnd: this.classDateEnd, | ||
} | ||
}, | ||
watch: { | ||
newClassDateStart (newVal) { | ||
this.$emit('classDateStartUpdated', newVal) | ||
}, | ||
newClassDateEnd (newVal) { | ||
this.$emit('classDateEndUpdated', newVal) | ||
}, | ||
}, |
There was a problem hiding this comment.
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.
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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()
.
const languages = JSON.parse(JSON.stringify(this.codeLanguageObject)) | ||
// ozaria do not have these 2 langs | ||
delete languages.coffeescript | ||
delete languages.lua |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 | ||
} | ||
}, |
There was a problem hiding this comment.
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.
} else { | ||
return this.getCourseInstances(this.classroomId)?.some(ci => ci.courseID === utils.courseIDs.HACKSTACK) && this.getCourseInstances(this.classroomId)?.length === 1 | ||
} |
There was a problem hiding this comment.
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.
} 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 | |
} |
<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" | ||
/> |
There was a problem hiding this comment.
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.
b6c3c78
to
d143288
Compare
There was a problem hiding this 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 logicThe 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 methodThere are a few areas that could be improved in the saveClass method:
- Date validation is duplicated at lines 309-312 and within the asClub condition
- 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 stateThe 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 stylingConsider 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 theenableBlocks
computed propertyThe default value for
newProgrammingLan 10000 guage
is already set in thedata()
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
📒 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.
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: '', | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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: { | 10000|
type: String, | |
required: true | |
}, |
There was a problem hiding this 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
📒 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.
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) | ||
}, |
There was a problem hiding this comment.
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
})
}
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) |
There was a problem hiding this comment.
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: {}, |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i18n?
There was a problem hiding this 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.
…hover modal initially
467e0d2
to
34e37ab
Compare
There was a problem hiding this 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 bindingThe 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 labelThe
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 robustnessThe 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 maintainabilityThe 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 specificityThe 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 accessibilityThe 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 propertiesConsider 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
📒 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:
- Create a plain object with required default properties:
{ ownerID: me.id }
- Create a Vue-specific classroom factory function
- 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.
data () { | ||
return { | ||
newInitialFreeCourses: [utils.courseIDs.INTRODUCTION_TO_COMPUTER_SCIENCE], | ||
newProgrammingLanguage: this.codeLanguage || 'python', | ||
newCodeFormats: this.codeFormats, | ||
newCodeFormatDefault: this.codeFormatDefault, | ||
} | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
)
}
}
::v-deep .plabel { | ||
height: 16px; | ||
width: 16px; | ||
border-radius: 16px; | ||
|
||
position: relative; | ||
top: -8px; | ||
|
||
.text-wrapper { | ||
font-size: 13px; | ||
} | ||
} |
There was a problem hiding this comment.
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.
::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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
:disabled="availableLanguages?.filter(l => !l.disabled).length === 0" | |
:disabled="!availableLanguages || availableLanguages.filter(l => !l.disabled).length === 0" |
newProgrammingLanguage (newVal) { | ||
this.$emit('programmingLanguageUpdated', newVal) | ||
}, | ||
newInitialFreeCourses (newVal) { | ||
this.$emit('initialFreeCoursesUpdated', newVal) | ||
}, | ||
newCodeFormats (newVal) { | ||
this.$emit('codeFormatsUpdated', newVal) | ||
}, | ||
newCodeFormatDefault (newVal) { | ||
this.$emit('codeFormatDefaultUpdated', newVal) | ||
}, |
There was a problem hiding this comment.
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.
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) | |
}, |
required: true, | ||
default: () => {} | ||
default: () => {}, | ||
}, |
There was a problem hiding this comment.
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:
- Making the prop required without a default, or
- 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.
required: true, | |
default: () => {} | |
default: () => {}, | |
}, | |
required: true, | |
}, |
fixes ENG-1349
Summary by CodeRabbit
Summary by CodeRabbit
Release Notes
New Features
Improvements
Bug Fixes