8000 changes to codeninja club/camp creation - show option of esports by mrfinch · Pull Request #7911 · codecombat/codecombat · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

changes to codeninja club/camp creation - show option of esports #7911

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 5 commits into from
Jan 29, 2025

Conversation

mrfinch
Copy link
Contributor
@mrfinch mrfinch commented Jan 24, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for Code Ninja classroom types.
    • Enhanced classroom settings with flexible age range and class type selections.
    • Introduced new validation for club and camp class durations.
    • Conditional navigation for users with specific roles.
    • Added visibility control for the "By Student" tab based on user type.
  • Improvements

    • Refined teacher dashboard components to better handle club and camp classroom types.
    • Updated modal interfaces to provide more context-aware editing experiences.
    • Introduced a sleep function to manage asynchronous operations effectively.
    • Enhanced control flow for displaying UI elements based on user roles.
  • User Interface

    • Improved code language and format selection for different classroom types.
    • Added more descriptive titles and options in classroom management modals.
    • Enhanced user interface with new links and tabs based on user identification.

Copy link
Contributor
coderabbitai bot commented Jan 24, 2025

Walkthrough

This pull request introduces enhancements to the classroom management system, focusing on Code Ninja classroom types. The changes span multiple files, adding new methods to the Classroom model, creating a new mixin for club and camp functionality, and updating various components to support more granular handling of classroom types. The modifications enable better differentiation and validation of club and camp classroom settings, with specific attention to age ranges, class type selections, and duration constraints.

Changes

File Change Summary
app/models/Classroom.js Added two static methods: codeNinjaClassroomTypes() and isCodeNinjaClubCamp() to define and validate Code Ninja classroom types.
app/templates/courses/classroom-settings-modal.pug Introduced ageRange mixin, removed specific class type options for Code Ninja users.
ozaria/site/components/teacher-dashboard/... Added clubCampMixin to multiple components, updated modal edit class components with new club/camp logic.
ozaria/site/components/teacher-dashboard/modals/ModalEditClass.vue Updated title display, club types, and added validation for class duration based on club/camp type.
ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/CourseCodeLanguageFormatComponent.vue Modified visibility of code language and format options for specific club types.
app/core/api/background-job.js Introduced pollTillResult function for polling job status and updated module exports.
app/core/store/modules/apiClient.js Made fetchLicenseStats asynchronous, updated error handling with try-catch, and integrated job polling.
app/lib/common-utils.js Added sleep function to introduce delays in asynchronous operations.
app/components/common/Navigation.vue Updated homeLink computed property to include a new navigation path for Code Ninja API clients.
app/views/api/components/ApiData.vue Introduced conditional rendering for "By Student" tab based on user role and modified lifecycle logic.
app/models/User.js Added isGeccClient() method to check if a user is associated with a specific client.

Sequence Diagram

sequenceDiagram
    participant User
    participant ModalEditClass
    participant Classroom
    participant ClubCampMixin

    User->>ModalEditClass: Select Classroom Type
    ModalEditClass->>Classroom: Get CodeNinja Classroom Types
    Classroom-->>ModalEditClass: Return Classroom Types
    ModalEditClass->>ClubCampMixin: Validate Classroom Type
    ClubCampMixin->>ModalEditClass: Validate Duration
    ModalEditClass->>User: Display Validation Results
Loading

Possibly related PRs

  • Curriculum sidebar promotion #7593: The changes in the main PR regarding the Classroom class and its methods are related to the modifications in the BaseMyClasses component, which now includes logic for handling the creation of students in classrooms of type 'camp-esports'.
  • classroom modal changes  #7799: The updates in the main PR about classroom types and the addition of methods in the Classroom class are relevant to the changes in the BaseTeacherDashboard component, which now allows for the creation of students in 'camp-esports' classrooms.
  • allow creation of camp-esports for cn #7884: The main PR's focus on enabling the creation of 'camp-esports' classrooms directly relates to the changes made in the ModalEditClass component, which now includes options for selecting 'Esports Camp' as a classroom type.

Suggested reviewers

  • adamkecskes

Poem

🐰 Code Ninja's classroom dance,
Mixins and methods enhance our stance,
Types sorted with rabbit-like grace,
Clubs and camps find their space,
A coding adventure, let's advance! 🚀

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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: 0

🧹 Nitpick comments (6)
ozaria/site/components/teacher-dashboard/mixins/clubCampMixin.js (1)

5-7: Avoid duplicated logic with Classroom model.
Currently, this method duplicates the logic found in Classroom.isCodeNinjaClubCamp(). Since the model already offers the same check, consider calling classroom.isCodeNinjaClubCamp() directly or referencing the model’s method in one place to reduce maintenance overhead.

- isCodeNinjaClubCamp (classroom) {
-   return Classroom.codeNinjaClassroomTypes().map(type => type.id).includes(classroom?.type)
- },
+ isCodeNinjaClubCamp (classroom) {
+   return classroom?.isCodeNinjaClubCamp?.() ?? false
+ },
ozaria/site/components/teacher-dashboard/BaseMyClasses/index.vue (1)

248-248: Prop naming alignment.
:as-club="isCodeNinjaClubCamp(editClassroomObject)" clearly communicates intent. Ensure future references consistently use as-club to avoid confusion with “camp” scenarios.

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

258-258: Possible duplication in hideCodeLanguageAndFormat.
You’re filtering club/camp types in multiple places (model, mixin, and now here). Consider a single, reusable approach (e.g., calling Classroom.codeNinjaClassroomTypes() or a helper) to keep these lists synced.

app/models/Classroom.js (1)

495-504: Export these new classroom types from a central config.
Defining them inline is fine for now, but if you foresee expansions or shared usage across modules, consider extracting them into a central constants file for easier maintenance.

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

127-127: Consider localizing the label.
Appending " (As Club / Camp)" works but may bypass your i18n/l10n best practices. Consider defining or reusing an i18n token for better international support.


297-300: Date range validation improvements.
The new restrictions for camp (5 days) and club (14 weeks) are a clear, logical approach. However, consider:
• Timezone edge cases (e.g., partial boundary day).
• Aligning error messages with your i18n framework.
• Whether you want inclusive vs. exclusive bounds (14 weeks plus 1 day?).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20d1396 and ed8bfe6.

📒 Files selected for processing (7)
  • app/models/Classroom.js (1 hunks)
  • app/templates/courses/classroom-settings-modal.pug (0 hunks)
  • ozaria/site/components/teacher-dashboard/BaseMyClasses/index.vue (3 hunks)
  • ozaria/site/components/teacher-dashboard/BaseTeacherDashboard/index.vue (3 hunks)
  • ozaria/site/components/teacher-dashboard/mixins/clubCampMixin.js (1 hunks)
  • ozaria/site/components/teacher-dashboard/modals/ModalEditClass.vue (3 hunks)
  • ozaria/site/components/teacher-dashboard/modals/modal-edit-class-components/CourseCodeLanguageFormatComponent.vue (2 hunks)
💤 Files with no reviewable changes (1)
  • app/templates/courses/classroom-settings-modal.pug
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Node.js CI (18.10.0)
🔇 Additional comments (9)
ozaria/site/components/teacher-dashboard/BaseMyClasses/index.vue (1)

Line range hint 12-28: Great addition of the club/camp mixin.
Importing clubCampMixin and mixing it in alongside sortClassroomMixin helps keep this component lean. The approach is clean and organized.

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

158-164: Good conditional rendering for single code format.
Allowing a disabled <input> when only one code format is available streamlines the UX. No issues found.


166-166: Ensure accessibility for dropdown.
While this conditional fallback is great, ensure the <select> has appropriate labeling or ARIA attributes for assistive technologies (if needed by your product’s accessibility requirements).

app/models/Classroom.js (1)

506-508: Method naming synergy.
isCodeNinjaClubCamp() is consistent with the new mixin’s logic, but having them in both places can cause confusion. Make sure to unify usage where possible.

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

24-24: Import looks good.
Importing the clubCampMixin here is well-structured and logically separated.


46-46: Consistent component registration.
Registering TopBanner follows the existing pattern. No issues found.


48-48: Proper use of mixins.
Including clubCampMixin in the mixins array is a clean approach for reusing club/camp-related logic across components.


463-463: Verify isClub usage.
Ensure that isClub is defined and properly handles all relevant classroom types. If it references the mixin, confirm it correctly identifies club vs. camp or other custom types.

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

171-171: Good extraction of club types.
Referencing Classroom.codeNinjaClassroomTypes() centralizes the club/camp type definitions, which aids maintainability and consistency.

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
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed8bfe6 and 465f31a.

📒 Files selected for processing (5)
  • app/core/api/background-job.js (2 hunks)
  • app/core/store/modules/apiClient.js (2 hunks)
  • app/lib/common-utils.js (1 hunks)
  • ozaria/site/components/teacher-dashboard/BaseMyClasses/index.vue (4 hunks)
  • ozaria/site/components/teacher-dashboard/BaseTeacherDashboard/index.vue (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ozaria/site/components/teacher-dashboard/BaseTeacherDashboard/index.vue
🔇 Additional comments (5)
ozaria/site/components/teacher-dashboard/BaseMyClasses/index.vue (3)

12-12: LGTM! Verify the clubCampMixin implementation.

The mixin is properly imported and integrated. The ordering with sortClassroomMixin first is correct.

Let's verify the mixin implementation:

Also applies to: 27-28

✅ Verification successful

Verified: clubCampMixin implementation is correct and properly used

The mixin provides a clean implementation of isCodeNinjaClubCamp method and is correctly integrated in both BaseMyClasses and BaseTeacherDashboard components. The implementation aligns with the Code Ninja classroom types functionality.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the clubCampMixin implementation and its usage

# Check the mixin implementation
echo "Checking clubCampMixin implementation..."
rg -p "clubCampMixin" --type vue --type js

# Check for isCodeNinjaClubCamp method usage
echo "Checking isCodeNinjaClubCamp usage..."
ast-grep --pattern 'isCodeNinjaClubCamp($_)'

Length of output: 293


Script:

#!/bin/bash
# Search for clubCampMixin implementation and usage
echo "=== Searching for clubCampMixin files ==="
fd -e js -e vue clubCampMixin

echo "=== Checking content of clubCampMixin ==="
fd -e js -e vue clubCampMixin -x cat {}

echo "=== Searching for references to clubCampMixin ==="
rg "clubCampMixin" -g "*.{js,vue}"

echo "=== Checking for isCodeNinjaClubCamp method ==="
rg "isCodeNinjaClubCamp" -g "*.{js,vue}"

Length of output: 1539


248-248: LGTM! Verify the as-club prop handling.

The :as-club prop is properly bound to determine if the classroom is a Code Ninja club/camp.

Let's verify the prop handling in ModalEditClass:

✅ Verification successful

Prop implementation verified and working as expected

The as-club prop is properly defined in ModalEditClass and used consistently for:

  • Conditional validation requirements
  • Club-specific UI elements
  • Proper prop forwarding to child components
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the as-club prop implementation in ModalEditClass

# Check ModalEditClass component for as-club prop definition
echo "Checking as-club prop definition..."
rg -p "as-club|asClub" --type vue

# Check for conditional rendering based on as-club
echo "Checking conditional rendering..."
ast-grep --pattern 'v-if="$_club"'

Length of output: 258


Script:

#!/bin/bash
# Description: Verify the as-club prop implementation with improved patterns

# Add .vue as a type and search for prop
echo "Checking as-club prop definition..."
rg -p --type-add 'vue:*.vue' -t vue '(as-club|asClub)' 

# Check for prop definition in component options
echo "Checking prop in component options..."
ast-grep --pattern 'props: {
  $$$
  asClub: $_,
  $$$
}'

# Check template conditions
echo "Checking template conditions..."
ast-grep --pattern '<template>
  $$$
  <$_
    $$$
    :as-club="$_"
    $$$
  />
  $$$
</template>'

Length of output: 1676


133-134: Verify the impact of disabling student creation.

The method now always returns false, which disables student creation functionality entirely. This is a significant change that might affect user workflows.

Let's verify the impact:

Consider adding a code comment explaining why student creation is disabled:

     showCreateStudents (_classroom) {
+      // Student creation disabled for Code Ninja club/camp classrooms
       return false
     },
✅ Verification successful

Verified: Student creation is intentionally disabled for Code Ninja classrooms

The change is part of the Code Ninja club/camp classroom implementation, where automatic student creation is disabled. The component uses clubCampMixin and the related API endpoint (createStudentsForCodeNinja) confirms this is specific to Code Ninja classrooms.

Consider adding a code comment to document this intentional restriction:

    showCreateStudents (_classroom) {
+     // Student creation is disabled for Code Ninja club/camp classrooms
      return false
    },
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage and potential impact of showCreateStudents

# Check for components using modal-add-students
echo "Checking modal-add-students usage..."
rg -p "modal-add-students" --type vue

# Check for references to showCreateStudents
echo "Checking showCreateStudents usage..."
rg -p "showCreateStudents|createStudents" --type vue

Length of output: 314


Script:

#!/bin/bash
# Search for showCreateStudents method usage and related UI elements
echo "Checking showCreateStudents usage..."
rg -p "showCreateStudents" -A 2 -B 2

echo "\nChecking create students related UI elements..."
rg -p "create-students|createStudents|add-students" -A 2 -B 2

echo "\nChecking the original file..."
cat "ozaria/site/components/teacher-dashboard/BaseMyClasses/index.vue"

Length of output: 16368

app/lib/common-utils.js (1)

6-8: LGTM! Clean Promise-based sleep implementation.

The sleep function follows the standard pattern for Promise-based delays and is properly implemented.

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

60-62: Verify the background job API contract.

Let's verify the structure of the background job response to ensure consistent handling across the codebase.

✅ Verification successful

Background job API contract is consistent ✓

The implementation in apiClient.js correctly follows the established background job contract used across the codebase, with proper job creation, status handling, and result processing.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for all background job API usages to verify consistent contract handling

# Look for job creation patterns
echo "Checking job creation patterns:"
rg -A 2 "createJob|create as createJob" --type js

# Look for job status access patterns
echo "\nChecking job status access patterns:"
rg -B 2 -A 2 "job\.status|job\.job|job\.output|job\.message" --type js

Length of output: 4526

Comment on lines 24 to 40
8000
async function pollTillResult (jobId, other, options = {}) {
let url = `/db/background-jobs/${jobId}`
if (other) {
url = '/' + other + url
}
let job = await fetchJson(url, options)
while (job.status !== 'completed' && job.status !== 'failed') {
await sleep(5000)
job = await fetchJson(url, options)
}
if (job.status === 'completed') {
return JSON.parse(job.output)
}
if (job.status === 'failed') {
throw new Error(job.message)
}
}
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 timeout and backoff mechanism to prevent excessive polling.

While the implementation is functional, it could be improved to handle edge cases better:

-async function pollTillResult (jobId, other, options = {}) {
+async function pollTillResult (jobId, other, options = {}) {
+  const MAX_ATTEMPTS = 60; // 5 minutes with 5s interval
+  const BASE_DELAY = 5000;
+  let attempts = 0;
   let url = `/db/background-jobs/${jobId}`
   if (other) {
     url = '/' + other + url
   }
   let job = await fetchJson(url, options)
-  while (job.status !== 'completed' && job.status !== 'failed') {
-    await sleep(5000)
+  while (job.status !== 'completed' && job.status !== 'failed' && attempts < MAX_ATTEMPTS) {
+    attempts++;
+    // Exponential backoff with max delay of 30 seconds
+    const delay = Math.min(BASE_DELAY * Math.pow(1.1, attempts - 1), 30000);
+    await sleep(delay)
     job = await fetchJson(url, options)
   }
+  if (attempts >= MAX_ATTEMPTS) {
+    throw new Error('Polling timeout exceeded')
+  }
   if (job.status === 'completed') {
-    return JSON.parse(job.output)
+    try {
+      return job.output ? JSON.parse(job.output) : null
+    } catch (e) {
+      throw new Error(`Failed to parse job output: ${e.message}`)
+    }
   }
   if (job.status === 'failed') {
     throw new Error(job.message)
   }
 }
📝 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
async function pollTillResult (jobId, other, options = {}) {
let url = `/db/background-jobs/${jobId}`
if (other) {
url = '/' + other + url
}
let job = await fetchJson(url, options)
while (job.status !== 'completed' && job.status !== 'failed') {
await sleep(5000)
job = await fetchJson(url, options)
}
if (job.status === 'completed') {
return JSON.parse(job.output)
}
if (job.status === 'failed') {
throw new Error(job.message)
}
}
async function pollTillResult (jobId, other, options = {}) {
const MAX_ATTEMPTS = 60; // 5 minutes with 5s interval
const BASE_DELAY = 5000;
let attempts = 0;
let url = `/db/background-jobs/${jobId}`
if (other) {
url = '/' + other + url
}
let job = await fetchJson(url, options)
while (job.status !== 'completed' && job.status !== 'failed' && attempts < MAX_ATTEMPTS) {
attempts++;
// Exponential backoff with max delay of 30 seconds
const delay = Math.min(BASE_DELAY * Math.pow(1.1, attempts - 1), 30000);
await sleep(delay)
job = await fetchJson(url, options)
}
if (attempts >= MAX_ATTEMPTS) {
throw new Error('Polling timeout exceeded')
}
if (job.status === 'completed') {
try {
return job.output ? JSON.parse(job.output) : null
} catch (e) {
throw new Error(`Failed to parse job output: ${e.message}`)
}
}
if (job.status === 'failed') {
throw new Error(job.message)
}
}

Comment on lines 58 to 70
fetchLicenseStats: async ({ commit }, { clientId, startDate, endDate }) => {
commit('toggleLoading', 'byLicense')
return apiclientsApi
.getLicenseStats(clientId, { startDate, endDate })
.then(res => {
if (res) {
commit('addLicenseStats', {
stats: res
})
} else {
throw new Error('Unexpected response from license stats by owner API.')
}
const job = await createJob('api-client-stats', { clientId, startDate, endDate })
try {
const res = await pollTillResult(job?.job)
commit('addLicenseStats', {
stats: res,
})
.catch((e) => noty({ text: 'Fetch license stats failure: ' + e, type: 'error' }))
.finally(() => commit('toggleLoading', 'byLicense'))
} catch (err) {
noty({ text: 'Fetch license stats failure: ' + err, type: 'error' })
} finally {
commit('toggleLoading', 'byLicense')
}
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

Improve error handling and loading state management.

The async/await implementation is good, but there are a few improvements needed:

 fetchLicenseStats: async ({ commit }, { clientId, startDate, endDate }) => {
   commit('toggleLoading', 'byLicense')
-  const job = await createJob('api-client-stats', { clientId, startDate, endDate })
   try {
+    const job = await createJob('api-client-stats', { clientId, startDate, endDate })
+    if (!job || !job.job) {
+      throw new Error('Failed to create background job')
+    }
-    const res = await pollTillResult(job?.job)
+    const res = await pollTillResult(job.job)
     commit('addLicenseStats', {
       stats: res,
     })
   } catch (err) {
-    noty({ text: 'Fetch license stats failure: ' + err, type: 'error' })
+    noty({
+      text: `Failed to fetch license stats: ${err.message || 'Unknown error'}`,
+      type: 'error'
+    })
   } finally {
     commit('toggleLoading', 'byLicense')
   }
 },
📝 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
fetchLicenseStats: async ({ commit }, { clientId, startDate, endDate }) => {
commit('toggleLoading', 'byLicense')
return apiclientsApi
.getLicenseStats(clientId, { startDate, endDate })
.then(res => {
if (res) {
commit('addLicenseStats', {
stats: res
})
} else {
throw new Error('Unexpected response from license stats by owner API.')
}
const job = await createJob('api-client-stats', { clientId, startDate, endDate })
try {
const res = await pollTillResult(job?.job)
commit('addLicenseStats', {
stats: res,
})
.catch((e) => noty({ text: 'Fetch license stats failure: ' + e, type: 'error' }))
.finally(() => commit('toggleLoading', 'byLicense'))
} catch (err) {
noty({ text: 'Fetch license stats failure: ' + err, type: 'error' })
} finally {
commit('toggleLoading', 'byLicense')
}
fetchLicenseStats: async ({ commit }, { clientId, startDate, endDate }) => {
commit('toggleLoading', 'byLicense')
try {
const job = await createJob('api-client-stats', { clientId, startDate, endDate })
if (!job || !job.job) {
throw new Error('Failed to create background job')
}
const res = await pollTillResult(job.job)
commit('addLicenseStats', {
stats: res,
})
} catch (err) {
noty({
text: `Failed to fetch license stats: ${err.message || 'Unknown error'}`,
type: 'error'
})
} finally {
commit('toggleLoading', 'byLicense')
}
},

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to d 8000 escribe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
app/views/api/components/ApiData.vue (2)

6-6: Consider simplifying the condition logic.

The negated condition !hideByStudentTab() makes the code slightly harder to read. Consider renaming the method to shouldShowByStudentTab() to avoid the negation.

-      .tab(v-if="!hideByStudentTab()" @click="tab = 'byStudent'" :class="{active: tab === 'byStudent'}") By Student
+      .tab(v-if="shouldShowByStudentTab()" @click="tab = 'byStudent'" :class="{active: tab === 'byStudent'}") By Student

9-10: Enhance link accessibility and security.

A few improvements needed:

  1. The text before the link should use i18n
  2. The external link needs security attributes
-      p(class="link-info") Access the new dashboard with graphs
-        a(href="/partner-dashboard?fromOld=1" target="_blank")  {{ $t('general.here') }}
+      p(class="link-info") {{ $t('dashboard.access_new_dashboard') }}
+        a(href="/partner-dashboard?fromOld=1" target="_blank" rel="noopener noreferrer")  {{ $t('general.here') }}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 465f31a and 72e1cea.

📒 Files selected for processing (2)
  • app/components/common/Navigation.vue (1 hunks)
  • app/views/api/components/ApiData.vue (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Node.js CI (18.10.0)
🔇 Additional comments (4)
app/components/common/Navigation.vue (2)

111-111: LGTM! The change is consistent with the account dropdown menu.

The new navigation path to '/api-dashboard' aligns with the existing API dashboard link in the account dropdown menu, which is shown when me.isAPIClient() is true.

Also applies to: 392-393


111-111: Verify the precedence of navigation paths for Code Ninja users.

The new condition for Code Ninja API clients is placed before the condition for Code Ninja teachers. Please verify that this order aligns with the expected user experience, as it means API clients will be directed to the API dashboard even if they are also teachers.

✅ Verification successful

The routing precedence for Code Ninja users is correctly implemented.

The order of conditions in the navigation logic is appropriate as the roles (student/API client/teacher) are mutually exclusive, and each Code Ninja user will be directed to their respective dashboard based on their specific role.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other Code Ninja-related routing logic
rg -A 5 "isCodeNinja\(\)" --type vue --type js

Length of output: 76


Script:

#!/bin/bash
# Search for Code Ninja and API client related logic
echo "=== Searching for isCodeNinja() calls ==="
rg "isCodeNinja\(\)"

echo -e "\n=== Searching for isAPIClient() calls ==="
rg "isAPIClient\(\)"

echo -e "\n=== Searching for API dashboard routes ==="
rg "/api-dashboard"

Length of output: 6609

app/views/api/components/ApiData.vue (2)

202-203: LGTM!

The xTicks configuration is properly set to disable tick marks on the x-axis, which is appropriate for categorical data like age ranges.


225-225: LGTM!

The initialization of the tab based on client type is correct and consistent with the component's logic.

Comment on lines 209 to 213
isGeccClient () {
this.myId = me.get('_id')
const geccId = '61e7e20658f1020024bd8cf7'
return this.myId === geccId
},
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 client identification logic.

Several issues with the current implementation:

  1. The GECC client ID is hardcoded
  2. The method has a side effect of setting myId
  3. No type checking or error handling for the ID comparison
+    // Move to constants/configuration
+    const GECC_CLIENT_ID = '61e7e20658f1020024bd8cf7'
+
+    data () {
+      return {
+        tab: 'byMonth',
+        spiedUser: window.serverSession.amActually,
+        myId: me.get('_id')  // Move to data
+      }
+    },
     isGeccClient () {
-      this.myId = me.get('_id')
-      const geccId = '61e7e20658f1020024bd8cf7'
-      return this.myId === geccId
+      return this.myId === GECC_CLIENT_ID
     },

Committable suggestion skipped: line range outside the PR's diff.

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

♻️ Duplicate comments (1)
app/core/api/background-job.js (1)

24-47: 🛠️ Refactor suggestion

Enhance polling mechanism with better error handling and backoff strategy.

The current implementation could be improved in several areas:

  1. Add exponential backoff to prevent overwhelming the server
  2. Add robust error handling for JSON parsing
  3. Improve timeout error message with more context

Apply these improvements:

 async function pollTillResult (jobId, other, options = {}) {
   let url = `/db/background-jobs/${jobId}`
   if (other) {
     url = '/' + other + url
   }
   let job = await fetchJson(url, options)
-  const MAX_ATTEMPTS = 30
-  const DELAY_MS = 5000
+  const MAX_ATTEMPTS = 60  // 5 minutes with initial 5s delay
+  const BASE_DELAY = 5000
   let attempts = 0
   while (job.status !== 'completed' && job.status !== 'failed' && attempts < MAX_ATTEMPTS) {
-    await sleep(DELAY_MS)
+    attempts++
+    // Exponential backoff with max delay of 30 seconds
+    const delay = Math.min(BASE_DELAY * Math.pow(1.1, attempts - 1), 30000)
+    await sleep(delay)
     job = await fetchJson(url, options)
-    attempts++
   }
   if (job.status === 'completed') {
-    return JSON.parse(job.output)
+    try {
+      return job.output ? JSON.parse(job.output) : null
+    } catch (e) {
+      throw new Error(`Failed to parse job output: ${e.message}`)
+    }
   }
   if (job.status === 'failed') {
     throw new Error(job.message)
   }
-  if (attempts === MAX_ATTEMPTS) {
-    throw new Error('Failed to load the results')
+  if (attempts >= MAX_ATTEMPTS) {
+    throw new Error(`Polling timeout exceeded after ${MAX_ATTEMPTS} attempts for job ${jobId}`)
   }
 }
🧹 Nitpick comments (4)
app/views/api/components/ApiData.vue (4)

6-6: Convert method to computed property for better performance.

The hideByStudentTab() method is used in a template conditional and would be better implemented as a computed property since it depends on user state.

-      .tab(v-if="!hideByStudentTab()" @click="tab = 'byStudent'" :class="{active: tab === 'byStudent'}") By Student
+      .tab(v-if="showByStudentTab" @click="tab = 'byStudent'" :class="{active: tab === 'byStudent'}") By Student

 // In computed section:
+    showByStudentTab () {
+      return me.isGeccClient()
+    }

9-10: Enhance accessibility of the dashboard link.

The link to the new dashboard lacks accessibility attributes and could be more descriptive.

-      p(class="link-info") Access the new dashboard with graphs
-        a(href="/partner-dashboard?fromOld=1" target="_blank")  {{ $t('general.here') }}
+      p(class="link-info") 
+        a(href="/partner-dashboard?fromOld=1" 
+          target="_blank"
+          rel="noopener noreferrer"
+          aria-label="Access the new dashboard with detailed graphs in a new tab") 
+          | Access the new dashboard with graphs {{ $t('general.here') }}

186-193: Simplify watch handler using object shorthand.

The watch block can be simplified using object shorthand syntax and early return.

   watch: {
-    clientId: function (id) {
-      if (id !== '') {
-        this.fetchTeachers(id)
-        this.fetchPrepaids({ teacherId: this.myId, clientId: id })
-        this.fetchLicenseStats({ clientId: id })
-      }
-    },
+    clientId (id) {
+      if (!id) return
+      this.fetchTeachers(id)
+      this.fetchPrepaids({ teacherId: this.myId, clientId: id })
+      this.fetchLicenseStats({ clientId: id })
+    },
   },

200-203: Remove commented code.

The commented code about play time stats should be removed if it's no longer needed. If it might be needed in the future, add a TODO comment explaining why it's commented out.

     this.fetchClientId()
-    // current play time for apiclient is the total time of all students so i think
-    // we doesn't need it now
-    /* this.fetchPlayTimeStats() */
+    // TODO: Implement individual student play time tracking instead of total time
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72e1cea and 1338e45.

📒 Files selected for processing (4)
  • app/core/api/background-job.js (2 hunks)
  • app/core/store/modules/apiClient.js (2 hunks)
  • app/models/User.js (1 hunks)
  • app/views/api/components/ApiData.vue (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/core/store/modules/apiClient.js
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Node.js CI (18.10.0)
🔇 Additional comments (4)
app/views/api/components/ApiData.vue (1)

227-229: Convert method to computed property.

This method should be a computed property as previously suggested in the template section.

app/core/api/background-job.js (3)

49-53: LGTM!

The module exports are clean and follow modern JavaScript conventions.


24-47: Verify integration with consuming code.

Let's ensure the new pollTillResult function is properly integrated with its consumers.

✅ Verification successful

Integration verification completed successfully

The pollTillResult function is properly integrated and centrally used for background job polling, with no duplicate implementations found in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of pollTillResult
rg "pollTillResult" --type js

# Check for similar polling patterns that might need to be cons
A93C
olidated
rg -A 5 "while.*status.*!==.*completed" --type js

Length of output: 823


Line range hint 16-19: Consider addressing the TODO comment.

The comment suggests moving the URL construction logic into fetchJson. This refactoring would improve code maintainability by centralizing URL handling logic.

Let's check if this URL construction pattern is used elsewhere:

✅ Verification successful

The TODO comment highlights a valid opportunity for improvement

The URL construction pattern url = '/' + other + url appears in multiple API files. Moving this logic to fetchJson would:

  • Centralize URL manipulation alongside existing URL processing logic
  • Reduce code duplication across API modules
  • Make URL construction more maintainable

Consider adding an option similar to callOz in fetchJson to handle this common URL prefix pattern.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for similar URL construction patterns
rg -g '*.js' "url = '/' \+ .* \+ url"

Length of output: 276


Script:

#!/bin/bash
# Check fetchJson implementation and its imports
ast-grep --pattern 'function fetchJson'
rg -g '*.js' "import.*fetchJson"

Length of output: 1533


Script:

#!/bin/bash
# Find fetch-json implementation
fd fetch-json.js
cat app/core/api/fetch-json.js

Length of output: 2453

Comment on lines +1479 to +1482
isGeccClient () {
const GECC_ID = '61e7e20658f1020024bd8cf7'
return this.get('_id') === GECC_ID
}
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

Improve the GECC client identification implementation.

The current implementation has several issues that should be addressed:

+    // Constants
+    static GECC_CLIENT_ID = '61e7e20658f1020024bd8cf7'
+
+    /**
+     * Checks if the current user is a GECC client
+     * @returns {boolean} True if user is GECC client, false otherwise
+     */
     isGeccClient () {
-      const GECC_ID = '61e7e20658f1020024bd8cf7'
-      return this.get('_id') === GECC_ID
+      const userId = this.get('_id')
+      if (!userId) return false
+      return userId === User.GECC_CLIENT_ID
     }

The refactoring:

  1. Moves the client ID to a static class constant
  2. Adds JSDoc documentation
  3. Adds null check for user ID
📝 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
isGeccClient () {
const GECC_ID = '61e7e20658f1020024bd8cf7'
return this.get('_id') === GECC_ID
}
// Constants
static GECC_CLIENT_ID = '61e7e20658f1020024bd8cf7'
/**
* Checks if the current user is a GECC client
* @returns {boolean} True if user is GECC client, false otherwise
*/
isGeccClient () {
const userId = this.get('_id')
if (!userId) return false
return userId === User.GECC_CLIENT_ID
}

@mrfinch mrfinch merged commit dd290a4 into master Jan 29, 2025
3 checks passed
@mrfinch mrfinch deleted the saurabh/cn-client branch January 29, 2025 09:41
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.

1 participant
0