Skip to content
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

refactor: migrate creation and editing process to new element instance structure #4343

Merged
merged 15 commits into from
Oct 31, 2024

Conversation

sjschlapbach
Copy link
Member

@sjschlapbach sjschlapbach commented Oct 30, 2024

Summary by CodeRabbit

  • New Features

    • Introduced CreateLiveQuiz and EditLiveQuiz mutations for managing live quizzes.
    • Added ElementDataInfo fragment for flexible querying of element data.
    • New LiveQuiz and ElementBlock types added to the GraphQL schema.
    • Enhanced functionality for managing quiz elements with new input types and mutations.
    • Added GetUserRunningLiveQuizzes query to retrieve active quizzes for users.
  • Bug Fixes

    • Updated references from session-based to quiz-based terminology across various components and queries.
  • Documentation

    • Updated localization files for improved clarity and consistency in user-facing messages.
  • Chores

    • Removed deprecated session management functionalities and updated related components to focus on live quizzes.

Copy link

coderabbitai bot commented Oct 30, 2024

Warning

Rate limit exceeded

@sjschlapbach has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 26 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 46052b4 and cc7fecf.

📝 Walkthrough
📝 Walkthrough
📝 Walkthrough
📝 Walkthrough
📝 Walkthrough
📝 Walkthrough
📝 Walkthrough
📝 Walkthrough
📝 Walkthrough
📝 Walkthrough

Walkthrough

This pull request introduces significant changes to the GraphQL schema and related components, transitioning from session-based functionality to live quiz management. Key updates include the introduction of new types and mutations for live quizzes, renaming existing session-related entities, and modifying input structures. Additionally, various components and queries have been updated to reflect this shift, enhancing the overall data handling and user interaction within the application.

Changes

File Path Change Summary
packages/graphql/src/graphql/ops/FElementDataInfo.graphql Added new fragment ElementDataInfo for ElementInstance, defining fields for various element data types.
packages/graphql/src/graphql/ops/MCreateLiveQuiz.graphql Renamed mutation from CreateSession to CreateLiveQuiz, updated input type for blocks, and modified response fields.
packages/graphql/src/graphql/ops/MEditLiveQuiz.graphql Renamed mutation from EditSession to EditLiveQuiz, updated input type for blocks, and modified response fields.
packages/graphql/src/ops.schema.json Added new types ElementBlock, LiveQuiz, and several fields to the Session type, reflecting changes for live quizzes.
packages/graphql/src/public/client.json Added entries for CreateLiveQuiz and EditLiveQuiz, removed CreateSession and EditSession.
packages/graphql/src/public/schema.graphql Introduced types and mutations for live quizzes, including createLiveQuiz and editLiveQuiz.
packages/graphql/src/public/server.json Added mutations for creating and editing live quizzes, along with related fragments.
packages/graphql/src/schema/course.ts Updated import paths and ensured integrity of the ICourse interface.
packages/graphql/src/schema/groupActivity.ts Updated import paths for practiceQuizzes.js to practiceQuiz.js.
packages/graphql/src/schema/liveQuiz.ts Introduced new types and interfaces for live quizzes and element blocks.
packages/graphql/src/schema/microLearning.ts Updated import paths for ElementStackRef and IElementStack.
packages/graphql/src/schema/mutation.ts Added mutations for live quizzes, updated existing session mutations to reflect new names and structures.
packages/graphql/src/schema/practiceQuiz.ts Added new input type ElementBlockInput, updated existing types for consistency.
packages/graphql/src/schema/query.ts Updated import paths and modified query fields to reflect the transition to live quizzes.
packages/graphql/src/services/liveQuizzes.ts Introduced functions for managing live quizzes, including creation and retrieval.
packages/graphql/src/services/microLearning.ts Enhanced functions related to micro-learning data management.
packages/graphql/src/services/sessions.ts Removed session management functions, indicating a significant refactor.
packages/types/src/index.ts Added new type definition for BlockInput.
apps/frontend-manage/src/components/sessions/creation/AddStackButton.tsx Modified to support both stack and block types with updated props.
apps/frontend-manage/src/components/sessions/creation/DropElementsStack.tsx Introduced new component for managing drop areas for elements.
apps/frontend-manage/src/components/sessions/creation/PasteSelectionButton.tsx Added component for managing selection of elements in the UI.
apps/frontend-manage/src/components/sessions/creation/StackBlockCreation.tsx Updated prop naming and internal logic for stack indices.
apps/frontend-manage/src/components/sessions/creation/StackCreationStep.tsx Renamed props and adjusted component structure to reflect new naming conventions.
apps/frontend-manage/src/components/sessions/creation/WizardElementList.tsx Introduced new component for managing a list of wizard elements.
apps/frontend-manage/src/components/sessions/creation/WizardLayout.tsx Updated interfaces and prop structures to reflect new focus on live quizzes.
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizAddBlockButton.tsx Removed component due to restructuring.
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizBlockSettingsModal.tsx Introduced new modal component for configuring live quiz block settings.
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizCourseMonitor.tsx Updated types and error handling to reflect new data structures.
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizCreationBlock.tsx Modified to integrate new components and updated prop structures.
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizQuestionsStep.tsx Enhanced type handling and component structure for managing quiz questions.
cypress/cypress/e2e/F-live-quiz-workflow.cy.ts Updated test identifiers to reflect changes in the quiz creation and management process.
cypress/cypress/support/commands.ts Introduced custom commands for user login and question creation.
apps/frontend-manage/src/components/sessions/creation/liveQuiz/submitLiveQuizForm.tsx Renamed and restructured form submission logic for live quizzes.
apps/lti/package.json Updated package version and removed offline development script.
packages/i18n/messages/de.ts Modified German localization strings for improved clarity.
packages/i18n/messages/en.ts Updated English localization strings for consistency and clarity.
apps/frontend-control/src/components/layout/MobileMenuBar.tsx Renamed prop for EmbeddingModal from sessionId to quizId.
apps/frontend-control/src/components/sessions/EmbeddingModal.tsx Refactored to support quiz-based functionality, updating props and data handling.
apps/frontend-control/src/components/sessions/SessionLists.tsx Updated prop for EmbeddingModal from sessionId to quizId.
apps/frontend-manage/src/components/sessions/EmbeddingModal.tsx Updated props and rendering logic to reflect new data structure.
apps/frontend-manage/src/components/sessions/LiveQuiz.tsx Renamed component and updated props to focus on live quizzes.
apps/frontend-manage/src/components/sessions/cockpit/CancelSessionModal.tsx Updated imports and mutation logic to reflect live quiz context.
apps/frontend-manage/src/pages/index.tsx Changed prop from elementId to activityId in ElementCreation.
apps/frontend-manage/src/pages/sessions/[id]/cockpit.tsx Updated imports and mutation logic for live quizzes.
apps/frontend-manage/src/pages/sessions/index.tsx Transitioned from session to live quiz querying and rendering logic.
packages/graphql/src/graphql/ops/QGetSingleLiveQuiz.graphql Updated query from GetSingleLiveSession to GetSingleLiveQuiz.
packages/graphql/src/graphql/ops/QGetUserLiveQuizzes.graphql Updated query from GetUserSessions to GetUserLiveQuizzes.

Possibly related PRs

Suggested reviewers

  • rschlaefli

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link

cypress bot commented Oct 30, 2024

klicker-uzh    Run #3474

Run Properties:  status check failed Failed #3474  •  git commit f16722f13b ℹ️: Merge cc7fecf6a4fca5c4fa1feac460b0e71985b85d55 into b8cef827ff5c5e3f442866d40da3...
Project klicker-uzh
Branch Review live-quiz-creation
Run status status check failed Failed #3474
Run duration 23m 03s
Commit git commit f16722f13b ℹ️: Merge cc7fecf6a4fca5c4fa1feac460b0e71985b85d55 into b8cef827ff5c5e3f442866d40da3...
Committer Julius Schlapbach
View all properties for this run ↗︎

Test results
Tests that failed  Failures 16
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 0
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 124
View all changes introduced in this branch ↗︎

Tests for review

Failed  C-control-workflow.cy.ts • 2 failed tests

View Output

Test Artifacts
Test functionalities of frontend-control application > Create a new live quiz with the SC question Test Replay Screenshots
Test functionalities of frontend-control application > Generate a token to log into the control-frontend application, execute session Test Replay Screenshots
Failed  E-course-workflow.cy.ts • 1 failed test

View Output

Test Artifacts
Test course creation and editing functionalities > Create a course with live quiz, practice quiz, and microlearning, and delete it again Test Replay Screenshots
Failed  F-live-quiz-workflow.cy.ts • 13 failed tests

View Output

Test Artifacts
Different live-quiz workflows > Start the created live quizzes, abort it, and restart & completes it Test Replay Screenshots
Different live-quiz workflows > Cleanup: Delete the created and completed live quiz Test Replay Screenshots
Different live-quiz workflows > Create and start a live quiz to test the entire execution cycle Test Replay Screenshots
Different live-quiz workflows > Responds to the first block of the running session from the student view Test Replay Screenshots
Different live-quiz workflows > Test the live quiz functionalities on mobile devices Test Replay Screenshots
Different live-quiz workflows > Start the second block of the live quiz Test Replay Screenshots
Different live-quiz workflows > Make feedbacks visible, respond to one and disable moderation Test Replay Screenshots
Different live-quiz workflows > Student answers questions in second block Test Replay Screenshots
Different live-quiz workflows > Verify that the feedbacks and the given feedback answer are visible to the student Test Replay Screenshots
Different live-quiz workflows > Close block and delete feedback / feedback response Test Replay Screenshots
The first 10 failed tests are shown, see all 13 tests in Cypress Cloud.

Copy link

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 18

🧹 Outside diff range and nitpick comments (12)
packages/graphql/src/graphql/ops/FElementDataInfo.graphql (1)

1-58: Consider using an interface to reduce field duplication.

While the fragment structure is well-organized, all element data types share identical fields. Consider defining a common interface to reduce duplication and improve maintainability.

Here's how you could refactor this using an interface:

interface BaseElementData {
  __typename: String!
  id: ID!
  elementId: ID!
  name: String!
  type: String!
  content: String!
  explanation: String
  pointsMultiplier: Float!
}

type ChoicesElementData implements BaseElementData {
  # Interface fields are automatically included
  # Add any Choices-specific fields here
}

# Similar implementation for other element types

Then simplify the fragment:

fragment ElementDataInfo on ElementInstance {
  elementData {
    ... on BaseElementData {
      __typename
      id
      elementId
      name
      type
      content
      explanation
      pointsMultiplier
    }
  }
}
packages/graphql/src/graphql/ops/MEditLiveQuiz.graphql (2)

Schema compatibility verified, but old type still exists

The mutation's use of ElementBlockInput is correct and matches the schema definition. However, I noticed that the old BlockInput type still exists in the schema, which might cause confusion. Consider removing the legacy BlockInput type if it's no longer used.

  • ElementBlockInput is properly defined with required fields: elements, order, and timeLimit
  • Both createLiveQuiz and editLiveQuiz mutations are consistently using the new ElementBlockInput type
  • Legacy BlockInput type is still present in the schema
🔗 Analysis chain

Line range hint 1-15: LGTM! Verify schema compatibility.

The mutation signature changes align well with the new element instance structure. The rename from EditSession to EditLiveQuiz and the type change from BlockInput to ElementBlockInput are consistent with the migration objectives.

Let's verify the schema compatibility:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that ElementBlockInput type exists and has the expected structure
# Expect: Definition of ElementBlockInput type with order, timeLimit, and elements fields

ast-grep --pattern 'input ElementBlockInput {
  $$$
}'

Length of output: 636


Script:

#!/bin/bash
# Let's check the schema definition for ElementBlockInput
rg -A 10 "input ElementBlockInput" packages/graphql/src/public/schema.graphql

# Also check for any references to BlockInput (old type) to ensure complete migration
rg "BlockInput" packages/graphql/src/public/schema.graphql

Length of output: 999


Migration from QuestionDataInfo to ElementDataInfo is incomplete

The codebase shows an ongoing migration from QuestionDataInfo to ElementDataInfo fragments. While the new fragment exists and is structurally similar, there are still several files using the old QuestionDataInfo fragment:

  • MUpdateQuestionInstances.graphql
  • QGetControlSession.graphql
  • QGetUserSessions.graphql
  • QGetCockpitSession.graphql

These files need to be updated to use the new ElementDataInfo fragment for consistency with the changes in MEditLiveQuiz.graphql.

🔗 Analysis chain

Line range hint 35-53: LGTM! Verify fragment definition.

The response structure changes appropriately reflect the new element instance structure. The switch from instances to elements and the use of the ElementDataInfo fragment indicates a more flexible approach to handling different element types.

Let's verify the fragment definition:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that ElementDataInfo fragment exists and QuestionDataInfo is fully migrated
# Expect: Definition of ElementDataInfo fragment and no remaining references to QuestionDataInfo

# Check for ElementDataInfo fragment
rg -l 'fragment ElementDataInfo'

# Verify no remaining references to old fragment
rg -l 'QuestionDataInfo'

Length of output: 528


Script:

#!/bin/bash
# Let's check the content of both fragments and their usage patterns
# Check ElementDataInfo fragment definition
echo "=== ElementDataInfo Fragment ==="
cat packages/graphql/src/graphql/ops/FElementDataInfo.graphql

# Check QuestionDataInfo fragment definition
echo -e "\n=== QuestionDataInfo Fragment ==="
cat packages/graphql/src/graphql/ops/FQuestionDataInfo.graphql

# Check the specific usage in files that still reference QuestionDataInfo
echo -e "\n=== Usage in MUpdateQuestionInstances ==="
cat packages/graphql/src/graphql/ops/MUpdateQuestionInstances.graphql

echo -e "\n=== Usage in QGetControlSession ==="
cat packages/graphql/src/graphql/ops/QGetControlSession.graphql

Length of output: 2983

packages/graphql/src/schema/practiceQuiz.ts (1)

35-41: Well-structured block input type definition.

The new ElementBlockInput type provides a clean structure for organizing quiz elements into blocks with ordering and optional time limits. This modular approach aligns well with the migration to the new element instance structure.

Consider documenting the following constraints in comments or documentation:

  • Valid ranges for timeLimit (if any)
  • Whether blocks can have overlapping order values
  • Maximum number of elements allowed per block
packages/types/src/index.ts (2)

33-40: Add documentation and improve type safety for BlockInput.

The new BlockInput type would benefit from:

  1. JSDoc documentation explaining its purpose and usage
  2. Stronger type constraints for validation

Consider applying these improvements:

+/**
+ * Represents a block of quiz elements with optional time limit
+ * @property {number | null} [timeLimit] - Optional time limit for the block in seconds
+ * @property {number} order - Position of this block in the quiz (1-based)
+ * @property {Array<{elementId: number, order: number}>} elements - Ordered list of elements in this block
+ */
 export type BlockInput = {
   timeLimit?: number | null
-  order: number
+  order: NonNegativeInteger
   elements: {
     elementId: number
-    order: number
+    order: NonNegativeInteger
   }[]
 }

+/** Ensures values are non-negative integers */
+type NonNegativeInteger = number & { __brand: 'NonNegativeInteger' }

33-40: Consider adding runtime validation for BlockInput.

While TypeScript types provide compile-time safety, consider adding runtime validation using a schema validation library (e.g., Zod, io-ts) to ensure:

  • Non-negative order values
  • Unique elementIds
  • Unique order values within elements array

Example validation schema:

import { z } from 'zod'

export const BlockInputSchema = z.object({
  timeLimit: z.number().nullable().optional(),
  order: z.number().min(0),
  elements: z.array(z.object({
    elementId: z.number(),
    order: z.number().min(0)
  }))
}).refine(
  data => new Set(data.elements.map(e => e.elementId)).size === data.elements.length,
  { message: 'Duplicate elementIds are not allowed' }
).refine(
  data => new Set(data.elements.map(e => e.order)).size === data.elements.length,
  { message: 'Duplicate order values are not allowed' }
)
packages/graphql/src/public/client.json (1)

Line range hint 1-180: Consider documenting the operation mapping.

Since this represents a significant architectural change from sessions to quizzes, consider adding documentation (e.g., in README.md) that maps old session operations to new quiz operations to help with the migration.

packages/graphql/src/ops.schema.json (3)

4308-4511: LGTM! Consider adding field descriptions.

The ElementBlock related types are well-structured with appropriate field types and nullability. The use of an enum for status is a good practice.

Consider adding descriptions to the fields to improve schema documentation. For example:

  • execution: Purpose of this integer field
  • randomSelection: How this field affects element selection
  • timeLimit: Units of measurement (seconds/minutes)

Line range hint 10510-10882: LGTM! Well-structured type with good feature separation.

The LiveQuiz type provides a comprehensive model with good separation of concerns through feature flags and appropriate field types.

Consider the following architectural aspects:

  1. Caching strategy for frequently accessed fields like status and accessMode
  2. Indexing strategy for queries filtering by status, startedAt, or finishedAt
  3. Rate limiting for real-time features (confusion feedback, live Q&A)

Line range hint 12190-13960: Consider reducing duplication through input types.

The mutations are well-structured but have significant field duplication across quiz types.

Consider creating shared input types for common fields:

  1. QuizBaseInput for fields like name, displayName, description
  2. GamificationInput for multiplier, maxBonusPoints, timeToZeroBonus
  3. QuizFeaturesInput for isConfusionFeedbackEnabled, isLiveQAEnabled, etc.

This would:

  • Reduce schema complexity
  • Make it easier to maintain consistent validation
  • Simplify future additions of quiz types
packages/graphql/src/public/schema.graphql (1)

471-471: Re-evaluate the Necessity of order in ElementStackInput

The order field in ElementStackInput may be redundant if the order is implied by the position in the list or managed elsewhere. Review whether this field is necessary or if it can be removed to simplify the input type.

packages/graphql/src/schema/mutation.ts (1)

824-824: Consider specifying non-empty array for blocks argument.

At line 824, the blocks argument is defined as an array of ElementBlockInput. If the logic requires at least one block, consider enforcing this by validating that the array is not empty to prevent runtime errors.

🛑 Comments failed to post (18)
packages/graphql/src/schema/course.ts (1)

16-16: 💡 Codebase verification

Inconsistent import paths need to be updated

The import path change in course.ts is part of a larger refactoring, but there are still files using the old path:

  • packages/graphql/src/scripts/2024-05-07_compute_correctness_counts_response.ts
  • packages/graphql/src/schema/query.ts
  • packages/graphql/src/schema/mutation.ts

These files are importing from ../services/practiceQuizzes.js while the schema file has been renamed to practiceQuiz.js. This inconsistency needs to be addressed.

🔗 Analysis chain

LGTM! Verify import path consistency.

The import path change from './practiceQuizzes.js' to './practiceQuiz.js' aligns with the module renaming mentioned in the summary.

Let's verify that this renaming is consistent across the codebase:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining references to the old 'practiceQuizzes.js' path
# and confirm the existence of the new 'practiceQuiz.js' file

echo "Checking for any remaining references to old path..."
rg -l "practiceQuizzes\.js"

echo -e "\nVerifying existence of new file..."
fd "practiceQuiz.js"

Length of output: 406


Script:

#!/bin/bash
# Let's examine the content of files that still reference the old path
echo "Content of compute_correctness_counts_response.ts:"
rg -A 5 -B 5 "practiceQuizzes\.js" packages/graphql/src/scripts/2024-05-07_compute_correctness_counts_response.ts

echo -e "\nContent of query.ts:"
rg -A 5 -B 5 "practiceQuizzes\.js" packages/graphql/src/schema/query.ts

echo -e "\nContent of mutation.ts:"
rg -A 5 -B 5 "practiceQuizzes\.js" packages/graphql/src/schema/mutation.ts

# Let's also check if there's a practiceQuiz.ts file (without .js extension) that might be the actual source
echo -e "\nChecking for .ts source file:"
fd "practiceQuiz.ts"

Length of output: 2228

packages/graphql/src/public/server.json (1)

18-18: 🛠️ Refactor suggestion

Consider optimizing fragment structure to reduce duplication.

The ElementDataInfo and ElementData fragments could be optimized by:

  1. Extracting common fields (id, name, type, content, explanation, pointsMultiplier) into a shared interface
  2. Using interface inheritance for specific element types

Consider refactoring the schema to use interfaces:

interface BaseElementData {
  id: ID!
  elementId: ID!
  name: String!
  type: String!
  content: String!
  explanation: String
  pointsMultiplier: Int!
}

interface ElementData implements BaseElementData {
  # Base fields from BaseElementData
  # Type specific fields...
}

Also applies to: 37-37

packages/graphql/src/ops.schema.json (1)

4837-4907: 💡 Codebase verification

Based on the code analysis, I can now provide a final response:

Server-side validation needed for ElementInstanceOptions

The current implementation of ElementInstanceOptions in the GraphQL schema lacks validation constraints for both pointsMultiplier and resetTimeDays. The fields are simply exposed as nullable integers without any restrictions:

export interface IElementInstanceOptions {
  pointsMultiplier?: number
  resetTimeDays?: number
}

Recommendations:

  • Add validation to ensure pointsMultiplier is positive and has a reasonable maximum (e.g., 1-10 range)
  • Add validation to ensure resetTimeDays is positive and has a reasonable maximum (e.g., 1-365 range)
🔗 Analysis chain

LGTM! Consider adding input validation.

The ElementInstance related types provide a clean interface for creating and configuring element instances.

Consider if we need server-side validation for:

  • pointsMultiplier: Should it have a minimum/maximum value?
  • resetTimeDays: Should it have a minimum value?

Let me search for validation logic around pointsMultiplier and resetTimeDays.


Let me try a different search pattern to find any validation logic.


Let me try one more search to look for any constraints or validation in the schema or mutation resolvers.


Let me check the actual implementation of ElementInstanceOptions and any related validation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing validation in resolvers
# Look for validation logic in resolver files
rg -g "*.ts" -g "*.js" "pointsMultiplier|resetTimeDays.*valid"

Length of output: 423062


Script:

#!/bin/bash
# Look for validation logic around pointsMultiplier and resetTimeDays
ast-grep --pattern 'if ($$$pointsMultiplier$$$) { $$$ }' 

Length of output: 58


Script:

#!/bin/bash
# Look for validation logic in resolver files and services
rg -g "*.ts" "validate.*point|point.*valid|validate.*reset|reset.*valid"

Length of output: 339


Script:

#!/bin/bash
# Look for validation in schema definitions and mutations
rg -g "*.ts" "ElementInstanceOptions|@Min|@Max|@IsPositive|@IsInt.*pointsMultiplier|@IsInt.*resetTimeDays"

Length of output: 4505


Script:

#!/bin/bash
# Check ElementInstanceOptions implementation and validation
cat packages/graphql/src/schema/elementData.ts

Length of output: 3456

packages/graphql/src/schema/liveQuiz.ts (3)

15-18: 🛠️ Refactor suggestion

Simplify type definitions by removing redundant nullability

In the ILiveQuiz interface, the course property is declared as optional (?) and includes | null in its type:

course?: DB.Course | null

This means course can be undefined, null, or a DB.Course object. Since marking it as optional already allows undefined, including | null is redundant. Consider removing | null to simplify the type definition.

Suggestion:

interface ILiveQuiz extends DB.LiveQuiz {
  blocks: DB.ElementBlock[]
-  course?: DB.Course | null
+  course?: DB.Course
}

79-82: 🛠️ Refactor suggestion

Simplify type definitions by removing redundant nullability

Similarly, in the IElementBlock interface, the elements property is declared as optional and includes | null:

elements?: DB.ElementInstance[] | null

This allows elements to be undefined, null, or an array of DB.ElementInstance. To reduce redundancy and simplify the type, consider removing | null from the type definition.

Suggestion:

export interface IElementBlock extends DB.ElementBlock {
  numOfParticipants?: number
-  elements?: DB.ElementInstance[] | null
+  elements?: DB.ElementInstance[]
}

16-16: ⚠️ Potential issue

Ensure consistency between property definitions and GraphQL exposure

In the ILiveQuiz interface, the blocks property is defined as a required array:

blocks: DB.ElementBlock[]

However, in the LiveQuiz GraphQL object, blocks is exposed with nullable: true, indicating it can be null:

blocks: t.expose('blocks', {
  type: [ElementBlockRef],
  nullable: true,
}),

This inconsistency might lead to confusion or errors. If blocks is always present (even as an empty array), consider removing nullable: true to reflect that it should not be null.

Suggestion:

blocks: t.expose('blocks', {
  type: [ElementBlockRef],
-  nullable: true,
+  nullable: false,
}),

Alternatively, if blocks can indeed be null or undefined, update the interface to match:

interface ILiveQuiz extends DB.LiveQuiz {
- blocks: DB.ElementBlock[]
+ blocks?: DB.ElementBlock[] | null
  course?: DB.Course | null
}

Also applies to: 46-49

packages/graphql/src/services/liveQuizzes.ts (3)

188-192: ⚠️ Potential issue

Emit correct ID in invalidate event

When creating a new live quiz, id may be undefined, resulting in an incorrect id being emitted in the 'invalidate' event. Use element.id to ensure the correct identifier is emitted.

Apply this diff to emit the correct ID:

 ctx.emitter.emit('invalidate', {
     typename: 'LiveQuiz',
-    id,
+    id: element.id,
 })

Committable suggestion was skipped due to low confidence.


98-101: ⚠️ Potential issue

Incorrect element existence check

Comparing dbElements.length to uniqueElements.size does not accurately verify that all requested elements were found. Since dbElements should have unique IDs by default, this check may not detect missing elements. Instead, compare the number of retrieved elements to the number of unique element IDs requested.

Apply this diff to fix the logic:

-const uniqueElements = new Set(dbElements.map((q) => q.id))
-if (dbElements.length !== uniqueElements.size) {
+if (dbElements.length !== new Set(elements).size) {
   throw new GraphQLError('Not all elements could be found')
 }
📝 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.

  if (dbElements.length !== new Set(elements).size) {
    throw new GraphQLError('Not all elements could be found')
  }

103-107: ⚠️ Potential issue

Avoid using spread operator in reduce accumulator

Using the spread operator ... in the accumulator of a reduce function can lead to O(n^2) time complexity, negatively impacting performance. Consider mutating the accumulator object directly to improve efficiency.

Apply this diff to optimize the accumulator:

-const elementMap = dbElements.reduce<Record<number, Element>>(
-  (acc, elem) => ({ ...acc, [elem.id]: elem }),
-  {}
-)
+const elementMap = dbElements.reduce<Record<number, Element>>(
+  (acc, elem) => {
+    acc[elem.id] = elem
+    return acc
+  },
+  {}
+)
📝 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.

  const elementMap = dbElements.reduce<Record<number, Element>>(
    (acc, elem) => {
      acc[elem.id] = elem
      return acc
    },
    {}
  )
🧰 Tools
🪛 Biome

[error] 104-104: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

packages/graphql/src/public/schema.graphql (7)

366-376: 🛠️ Refactor suggestion

Consider Non-nullable Fields in ElementBlock

In the ElementBlock type, fields like execution, expiresAt, numOfParticipants, randomSelection, and timeLimit are nullable. If these fields are essential for the functioning of an ElementBlock, consider making them non-nullable to ensure data integrity.


378-382: ⚠️ Potential issue

Align Fields Between ElementBlock and ElementBlockInput

The ElementBlockInput input type lacks several fields present in ElementBlock, such as randomSelection, execution, and expiresAt. If these fields are necessary during the creation of an ElementBlock, consider adding them to ElementBlockInput for consistency and completeness.


384-388: 🛠️ Refactor suggestion

Ensure Consistent Naming for Status Enums

The ElementBlockStatus enum may overlap with existing enums like SessionBlockStatus. To maintain consistency and avoid redundancy, consider reusing existing enums if they have the same values or ensure that the naming conventions clearly differentiate their purposes.


416-419: 🛠️ Refactor suggestion

Add options Field to ElementInstanceInput if Necessary

If ElementInstance includes an options field that needs to be set during creation, consider adding an options field to ElementInstanceInput to allow for this configuration.


943-965: 🛠️ Refactor suggestion

Review Nullability of Fields in LiveQuiz

Several fields in the LiveQuiz type, such as course, pinCode, startedAt, and finishedAt, are nullable. If these fields are essential for a live quiz's operation or should always have values, consider making them non-nullable to enforce data integrity.


967-971: 🛠️ Refactor suggestion

Avoid Redundant Enums by Reusing SessionAccessMode

The LiveQuizAccessMode enum duplicates the values of the existing SessionAccessMode enum. To prevent redundancy and maintain consistency, consider reusing SessionAccessMode instead of defining LiveQuizAccessMode.


1017-1017: ⚠️ Potential issue

Ensure Consistent ID Types for courseId Parameter

In the createLiveQuiz mutation, the courseId parameter is of type String. To maintain consistency across the schema, consider using ID or ID! for identifier fields like courseId.

packages/graphql/src/schema/mutation.ts (2)

40-46: 🛠️ Refactor suggestion

Verify the import source of ElementBlockInput.

ElementBlockInput is imported from './practiceQuiz.js' between lines 40-46. Considering it's now used in createLiveQuiz and editLiveQuiz, it might be more appropriate to move ElementBlockInput to a shared module or a more general location to improve modularity.


830-831: 🛠️ Refactor suggestion

Consider default values for optional parameters.

Since maxBonusPoints (line 830) and timeToZeroBonus (line 831) are now optional, it might be beneficial to assign default values within LiveQuizService.manipulateLiveQuiz to maintain consistent behavior when these arguments are not provided.

Would you like assistance in implementing default values for these parameters?

Copy link

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 27

🧹 Outside diff range and nitpick comments (21)
apps/frontend-manage/src/components/sessions/creation/DropElementsStack.tsx (1)

16-17: Consider adding proper typing for options parameter.

Using any type reduces type safety. Consider defining a proper interface for the options parameter based on its expected usage.

-  options?: any
+  options?: {
+    // TODO: Define specific options interface based on usage
+  }
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizCourseMonitor.tsx (1)

Line range hint 28-31: Consider improving type safety and simplifying the course lookup.

The current implementation has a few potential improvements:

  1. The non-null assertions (!) could be unsafe if the arrays are undefined
  2. The course lookup could be simplified

Consider this safer implementation:

- [...gamifiedCourses!, ...nonGamifiedCourses!].find(
+ [...(gamifiedCourses ?? []), ...(nonGamifiedCourses ?? [])].find(
  (course) => course.value === values.courseId
)?.isGamified
🧰 Tools
🪛 Biome

[error] 16-16: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizBlockSettingsModal.tsx (3)

5-17: Add JSDoc documentation for better maintainability

Consider adding JSDoc documentation to describe the purpose of each prop and their expected values, especially for the replace callback function.

+/**
+ * Modal component for configuring live quiz block settings
+ * @param {boolean} openSettings - Controls modal visibility
+ * @param {function} setOpenSettings - Callback to update modal visibility
+ * @param {ElementBlockFormValues} block - Current block settings
+ * @param {number} index - Block position in the quiz
+ * @param {function} replace - Callback to update block settings at given index
+ */
 function LiveQuizBlockSettingsModal({

21-30: Extract modal styling to constants for reusability

Consider moving the modal width classes to a constant to maintain consistency across the application.

+const MODAL_CLASSES = {
+  content: 'sm:w-3/4 md:w-1/2',
+} as const

 <Modal
   open={openSettings}
   onClose={() => setOpenSettings(false)}
   title={t('manage.sessionForms.blockSettingsTitle', {
     blockIx: index + 1,
   })}
-  className={{
-    content: 'sm:w-3/4 md:w-1/2',
-  }}
+  className={MODAL_CLASSES}

31-46: Enhance accessibility with ARIA labels

Add aria-label to improve screen reader support.

 <NumberField
+  aria-label={t('manage.sessionForms.timeLimit')}
   label={t('manage.sessionForms.timeLimit')}
   tooltip={t('manage.sessionForms.timeLimitTooltip', {
apps/frontend-manage/src/components/sessions/creation/PasteSelectionButton.tsx (1)

34-67: Enhance button accessibility

While using the design system button is good, consider adding an aria-label for better screen reader support.

 <Button
   fluid
+  aria-label={t('manage.sessionForms.pasteSelectionElements', {
+    count: Object.keys(selection).length,
+  })}
   className={{
     root: 'mb-2 justify-center gap-3 border-orange-300 bg-orange-100 text-sm hover:border-orange-400 hover:bg-orange-200 hover:text-orange-900',
   }}
apps/frontend-manage/src/components/sessions/creation/liveQuiz/submitLiveSessionForm.tsx (2)

Line range hint 39-102: Standardize numeric parsing and add validation.

The code inconsistently handles numeric parsing:

  • Some values use parseInt(String(value))
  • Others directly use parseInt(value)
  • No validation is performed before parsing

Additionally, there's an inconsistency in multiplier handling between edit and create modes.

Consider applying these improvements:

+ const parseNumericInput = (value: string | number, fallback: number = 0): number => {
+   const parsed = Number(value)
+   return isNaN(parsed) ? fallback : parsed
+ }

  if (editMode && id) {
    const session = await editLiveQuiz({
      variables: {
        // ... other fields
-       multiplier: values.courseId !== '' ? parseInt(values.multiplier) : 1,
-       maxBonusPoints: parseInt(String(values.maxBonusPoints)),
-       timeToZeroBonus: parseInt(String(values.timeToZeroBonus)),
+       multiplier: values.courseId !== '' ? parseNumericInput(values.multiplier, 1) : 1,
+       maxBonusPoints: parseNumericInput(values.maxBonusPoints),
+       timeToZeroBonus: parseNumericInput(values.timeToZeroBonus),
        // ... other fields
      },
    })
  } else {
    const session = await createLiveQuiz({
      variables: {
        // ... other fields
-       multiplier: parseInt(values.multiplier),
-       maxBonusPoints: parseInt(String(values.maxBonusPoints)),
-       timeToZeroBonus: parseInt(String(values.timeToZeroBonus)),
+       multiplier: values.courseId !== '' ? parseNumericInput(values.multiplier, 1) : 1,
+       maxBonusPoints: parseNumericInput(values.maxBonusPoints),
+       timeToZeroBonus: parseNumericInput(values.timeToZeroBonus),
        // ... other fields
      },
    })
  }

Line range hint 35-102: Consider adding error details to the error toast.

The error handling only shows a generic toast without providing any details about what went wrong.

Consider enhancing the error handling:

  } catch (error) {
    console.log('error: ', error)
-   setErrorToastOpen(true)
+   const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'
+   setErrorToastOpen(true)
+   // Assuming you have a way to set error message content
+   setErrorMessage(errorMessage)
  }
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizQuestionsStep.tsx (2)

14-23: Consider tracking TODOs more formally.

While the accepted types are well-defined, having commented-out types with a TODO comment in production code isn't ideal. Consider either:

  1. Implementing the flashcard and content element support now, or
  2. Creating a tracked issue for this enhancement

Would you like me to help create a GitHub issue to track the implementation of flashcard and content element support?


72-72: Consider improving error prop type safety.

The error prop is currently cast to any. Consider defining a proper type for the block errors to maintain type safety.

-error={errors.blocks as any}
+error={errors.blocks as FormikErrors<typeof block>[]}
apps/frontend-manage/src/components/sessions/creation/StackCreationStep.tsx (1)

59-65: Consider consistent naming for index variables.

While renaming index to stackIx improves clarity, the key prop still uses index. Consider updating it for consistency:

-key={`stack-${index}-${stack.elements.map((e) => e.id).join('-')}`}
+key={`stack-${stackIx}-${stack.elements.map((e) => e.id).join('-')}`}
apps/frontend-manage/src/components/sessions/creation/WizardLayout.tsx (1)

28-47: Consider documenting the new element instance structure

The refactoring introduces a clear separation between elements and their containers (blocks/stacks), which is a good architectural pattern. Consider adding documentation (e.g., in README or comments) explaining this new structure and its benefits to help other developers understand the design decisions.

Also applies to: 74-75

apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizCreationBlock.tsx (1)

Line range hint 55-77: Update useDrop dependency array to prevent stale closures.

The empty dependency array [] means the drop callback will always use the initial values of block, blockIx, and replace. This could lead to incorrect behavior if these values change.

    }),
-   []
+   [block, blockIx, replace, acceptedTypes]
  )
apps/frontend-manage/src/components/sessions/creation/WizardElementList.tsx (1)

19-35: LGTM! Consider adding JSDoc comments.

The interface hierarchy is well-structured. Consider adding JSDoc comments to document the purpose of each interface and their properties, especially for props like highlightFTNoSL which has different behavior in each interface.

/**
 * Base properties for wizard element list components
 */
interface BaseProps {
  /** Index of the current stack */
  stackIx: number
}

/**
 * Properties for stack-based wizard element lists
 */
interface StackWizardElementListProps extends BaseProps {
  // ... (add property descriptions)
}
apps/frontend-manage/src/components/sessions/creation/AddStackButton.tsx (1)

Line range hint 1-194: Implementation is correct but could benefit from better organization.

The component successfully handles both stack and block types with proper type safety. Consider creating a separate utilities file for the shared logic to improve maintainability and reusability across the application.

Consider:

  1. Moving utility functions to a separate file (e.g., stackUtils.ts)
  2. Creating a dedicated type file for shared interfaces
  3. Using a reducer pattern if the component grows more complex
apps/frontend-manage/src/components/sessions/creation/StackBlockCreation.tsx (1)

Line range hint 133-144: Consider simplifying error handling logic.

The current error handling implementation has duplicate logic for single and multiple modes. Consider extracting the error tooltip into a separate component to reduce duplication and improve readability.

+ // Create a new component ErrorTooltip.tsx
+ interface ErrorTooltipProps {
+   errors: ElementStackErrorValues | ElementStackErrorValues[];
+ }
+ 
+ function ErrorTooltip({ errors }: ErrorTooltipProps) {
+   return (
+     <Tooltip
+       tooltip={<StackCreationErrors errors={errors} />}
+       delay={0}
+       className={{ tooltip: 'z-20 max-w-[30rem] text-sm' }}
+     >
+       <FontAwesomeIcon
+         icon={faCircleExclamation}
+         className="mr-1 text-red-600"
+       />
+     </Tooltip>
+   );
+ }

  // In StackBlockCreation.tsx
- {error &&
-   !singleStackMode &&
-   Array.isArray(error) &&
-   error.length > stackIx &&
-   typeof error[stackIx] !== 'undefined' && (
-     <Tooltip
-       tooltip={<StackCreationErrors errors={error[stackIx]} />}
-       delay={0}
-       className={{ tooltip: 'z-20 max-w-[30rem] text-sm' }}
-     >
-       <FontAwesomeIcon
-         icon={faCircleExclamation}
-         className="mr-1 text-red-600"
-       />
-     </Tooltip>
-   )}
- {error && !Array.isArray(error) && singleStackMode && (
-   <Tooltip
-     tooltip={<StackCreationErrors errors={error} />}
-     delay={0}
-     className={{ tooltip: 'z-20 max-w-[30rem] text-sm' }}
-   >
-     <FontAwesomeIcon
-       icon={faCircleExclamation}
-       className="mr-1 text-red-600"
-     />
-   </Tooltip>
- )}
+ {error && ((!singleStackMode && Array.isArray(error) && error.length > stackIx && error[stackIx]) || 
+           (singleStackMode && !Array.isArray(error))) && (
+   <ErrorTooltip errors={singleStackMode ? error : error[stackIx]} />
+ )}

Also applies to: 145-157

cypress/cypress/support/commands.ts (2)

Line range hint 4-4: Add tests for the Cypress commands.

The TODO comment indicates missing tests for these commands. Given the complexity and number of commands, having tests would help ensure reliability and catch potential regressions.

Would you like me to help create test cases for these Cypress commands?


Line range hint 573-627: Consider abstracting common drag-and-drop logic.

The drag-and-drop implementation is duplicated across multiple commands (createLiveQuiz, createStacks, etc.). Consider extracting this into a reusable helper function to improve maintainability and reduce duplication.

Here's a suggested implementation:

function dragAndDropElement(elementSelector: string, targetSelector: string, index: number, blockName: string) {
  const dataTransfer = new DataTransfer()
  cy.get(`[data-cy="${elementSelector}"]`)
    .contains(elementSelector)
    .trigger('dragstart', {
      dataTransfer,
    })
  cy.get(`[data-cy="${targetSelector}"]`).trigger('drop', {
    dataTransfer,
  })
  cy.get(`[data-cy="question-${index}-${blockName}"]`)
    .should('exist')
    .should('contain', elementSelector.substring(0, 20))
}
cypress/cypress/e2e/F-live-quiz-workflow.cy.ts (2)

203-204: LGTM! The updated drop zone selectors better reflect the new element structure.

The change from drop-questions-here-X to drop-elements-block-X aligns with the new element instance structure and provides better semantic clarity.

Consider extracting these selectors into constants at the top of the file to make future updates easier and maintain consistency:

const DROP_ELEMENT_BLOCK = (index: number) => `drop-elements-block-${index}`

Also applies to: 633-634, 646-647


Line range hint 626-626: Consider implementing the cy.createStacks helper function.

The TODO comment indicates a planned refactoring to use a helper function. This would improve test maintainability by encapsulating the stack creation logic.

Would you like me to help create the cy.createStacks helper function? This could include:

  • Typescript types for stack configuration
  • Handling of drag and drop operations
  • Validation of stack creation
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveSessionWizard.tsx (1)

Line range hint 276-296: Avoid non-null assertions to prevent potential runtime errors

At lines 285 and 289, you're using non-null assertions (!) with data.createLiveQuiz!.id. Even though you check data?.createLiveQuiz?.id in the condition at line 276, it's safer to avoid non-null assertions to prevent possible runtime errors if data.createLiveQuiz is undefined when the button is clicked.

Apply this diff to safely access data.createLiveQuiz.id without non-null assertions:

- {!editMode && data?.createLiveQuiz?.id ? (
+ {!editMode && data?.createLiveQuiz?.id && (
    <Button
      data={{ cy: 'quick-start' }}
      onClick={async () => {
        await startSession({
          variables: {
-           id: data.createLiveQuiz!.id,
+           id: data.createLiveQuiz.id,
          },
        })
-       router.push(`/sessions/${data.createLiveQuiz!.id}/cockpit`)
+       router.push(`/sessions/${data.createLiveQuiz.id}/cockpit`)
      }}
      className={{ root: 'space-x-1' }}
    >
      <Button.Icon>
        <FontAwesomeIcon icon={faPlay} />
      </Button.Icon>
      <Button.Label>
        {t('manage.sessionForms.liveQuizStartNow')}
      </Button.Label>
    </Button>
- ) : null}
+ )}
🛑 Comments failed to post (27)
apps/frontend-manage/src/components/sessions/creation/DropElementsStack.tsx (2)

7-20: 🛠️ Refactor suggestion

Consider extracting props interface.

For better reusability and maintainability, consider extracting the props interface.

+interface DropElementsStackProps {
+  type: 'block' | 'stack'
+  drop: (
+    elementOrNode: ConnectableElement,
+    options?: any  // TODO: Define specific options type
+  ) => ReactElement | null
+  isOver: boolean
+  index: number
+}

-function DropElementsStack({
+function DropElementsStack({
   type,
   drop,
   isOver,
   index,
-}: {
-  type: 'block' | 'stack'
-  drop: (
-    elementOrNode: ConnectableElement,
-    options?: any
-  ) => ReactElement | null
-  isOver: boolean
-  index: number
-}) {
+}: DropElementsStackProps) {
📝 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.

interface DropElementsStackProps {
  type: 'block' | 'stack'
  drop: (
    elementOrNode: ConnectableElement,
    options?: any  // TODO: Define specific options type
  ) => ReactElement | null
  isOver: boolean
  index: number
}

function DropElementsStack({
  type,
  drop,
  isOver,
  index,
}: DropElementsStackProps) {

21-31: 🛠️ Refactor suggestion

Enhance accessibility.

The drop zone could benefit from improved accessibility features.

   return drop(
     <div
       className={twMerge(
         'w-full rounded border border-solid p-0.5 text-center',
         isOver && 'bg-primary-20'
       )}
       data-cy={`drop-elements-${type}-${index}`}
+      role="button"
+      tabIndex={0}
+      aria-label={`Drop zone for ${type} at position ${index + 1}`}
     >
       <FontAwesomeIcon icon={faPlus} size="lg" />
     </div>
📝 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.

  return drop(
    <div
      className={twMerge(
        'w-full rounded border border-solid p-0.5 text-center',
        isOver && 'bg-primary-20'
      )}
      data-cy={`drop-elements-${type}-${index}`}
      role="button"
      tabIndex={0}
      aria-label={`Drop zone for ${type} at position ${index + 1}`}
    >
      <FontAwesomeIcon icon={faPlus} size="lg" />
    </div>
  )
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizBlockSettingsModal.tsx (1)

38-43: ⚠️ Potential issue

Improve time limit input validation and parsing

The current implementation could be more robust in handling invalid inputs.

 onChange={(newValue: string) => {
+  const parsedValue = parseInt(newValue)
+  if (newValue !== '' && (isNaN(parsedValue) || parsedValue < 0)) {
+    return // or show error message
+  }
   replace(index, {
     ...block,
-    timeLimit: newValue === '' ? undefined : parseInt(newValue),
+    timeLimit: newValue === '' ? undefined : parsedValue,
   })
 }}
📝 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.

        onChange={(newValue: string) => {
          const parsedValue = parseInt(newValue)
          if (newValue !== '' && (isNaN(parsedValue) || parsedValue < 0)) {
            return // or show error message
          }
          replace(index, {
            ...block,
            timeLimit: newValue === '' ? undefined : parsedValue,
          })
        }}
apps/frontend-manage/src/components/sessions/creation/PasteSelectionButton.tsx (4)

8-22: 🛠️ Refactor suggestion

Consider using a discriminated union for better type safety

While the current interface structure is good, you could improve type safety by using a discriminated union to distinguish between stack and block props.

Consider this alternative approach:

interface BaseProps {
  index: number
  selection: Record<number, Element>
  resetSelection: (() => void) | undefined
}

interface StackProps extends BaseProps {
  type: 'stack'
  stack: ElementStackFormValues
  replace: (index: number, value: ElementStackFormValues) => void
}

interface BlockProps extends BaseProps {
  type: 'block'
  stack: ElementBlockFormValues
  replace: (index: number, value: ElementBlockFormValues) => void
}

type PasteSelectionButtonProps = StackProps | BlockProps

49-54: 🛠️ Refactor suggestion

Use immutable state updates

Instead of mutating state with concat, prefer spread operator for immutability.

-const stackElements = stack.elements.concat(newElements)
+const stackElements = [...stack.elements, ...newElements]

 replace(index, {
   ...stack,
   elements: stackElements,
 })
📝 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.

        const stackElements = [...stack.elements, ...newElements]

        replace(index, {
          ...stack,
          elements: stackElements,
        })

40-48: ⚠️ Potential issue

Add validation and error handling for selected elements

The mapping of selected elements lacks validation and error handling. Consider adding checks for required properties and handling potential errors.

 const newElements = Object.values(selection).map((question) => {
+  if (!question.id || !question.name || !question.type) {
+    throw new Error('Invalid question format')
+  }
   return {
     id: question.id,
     title: question.name,
     type: question.type,
     hasSampleSolution:
       'options' in question
         ? (question.options.hasSampleSolution ?? false)
         : 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.

        const newElements = Object.values(selection).map((question) => {
          if (!question.id || !question.name || !question.type) {
            throw new Error('Invalid question format')
          }
          return {
            id: question.id,
            title: question.name,
            type: question.type,
            hasSampleSolution:
              'options' in question
                ? (question.options.hasSampleSolution ?? false)
                : true,
          }
        })

34-58: 🛠️ Refactor suggestion

Consider adding loading state during processing

The button should indicate when it's processing the selection to prevent multiple clicks.

+const [isProcessing, setIsProcessing] = useState(false)

 <Button
   fluid
+  disabled={isProcessing}
   className={{
     root: 'mb-2 justify-center gap-3 border-orange-300 bg-orange-100 text-sm hover:border-orange-400 hover:bg-orange-200 hover:text-orange-900',
   }}
-  onClick={() => {
+  onClick={async () => {
+    setIsProcessing(true)
+    try {
       // ... existing code ...
+    } finally {
+      setIsProcessing(false)
+    }
   }}
   data={{ cy: 'paste-selected-questions' }}
 >

Committable suggestion was skipped due to low confidence.

apps/frontend-manage/src/components/sessions/creation/liveQuiz/submitLiveSessionForm.tsx (3)

74-74: ⚠️ Potential issue

Update success check to use new mutation names.

The success check still references the old mutation name createSession instead of createLiveQuiz.

Apply this change:

- success = Boolean(session.data?.createSession)
+ success = Boolean(session.data?.createLiveQuiz)

Also applies to: 102-102


39-39: ⚠️ Potential issue

Update success check to use new mutation names.

The success check still references the old mutation name editSession instead of editLiveQuiz.

Apply this change:

- success = Boolean(session.data?.editSession)
+ success = Boolean(session.data?.editLiveQuiz)

Also applies to: 65-65


7-14: ⚠️ Potential issue

Replace any types with proper GraphQL mutation types.

The interface uses any type for createLiveQuiz and editLiveQuiz mutations, which loses TypeScript's type safety benefits. Consider using the generated GraphQL types from your schema.

Apply this change:

interface LiveSessionFormProps {
  id?: string
  editMode: boolean
  values: LiveQuizFormValues
- createLiveQuiz: any
- editLiveQuiz: any
+ createLiveQuiz: MutationFunction<CreateLiveQuizMutation, CreateLiveQuizMutationVariables>
+ editLiveQuiz: MutationFunction<EditLiveQuizMutation, EditLiveQuizMutationVariables>
  setIsWizardCompleted: (isCompleted: boolean) => void
  setErrorToastOpen: (isOpen: boolean) => void
}

Committable suggestion was skipped due to low confidence.

apps/frontend-manage/src/components/sessions/creation/WizardLayout.tsx (1)

38-38: ⚠️ Potential issue

Fix typo in interface name "ElememntBlockErrorValues"

The interface name contains a typo: "Elememnt" should be "Element".

-export interface ElememntBlockErrorValues {
+export interface ElementBlockErrorValues {
📝 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.

export interface ElementBlockErrorValues {
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizCreationBlock.tsx (4)

162-176: ⚠️ Potential issue

Maintain consistent prop naming across components.

There's inconsistency in prop naming between components. Some use index while others use blockIx. This should be standardized across all components for better maintainability.

  <PasteSelectionButton
-   index={blockIx}
+   blockIx={blockIx}
    selection={selection}
    resetSelection={resetSelection}
    stack={block}
    replace={replace}
  />
  
  <LiveQuizBlockSettingsModal
    openSettings={openSettings}
    setOpenSettings={setOpenSettings}
    block={block}
-   index={blockIx}
+   blockIx={blockIx}
    replace={replace}
  />

Also applies to: 184-190


20-21: ⚠️ Potential issue

Fix typo in type import name.

There's a typo in the import name: ElememntBlockErrorValues should be ElementBlockErrorValues.

- ElememntBlockErrorValues,
+ ElementBlockErrorValues,
📝 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.

  ElementBlockErrorValues,
  ElementBlockFormValues,

125-127: ⚠️ Potential issue

Fix block navigation boundary condition.

The condition blockIx !== numOfBlocks is incorrect for the right move button. Since blockIx is zero-based, the last valid index is numOfBlocks - 1.

- move(blockIx, blockIx !== numOfBlocks ? blockIx + 1 : blockIx)
+ move(blockIx, blockIx < numOfBlocks - 1 ? blockIx + 1 : blockIx)
📝 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.

            onClick={() =>
              move(blockIx, blockIx < numOfBlocks - 1 ? blockIx + 1 : blockIx)
            }

89-93: 🛠️ Refactor suggestion

Simplify error checking logic.

The current error checking logic is verbose and could be simplified using optional chaining.

- {error &&
-   Array.isArray(error) &&
-   error.length > blockIx &&
-   typeof error[blockIx] !== 'undefined' && (
+ {error?.[blockIx] && (
📝 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.

          {error?.[blockIx] && (
              <Tooltip
apps/frontend-manage/src/components/sessions/creation/WizardElementList.tsx (4)

47-52: 🛠️ Refactor suggestion

Simplify error handling logic.

The nested ternary operations make the error handling logic difficult to read and maintain.

Consider extracting this into a separate function for better readability:

const getElementErrors = (error: ElementStackErrorValues | ElementStackErrorValues[] | undefined, stackIx: number) => {
  if (!error) return undefined
  if (!Array.isArray(error)) return error.elements
  return error.length > stackIx ? error[stackIx]?.elements : undefined
}

Then use it as:

const errors = getElementErrors(error, stackIx)

143-150: 🛠️ Refactor suggestion

Consider adding delete confirmation.

Deleting an element is irreversible and might be triggered accidentally.

Consider adding a confirmation dialog before deletion:

const handleDelete = () => {
  if (window.confirm('Are you sure you want to delete this element?')) {
    replace(stackIx, {
      ...stack,
      elements: stack.elements
        .slice(0, elementIdx)
        .concat(stack.elements.slice(elementIdx + 1)),
    })
  }
}

44-162: 🛠️ Refactor suggestion

Optimize performance with React.memo and useCallback.

The component might benefit from performance optimizations, especially with large lists.

Consider these optimizations:

  1. Memoize the component:
export default React.memo(WizardElementList)
  1. Extract the element rendering into a separate memoized component:
const WizardElement = React.memo(({ element, onMoveUp, onMoveDown, onDelete, ...props }) => {
  // Current element rendering logic
})
  1. Memoize callback functions:
const handleMoveUp = useCallback((elementIdx: number) => {
  if (!(elementIdx === 0 || stack.elements.length === 1)) {
    replace(stackIx, {
      ...stack,
      elements: swapIndices(stack.elements, elementIdx, elementIdx - 1),
    })
  }
}, [stack, stackIx, replace])

114-120: ⚠️ Potential issue

Fix move down button disabled logic.

The condition for disabling the move down button has an incorrect comparison.

- if (!(stack.elements.length === elementIdx - 1 || stack.elements.length === 1))
+ if (!(elementIdx === stack.elements.length - 1 || stack.elements.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.

                onClick={() => {
                  if (
                    !(
                      elementIdx === stack.elements.length - 1 ||
                      stack.elements.length === 1
                    )
                  ) {
apps/frontend-manage/src/components/sessions/creation/AddStackButton.tsx (4)

80-88: 🛠️ Refactor suggestion

Simplify the hasSampleSolution logic.

The nested ternary operation for hasSampleSolution makes the code harder to read. Consider using a more explicit approach.

-hasSampleSolution:
-  'options' in question
-    ? (question.options.hasSampleSolution ?? false)
-    : true,
+hasSampleSolution: getHasSampleSolution(question),

+// Add this utility function
+const getHasSampleSolution = (question: Element) => {
+  if (!('options' in question)) return true
+  return question.options?.hasSampleSolution ?? false
+}

Committable suggestion was skipped due to low confidence.


11-28: 🛠️ Refactor suggestion

Consider extracting common interface properties.

The AddStackButtonProps and AddBlockButtonProps interfaces share several identical properties. Consider creating a base interface to reduce duplication.

+interface BaseButtonProps {
+  selection?: Record<number, Element>
+  resetSelection?: () => void
+  acceptedTypes: ElementType[]
+}

-interface AddStackButtonProps {
+interface AddStackButtonProps extends BaseButtonProps {
   type: 'stack'
   push: (value: ElementStackFormValues) => void
-  selection?: Record<number, Element>
-  resetSelection?: () => void
-  acceptedTypes: ElementType[]
}

-interface AddBlockButtonProps {
+interface AddBlockButtonProps extends BaseButtonProps {
   type: 'block'
   push: (value: ElementBlockFormValues) => void
-  selection?: Record<number, Element>
-  resetSelection?: () => void
-  acceptedTypes: ElementType[]
}
📝 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.

import { ElementBlockFormValues, ElementStackFormValues } from './WizardLayout'

interface BaseButtonProps {
  selection?: Record<number, Element>
  resetSelection?: () => void
  acceptedTypes: ElementType[]
}

interface AddStackButtonProps extends BaseButtonProps {
  type: 'stack'
  push: (value: ElementStackFormValues) => void
}

interface AddBlockButtonProps extends BaseButtonProps {
  type: 'block'
  push: (value: ElementBlockFormValues) => void
}

172-185: 🛠️ Refactor suggestion

Extract push logic to reduce duplication.

The type-based push logic is repeated across different handlers. Consider extracting it into a reusable function.

+const handlePush = (type: 'block' | 'stack', elements: any[]) => {
+  if (type === 'block') {
+    return {
+      timeLimit: undefined,
+      elements,
+    }
+  }
+  return {
+    displayName: '',
+    description: '',
+    elements,
+  }
+}

-onClick={() => {
-  if (type === 'block') {
-    push({
-      timeLimit: undefined,
-      elements: [],
-    })
-  } else {
-    push({
-      displayName: '',
-      description: '',
-      elements: [],
-    })
-  }
-}}
+onClick={() => push(handlePush(type, []))}
📝 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.

          const handlePush = (type: 'block' | 'stack', elements: any[]) => {
            if (type === 'block') {
              return {
                timeLimit: undefined,
                elements,
              }
            }
            return {
              displayName: '',
              description: '',
              elements,
            }
          }

          onClick={() => push(handlePush(type, []))}

41-61: 🛠️ Refactor suggestion

Extract element creation logic to reduce duplication.

The element creation logic is repeated across different handlers. Consider extracting it into a utility function.

+const createElementFromItem = (item: QuestionDragDropTypes) => ({
+  id: item.id,
+  title: item.title,
+  type: item.questionType,
+  hasSampleSolution: item.hasSampleSolution,
+})

 const initialElements = [
-  {
-    id: item.id,
-    title: item.title,
-    type: item.questionType,
-    hasSampleSolution: item.hasSampleSolution,
-  },
+  createElementFromItem(item),
 ]
📝 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.

        const createElementFromItem = (item: QuestionDragDropTypes) => ({
          id: item.id,
          title: item.title,
          type: item.questionType,
          hasSampleSolution: item.hasSampleSolution,
        })

        const initialElements = [
          createElementFromItem(item),
        ]

        if (type === 'block') {
          push({
            timeLimit: undefined,
            elements: initialElements,
          })
        } else {
          push({
            displayName: '',
            description: '',
            elements: initialElements,
          })
        }
apps/frontend-manage/src/components/sessions/creation/StackBlockCreation.tsx (3)

225-231: 🛠️ Refactor suggestion

Consider using React Context for shared props.

The WizardElementList receives multiple props that might be shared across components. Consider using React Context to manage shared state and reduce prop drilling, especially for props like highlightFTNoSL and error handling.

// Create a new WizardContext.tsx
interface WizardContextType {
  highlightFTNoSL: boolean;
  handleError: (error: ElementStackErrorValues) => void;
  // Add other shared props/handlers
}

export const WizardContext = React.createContext<WizardContextType | undefined>(undefined);

// In parent component
<WizardContext.Provider value={{ highlightFTNoSL, handleError }}>
  <WizardElementList
    stack={stack}
    stackIx={stackIx}
    replace={replace}
  />
</WizardContext.Provider>

243-248: ⚠️ Potential issue

Maintain consistent prop naming.

The DropElementsStack component also uses index prop while the rest of the codebase has migrated to stackIx. Consider updating for consistency.

- index={stackIx}
+ stackIx={stackIx}

Committable suggestion was skipped due to low confidence.


233-240: ⚠️ Potential issue

Maintain consistent prop naming.

The PasteSelectionButton component uses index prop while the rest of the codebase has migrated to stackIx. Consider updating for consistency.

- index={stackIx}
+ stackIx={stackIx}

Committable suggestion was skipped due to low confidence.

apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveSessionWizard.tsx (1)

224-235: ⚠️ Potential issue

Include all dependencies in the useCallback dependency array

In the handleSubmit function defined at lines 224-235, you're using setIsWizardCompleted and setErrorToastOpen but they are not included in the dependency array at line 235. Omitting these dependencies can lead to unexpected behavior due to stale closures.

Apply this diff to include the missing dependencies:

}, 
- [createLiveQuiz, editMode, editLiveQuiz, initialValues?.id]
+ [createLiveQuiz, editMode, editLiveQuiz, initialValues?.id, setIsWizardCompleted, setErrorToastOpen]
📝 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.

    async (values: LiveQuizFormValues) => {
      submitLiveSessionForm({
        id: initialValues?.id,
        editMode,
        values,
        createLiveQuiz,
        editLiveQuiz,
        setIsWizardCompleted,
        setErrorToastOpen,
      })
    },
    [createLiveQuiz, editMode, editLiveQuiz, initialValues?.id, setIsWizardCompleted, setErrorToastOpen]

Copy link

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (8)
apps/frontend-manage/src/components/sessions/creation/liveQuiz/submitLiveQuizForm.tsx (3)

17-36: Update function parameter type

The function parameters should use the new interface name for consistency.

async function submitLiveQuizForm({
  id,
  editMode,
  values,
  createLiveQuiz,
  editLiveQuiz,
  setIsWizardCompleted,
  setErrorToastOpen,
-}: LiveSessionFormProps) {
+}: LiveQuizFormProps) {

Line range hint 77-109: Fix inconsistent multiplier parsing between edit and create modes

There's an inconsistency in how the multiplier is handled:

  • Edit mode has a fallback to 1 when courseId is empty
  • Create mode doesn't have this fallback
-multiplier: parseInt(values.multiplier),
+multiplier: values.courseId !== '' ? parseInt(values.multiplier) : 1,

Line range hint 111-119: Improve error handling and logging

Consider these improvements:

  1. Replace console.log with proper error logging
  2. Provide more specific error feedback to users
  3. Consider adding error boundaries for better error recovery
 } catch (error) {
-  console.log('error: ', error)
+  logger.error('Failed to submit live quiz form:', { error, formValues: values })
+  const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred'
   setErrorToastOpen(true)
+  setErrorMessage(errorMessage)
 }
packages/graphql/src/services/liveQuizzes.ts (1)

1-195: Consider splitting the function for better maintainability.

The manipulateLiveQuiz function handles multiple responsibilities (validation, element processing, quiz creation/update). Consider splitting it into smaller, focused functions:

  1. validateQuizInput - Input validation
  2. processElements - Element retrieval and processing
  3. constructQuizData - Quiz data construction
  4. persistQuiz - Database operations

This would improve:

  • Testability: Each function can be tested in isolation
  • Maintainability: Easier to modify individual parts
  • Reusability: Functions can be reused in other contexts

Would you like me to provide an example of this refactored structure?

🧰 Tools
🪛 Biome

[error] 104-104: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

apps/frontend-manage/src/components/sessions/creation/AddStackButton.tsx (1)

Line range hint 111-202: Verify translation key consistency and fix potential issues.

There seems to be an inconsistency in the translation keys. The condition appears reversed:

-  {type === 'block'
-    ? t('manage.sessionForms.newStackSelected', {
-        count: Object.keys(selection).length,
-      })
-    : t('manage.sessionForms.newBlockSelected', {
-        count: Object.keys(selection).length,
-      })}
+  {type === 'block'
+    ? t('manage.sessionForms.newBlockSelected', {
+        count: Object.keys(selection).length,
+      })
+    : t('manage.sessionForms.newStackSelected', {
+        count: Object.keys(selection).length,
+      })}

Also, consider extracting the empty state objects to constants:

const EMPTY_BLOCK = { timeLimit: undefined, elements: [] }
const EMPTY_STACK = { displayName: '', description: '', elements: [] }

// Usage in onClick:
onClick={() => {
  push(type === 'block' ? EMPTY_BLOCK : EMPTY_STACK)
}}
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveSessionWizard.tsx (3)

Line range hint 1-49: Consider renaming the component for consistency

The component name LiveSessionWizard doesn't align with the new "LiveQuiz" terminology used throughout the codebase. Consider renaming it to LiveQuizWizard for better consistency.

-function LiveSessionWizard({
+function LiveQuizWizard({
   title,
   courses,
   initialValues,
   // ...
 }: LiveSessionWizardProps)

-export default LiveSessionWizard
+export default LiveQuizWizard

Line range hint 117-146: Update validation schema to match new block structure

The questionsValidationSchema still validates the old structure with separate arrays (questionIds, titles, types), but the form now uses an elements array. This mismatch could lead to validation issues.

Update the validation schema to match the new structure:

 const questionsValidationSchema = yup.object().shape({
   blocks: yup.array().of(
     yup.object().shape({
-      questionIds: yup.array().of(yup.number()),
-      titles: yup.array().of(yup.string()),
-      types: yup.array().of(
-        yup.string().oneOf([
-          ElementType.Sc,
-          ElementType.Mc,
-          ElementType.Kprim,
-          ElementType.Numerical,
-          ElementType.FreeText,
-        ])
-      ),
+      elements: yup.array().of(
+        yup.object().shape({
+          questionId: yup.number().required(),
+          title: yup.string().required(),
+          type: yup.string().oneOf([
+            ElementType.Sc,
+            ElementType.Mc,
+            ElementType.Kprim,
+            ElementType.Numerical,
+            ElementType.FreeText,
+          ]).required(),
+        })
+      ),
       timeLimit: yup.number().min(1)
     })
   )
 })

Line range hint 276-296: Add null safety check for createLiveQuiz data

While the condition checks for data?.createLiveQuiz?.id, the subsequent usage doesn't handle potential null values safely.

Add null checks to prevent potential runtime errors:

 {!editMode && data?.createLiveQuiz?.id ? (
   <Button
     data={{ cy: 'quick-start' }}
     onClick={async () => {
+      const quizId = data.createLiveQuiz?.id
+      if (!quizId) return
+
       await startSession({
         variables: {
-          id: data.createLiveQuiz!.id,
+          id: quizId,
         },
       })
-      router.push(`/sessions/${data.createLiveQuiz!.id}/cockpit`)
+      router.push(`/sessions/${quizId}/cockpit`)
     }}
     // ...
   </Button>
 ) : null}
🛑 Comments failed to post (7)
apps/frontend-manage/src/components/sessions/creation/liveQuiz/submitLiveQuizForm.tsx (1)

7-14: 🛠️ Refactor suggestion

Update interface name and improve type safety

The interface name LiveSessionFormProps should be updated to LiveQuizFormProps for consistency with the new terminology. Additionally, the any types for mutations should be replaced with proper GraphQL mutation types.

-interface LiveSessionFormProps {
+interface LiveQuizFormProps {
   id?: string
   editMode: boolean
   values: LiveQuizFormValues
-  createLiveQuiz: any
-  editLiveQuiz: any
+  createLiveQuiz: MutationFunction<CreateLiveQuizMutation, CreateLiveQuizMutationVariables>
+  editLiveQuiz: MutationFunction<EditLiveQuizMutation, EditLiveQuizMutationVariables>
   setIsWizardCompleted: (isCompleted: boolean) => void
   setErrorToastOpen: (isOpen: boolean) => void
 }

Committable suggestion was skipped due to low confidence.

packages/graphql/src/services/liveQuizzes.ts (3)

32-82: 🛠️ Refactor suggestion

Enhance error handling and validation.

While the basic validation is present, consider these improvements:

  1. Add more descriptive error messages with specific failure reasons
  2. Consider validating input parameters (e.g., multiplier range, name length)
  3. Add transaction wrapper to ensure atomic operations

Here's a suggested improvement for error messages and validation:

 if (!oldElement) {
-  throw new GraphQLError('Live quiz not found')
+  throw new GraphQLError('Live quiz not found or you do not have permission to edit it')
 }
 if (oldElement.status === PublicationStatus.PUBLISHED) {
-  throw new GraphQLError('Cannot edit a published live quiz')
+  throw new GraphQLError('Cannot edit a published live quiz. Please unpublish it first.')
 }
+
+// Add input validation
+if (multiplier <= 0) {
+  throw new GraphQLError('Multiplier must be greater than 0')
+}
+if (name.trim().length === 0 || displayName.trim().length === 0) {
+  throw new GraphQLError('Name and display name cannot be empty')
+}

Committable suggestion was skipped due to low confidence.


103-106: ⚠️ Potential issue

Fix O(n²) complexity in element map creation.

The current implementation using spread operator in reduce has O(n²) complexity.

Replace with a more efficient implementation:

-const elementMap = dbElements.reduce<Record<number, Element>>(
-  (acc, elem) => ({ ...acc, [elem.id]: elem }),
-  {}
-)
+const elementMap = Object.fromEntries(
+  dbElements.map((elem) => [elem.id, elem])
+)
📝 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.

  const elementMap = Object.fromEntries(
    dbElements.map((elem) => [elem.id, elem])
  )
🧰 Tools
🪛 Biome

[error] 104-104: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)


168-195: ⚠️ Potential issue

Add transaction boundary and improve error handling.

The upsert operation and cache invalidation could be improved:

  1. Missing transaction boundary for atomic operations
  2. Cache invalidation might not work for new quiz creation (id is undefined)
  3. Potential UUID collision in upsert operation

Here's a suggested improvement:

+  const quizId = id ?? uuidv4()
+  const element = await ctx.prisma.$transaction(async (tx) => {
     const element = await ctx.prisma.liveQuiz.upsert({
-      where: { id: id ?? uuidv4() },
+      where: { id: quizId },
       create: createOrUpdateJSON,
       update: createOrUpdateJSON,
       include: {
         // ... existing include options
       },
     })
+    return element
+  })

   ctx.emitter.emit('invalidate', {
     typename: 'LiveQuiz',
-    id,
+    id: quizId,
   })

Committable suggestion was skipped due to low confidence.

apps/frontend-manage/src/components/sessions/creation/AddStackButton.tsx (3)

11-27: 🛠️ Refactor suggestion

Consider optimizing interface definitions to reduce duplication.

The interfaces share common properties. Consider extracting them into a base interface:

interface BaseButtonProps {
  selection?: Record<number, Element>
  resetSelection?: () => void
  acceptedTypes: ElementType[]
}

interface AddStackButtonProps extends BaseButtonProps {
  type: 'stack'
  push: (value: ElementStackFormValues) => void
}

interface AddBlockButtonProps extends BaseButtonProps {
  type: 'block'
  push: (value: ElementBlockFormValues) => void
}

41-61: 🛠️ Refactor suggestion

Consider extracting repeated element construction logic.

The element construction logic appears in multiple places. Consider extracting it into a utility function:

const createElementFromItem = (item: QuestionDragDropTypes) => ({
  id: item.id,
  title: item.title,
  type: item.questionType,
  hasSampleSolution: item.hasSampleSolution,
})

// Usage in drop handler:
const initialElements = [createElementFromItem(item)]

80-101: 🛠️ Refactor suggestion

Simplify hasSampleSolution logic and reuse element construction.

The hasSampleSolution logic uses a complex nested ternary. Consider simplifying it and reusing the element construction logic:

const getHasSampleSolution = (question: Element) => 
  'options' in question ? question.options.hasSampleSolution ?? false : true

const createElementFromQuestion = (question: Element) => ({
  id: question.id,
  title: question.name,
  type: question.type,
  hasSampleSolution: getHasSampleSolution(question),
})

// Usage:
const elements = Object.values(selection).map(createElementFromQuestion)

Copy link

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 21

🧹 Outside diff range and nitpick comments (36)
packages/graphql/src/graphql/ops/QGetSingleLiveQuiz.graphql (1)

Line range hint 1-32: Consider adding documentation for the new quiz structure.

While the query structure is clear, adding documentation comments to explain the relationship between quizzes, blocks, and elements would improve maintainability.

Consider adding a comment like this at the top of the file:

+# Query to fetch a single live quiz with its blocks and elements.
+# - blocks: Ordered containers that group related elements
+# - elements: Individual interactive components (questions, content, etc.)
 query GetSingleLiveQuiz($quizId: String!) {
apps/frontend-control/src/components/layout/MobileMenuBar.tsx (2)

Line range hint 11-13: Complete the migration from sessionId to quizId

The migration from session-based to quiz-based functionality is incomplete in this component. While the prop passed to EmbeddingModal has been updated to quizId, the interface and other usages still reference sessionId.

Apply this diff to complete the migration:

 interface MobileMenuBarProps {
-  sessionId?: string
+  quizId?: string
 }

-function MobileMenuBar({ sessionId }: MobileMenuBarProps) {
+function MobileMenuBar({ quizId }: MobileMenuBarProps) {
   const t = useTranslations()
   const router = useRouter()
   const [embedModalOpen, setEmbedModalOpen] = useState<boolean>(false)

   // ... rest of the component

   return (
     <div className="fixed bottom-0 h-12 w-full bg-slate-800">
       <div className="flex h-full flex-row justify-between">
         {/* ... other buttons */}
         <MenuButton
           icon={<FontAwesomeIcon icon={faPersonChalkboard} />}
           onClick={() => setEmbedModalOpen(true)}
-          disabled={!sessionId}
+          disabled={!quizId}
           data={{ cy: 'ppt-button' }}
         >
           PPT
         </MenuButton>
       </div>

-      {sessionId && (
+      {quizId && (
         <EmbeddingModal
           open={embedModalOpen}
           setOpen={setEmbedModalOpen}
-          quizId={sessionId}
+          quizId={quizId}
         />
       )}
     </div>
   )
 }

Also applies to: 53-53


Line range hint 41-44: Consider updating the data-cy attribute to reflect quiz terminology

For consistency with the new quiz-based terminology, consider updating the data-cy attribute.

 <MenuButton
   icon={<FontAwesomeIcon icon={faPersonChalkboard} />}
   onClick={() => setEmbedModalOpen(true)}
   disabled={!quizId}
-  data={{ cy: 'ppt-button' }}
+  data={{ cy: 'quiz-presentation-button' }}
 >
   PPT
 </MenuButton>
apps/frontend-control/src/components/sessions/EmbeddingModal.tsx (3)

6-6: Consider relocating file to reflect new quiz-based structure.

While the import and interface changes correctly reflect the migration from sessions to quizzes, the file itself remains in the sessions directory. This could be misleading for future maintenance.

Consider moving this file to a more appropriate directory like components/quizzes/.

Also applies to: 16-16


Line range hint 19-54: Update LazyHMACLink component to use quiz terminology.

The component still uses session-based terminology in its props and implementation while being used with quiz IDs. This creates a type mismatch and maintains outdated terminology.

Apply these changes to maintain consistency:

 function LazyHMACLink({
-  sessionId,
+  quizId,
   params,
 }: {
-  sessionId: string
+  quizId: string
   params: string
 }) {
   const sessionHMAC = useQuery(GetSessionHmacDocument, {
     variables: {
-      id: sessionId,
+      id: quizId,
     },
   })

   if (sessionHMAC.loading || !sessionHMAC.data?.sessionHMAC) {
     return <></>
   }

   const link = `${
     process.env.NEXT_PUBLIC_MANAGE_URL
-  }/sessions/${sessionId}/evaluation?hmac=${sessionHMAC.data?.sessionHMAC}${
+  }/quizzes/${quizId}/evaluation?hmac=${sessionHMAC.data?.sessionHMAC}${
     params ? `&${params}` : ''
   }`

Mixed terminology found in EmbeddingModal component

The component uses inconsistent parameter naming:

  • Props accept quizId but internally uses it as sessionId in LazyHMACLink
  • Uses dataLiveSession for quiz data
  • GraphQL operations mix session and quiz terminology

Locations requiring attention:

  • apps/frontend-control/src/components/sessions/EmbeddingModal.tsx:
    • Line 17: sessionId in LazyHMACLink props should be renamed to quizId
    • Line 24: sessionHMAC query should be renamed to quizHMAC
    • Line 56: Variable dataLiveSession should be renamed to dataLiveQuiz
🔗 Analysis chain

Line range hint 56-110: Verify consistent terminology across the codebase.

Let's ensure the migration from sessions to quizzes is consistent across related components and usages.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other instances of EmbeddingModal usage
echo "Checking EmbeddingModal usage patterns:"
rg "EmbeddingModal.*sessionId" -A 2

# Search for mixed terminology
echo "\nChecking for mixed session/quiz terminology:"
rg -i "session.*quiz|quiz.*session" --type tsx

Length of output: 306


Script:

#!/bin/bash
# Search for EmbeddingModal usage with better pattern
echo "Checking EmbeddingModal usage:"
rg "EmbeddingModal" -A 3

# Search for mixed terminology with proper file type
echo -e "\nChecking for mixed session/quiz terminology:"
rg -i "session.*quiz|quiz.*session" -t ts -t tsx

# Look for files containing EmbeddingModal
echo -e "\nLocating EmbeddingModal files:"
fd -e tsx -e ts "EmbeddingModal"

# Check imports of EmbeddingModal
echo -e "\nChecking imports of EmbeddingModal:"
rg "import.*EmbeddingModal"

Length of output: 6487


Script:

#!/bin/bash
# Let's check the specific terminology in both EmbeddingModal files
echo "Checking frontend-control EmbeddingModal:"
cat apps/frontend-control/src/components/sessions/EmbeddingModal.tsx

echo -e "\nChecking frontend-manage EmbeddingModal:"
cat apps/frontend-manage/src/components/sessions/EmbeddingModal.tsx

# Let's also check for session/quiz terminology with proper extension
echo -e "\nChecking for mixed terminology:"
rg -i "session.*quiz|quiz.*session" -g "*.{ts,tsx}"

Length of output: 41653

apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizQuestionsStep.tsx (2)

9-13: Consider adding JSDoc comments to document the props interface.

Adding documentation for the selection and resetSelection props would improve code maintainability and help other developers understand their purpose in the component.

 interface LiveQuizQuestionsStepProps extends LiveQuizWizardStepProps {
+  /** Record mapping element IDs to their corresponding Element objects */
   selection: Record<number, Element>
+  /** Callback to clear the current element selection */
   resetSelection: () => void
 }

75-80: Consider documenting the architectural change from LiveQuizAddBlockButton to AddStackButton.

The switch to AddStackButton with acceptedTypes suggests a more generic and reusable approach. Consider adding a comment or updating documentation to explain this architectural decision for future maintainers.

apps/frontend-manage/src/components/sessions/creation/liveQuiz/submitLiveQuizForm.tsx (3)

Line range hint 42-75: Improve type parsing and error handling.

  1. The multiplier parsing could be more robust
  2. Consider using nullish coalescing for default values
  3. The success check could be more explicit

Consider these improvements:

-multiplier: values.courseId !== '' ? parseInt(values.multiplier) : 1,
+multiplier: values.courseId ? Number(values.multiplier) ?? 1 : 1,
-maxBonusPoints: parseInt(String(values.maxBonusPoints)),
+maxBonusPoints: Number(values.maxBonusPoints) || 0,
-success = Boolean(liveQuiz.data?.editLiveQuiz)
+success = liveQuiz.data?.editLiveQuiz?.id != null

Line range hint 77-109: Extract common mutation variables logic.

There's significant duplication between create and edit modes. Consider extracting the common mutation variables logic into a helper function.

Example implementation:

const getCommonMutationVariables = (values: LiveQuizFormValues) => ({
  name: values.name,
  displayName: values.displayName,
  description: values.description,
  blocks: blockSubmission,
  courseId: values.courseId,
  multiplier: Number(values.multiplier) ?? 1,
  maxBonusPoints: Number(values.maxBonusPoints) || 0,
  timeToZeroBonus: Number(values.timeToZeroBonus) || 0,
  isGamificationEnabled: Boolean(values.courseId) && values.isGamificationEnabled,
  isConfusionFeedbackEnabled: values.isConfusionFeedbackEnabled,
  isLiveQAEnabled: values.isLiveQAEnabled,
  isModerationEnabled: values.isModerationEnabled,
})

Line range hint 114-119: Enhance error handling with specific error types.

The current error handling is basic. Consider adding specific error handling for different scenarios (validation, network, server errors) with appropriate user messages.

Example implementation:

try {
  // ... existing code ...
} catch (error) {
  console.error('Failed to submit live quiz:', error)
  
  if (error instanceof ValidationError) {
    setErrorToastOpen(true, 'Please check your input values')
  } else if (error instanceof NetworkError) {
    setErrorToastOpen(true, 'Network connection issue. Please try again')
  } else {
    setErrorToastOpen(true, 'An unexpected error occurred')
  }
}
packages/graphql/src/schema/liveQuiz.ts (2)

52-68: Clean up or implement commented code blocks.

There are several commented-out sections that seem to represent important functionality (activeBlock, feedbacks, confusionFeedbacks, confusionSummary). Consider either:

  1. Implementing these features if they're required
  2. Removing the comments if they're no longer needed
  3. Adding TODO comments with tracking issues if they're planned for future

Would you like me to help create GitHub issues to track these pending implementations?


90-90: Define valid status transitions for ElementBlockStatus.

Consider implementing a state machine or validation logic to ensure that status transitions follow a valid flow (e.g., SCHEDULED -> ACTIVE -> COMPLETED).

Would you like me to propose a state machine implementation for managing block status transitions?

apps/frontend-manage/src/components/sessions/cockpit/CancelSessionModal.tsx (1)

Line range hint 1-150: Consider completing the migration to live quiz terminology

While the GraphQL queries have been updated to support live quizzes, the component still uses session-centric terminology throughout. This mixed terminology could lead to confusion and maintenance issues.

Consider:

  1. Renaming the component to CancelLiveQuizModal
  2. Updating props from sessionId to quizId
  3. Updating internal variable names and documentation
  4. Reviewing and updating the translation keys

Example refactoring:

- interface CancelSessionModalProps {
-   sessionId: string
+ interface CancelLiveQuizModalProps {
+   quizId: string
    title: string
    open: boolean
    setOpen: (value: boolean) => void
  }

- function CancelSessionModal({
-   sessionId,
+ function CancelLiveQuizModal({
+   quizId,
    title,
    open,
    setOpen,
- }: CancelSessionModalProps) {
+ }: CancelLiveQuizModalProps) {
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizInformationStep.tsx (1)

Line range hint 89-143: Consider extracting documentation URLs to a configuration file.

The component contains hardcoded documentation URLs which could be challenging to maintain. Consider moving these URLs to a centralized configuration file for easier maintenance and reusability.

Create a new configuration file (e.g., config/documentation.ts):

export const DOCUMENTATION_URLS = {
  LIVE_QUIZ: {
    USE_CASE: 'https://www.klicker.uzh.ch/use_cases/live_quiz/',
    LECTURER: 'https://www.klicker.uzh.ch/tutorials/live_quiz/',
    STUDENT: 'https://www.klicker.uzh.ch/student_tutorials/live_quiz/',
  },
}

Then update the component to use these constants:

- href="https://www.klicker.uzh.ch/use_cases/live_quiz/"
+ href={DOCUMENTATION_URLS.LIVE_QUIZ.USE_CASE}
apps/frontend-manage/src/components/sessions/creation/DescriptionStep.tsx (3)

Line range hint 13-48: Consider extracting common interface properties.

The interfaces MicroLearningDescriptionStepProps, PracticeQuizDescriptionStepProps, LiveQuizDescriptionStepProps, and GroupActivityDescriptionStepProps share identical property definitions. This duplication could be reduced by extracting the common properties into a base interface.

Consider refactoring to:

interface BaseDescriptionStepProps {
  displayNameTooltip: string
  descriptionTooltip: string
  descriptionLabel?: string
  dataDisplayName?: { test?: string; cy?: string }
  dataDescription?: { test?: string; cy?: string }
  descriptionRequired?: boolean
}

interface MicroLearningDescriptionStepProps 
  extends MicroLearningWizardStepProps, BaseDescriptionStepProps {}

interface PracticeQuizDescriptionStepProps 
  extends PracticeQuizWizardStepProps, BaseDescriptionStepProps {}

interface LiveQuizDescriptionStepProps 
  extends LiveQuizWizardStepProps, BaseDescriptionStepProps {}

interface GroupActivityDescriptionStepProps 
  extends GroupActivityWizardStepProps, BaseDescriptionStepProps {}

Line range hint 82-83: Fix TypeScript type assertions.

The FIXME comments indicate TypeScript type inference issues that are currently worked around using any type assertions. This bypasses type safety.

Consider using a generic type parameter for the form values:

function DescriptionStep<T extends object>({
  // ... props
}: DescriptionStepProps<T>) {
  return (
    <Formik<T>
      validateOnMount
      initialValues={formData}
      onSubmit={onNextStep!}
      // ... rest
    >

Line range hint 115-122: Review commented code and hardcoded height.

Two potential improvements in the EditorField implementation:

  1. There's a commented out key prop that should be either removed or implemented
  2. The preview section uses a hardcoded height (h-24) which might not be responsive to different content lengths

Consider:

  1. Removing the commented out code if it's no longer needed
  2. Using a more flexible height approach:
-              className={{ root: 'h-24' }}
+              className={{ root: 'min-h-24 max-h-96 overflow-y-auto' }}
apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizSettingsStep.tsx (2)

Line range hint 89-93: Consider passing numeric values directly.

The maxBonusPoints and timeToZeroBonus values are being converted to strings before passing to AdvancedLiveQuizSettings. Consider passing the numeric values directly if the component can handle them.

-maxBonusValue={String(values.maxBonusPoints)}
-timeToZeroValue={String(values.timeToZeroBonus)}
+maxBonusValue={values.maxBonusPoints}
+timeToZeroValue={values.timeToZeroBonus}

Line range hint 82-82: Review z-index usage.

The tooltip's z-index is set to 20. Consider using design system constants for z-index values to maintain consistency across the application.

-className={{ tooltip: 'z-20' }}
+className={{ tooltip: 'z-tooltip' }} // Assuming design system provides z-index tokens
packages/graphql/src/services/liveQuizzes.ts (3)

189-192: Add error handling for cache invalidation.

The cache invalidation event emission lacks error handling, which could silently fail.

Consider wrapping the emit in a try-catch:

- ctx.emitter.emit('invalidate', {
-   typename: 'LiveQuiz',
-   id,
- })
+ try {
+   ctx.emitter.emit('invalidate', {
+     typename: 'LiveQuiz',
+     id,
+   })
+ } catch (error) {
+   console.error('Failed to invalidate cache:', error)
+   // Consider if this should be a fatal error or just logged
+ }

52-65: Security: Consider adding a transaction for atomic operations.

The quiz lookup and subsequent operations should be atomic to prevent race conditions.

Consider wrapping the operations in a transaction:

await ctx.prisma.$transaction(async (tx) => {
  const oldElement = await tx.liveQuiz.findUnique({...})
  // ... validation logic ...
  await tx.liveQuiz.update({...})
})

209-229: Maintain naming consistency during migration.

The variable session should be renamed to liveQuiz to maintain consistency with the new terminology.

- const session = await ctx.prisma.liveQuiz.findUnique({
+ const liveQuiz = await ctx.prisma.liveQuiz.findUnique({
    // ... query options ...
  })

- return session
+ return liveQuiz
apps/frontend-manage/src/components/sessions/creation/ElementCreation.tsx (2)

Line range hint 63-104: Consider extracting repeated skip logic into a utility function.

The skip conditions across queries follow a similar pattern. Consider extracting this logic into a utility function to improve maintainability:

const shouldSkipQuery = (
  activityId: string | undefined,
  targetMode: WizardMode,
  currentEditMode: string | undefined,
  currentDuplicationMode: WizardMode | undefined,
  conversionMode?: string
) => {
  return (
    !activityId ||
    (currentEditMode !== targetMode && currentDuplicationMode !== targetMode) ||
    (conversionMode && conversionMode === 'microLearningToPracticeQuiz')
  )
}

// Usage example:
const { data: dataLiveQuiz, loading: liveLoading } = useQuery(
  GetSingleLiveQuizDocument,
  {
    variables: { quizId: activityId || '' },
    skip: shouldSkipQuery(
      activityId,
      WizardMode.LiveQuiz,
      editMode,
      duplicationMode,
      conversionMode
    ),
  }
)

Line range hint 141-162: Consider simplifying loading conditions.

The loading conditions are verbose and could be simplified using array methods:

const loadingStates = [
  { condition: !errorCourses && loadingCourses },
  {
    condition: activityId && 
               [editMode, duplicationMode].includes(WizardMode.LiveQuiz) && 
               liveLoading
  },
  // ... other conditions
]

if (loadingStates.some(({ condition }) => condition)) {
  return <Loader />
}
apps/frontend-manage/src/components/sessions/LiveQuiz.tsx (4)

Line range hint 54-63: Consider renaming StartSession mutation for consistency

The mutation name StartSession doesn't align with the new live quiz nomenclature. Consider renaming it to StartLiveQuiz for consistency with other mutations like DeleteLiveQuiz.


111-111: Standardize data-cy attributes

There's inconsistent naming in data-cy attributes:

  • Some use session- prefix (e.g., session-cockpit-)
  • Others use live-quiz- prefix (e.g., delete-live-quiz-)

Standardize all attributes to use the live-quiz- prefix for consistency.

Also applies to: 138-138, 178-178, 193-193, 211-211, 290-290


Line range hint 251-258: Enhance button accessibility

The icon-only buttons should have aria-labels for better accessibility:

 <Button.Icon className={{ root: 'text-slate-600' }}>
-  <FontAwesomeIcon icon={faCopy} />
+  <FontAwesomeIcon icon={faCopy} aria-label={t('manage.sessions.duplicateSession')} />
 </Button.Icon>

Also applies to: 272-280, 290-298


Line range hint 326-342: Use translation keys for element type

The element type is directly interpolated:

{t(`shared.${instance.elementData!.type}.short`)}

Consider defining an enum or constant for supported element types to ensure type safety and maintainability.

enum ElementType {
  SINGLE_CHOICE = 'singleChoice',
  MULTIPLE_CHOICE = 'multipleChoice',
  // ... other types
}

// Usage
{t(`shared.${ElementType[instance.elementData!.type]}.short`)}
apps/frontend-manage/src/pages/index.tsx (2)

Line range hint 161-166: Consider aligning query parameter name with prop name for consistency.

The prop name activityId is being passed a value from router.query.elementId. This inconsistency between the query parameter name and the prop name could lead to confusion. Consider either:

  1. Updating the query parameter name to match the new prop name, or
  2. Adding a comment explaining the reason for the different naming.

Additionally, consider adding type safety improvements:

- activityId={router.query.elementId as string}
- editMode={router.query.editMode as string}
- conversionMode={router.query.conversionMode as string}
+ activityId={router.query.elementId?.toString()}
+ editMode={router.query.editMode?.toString()}
+ conversionMode={router.query.conversionMode?.toString()}

Line range hint 71-85: Consider refactoring mode handling logic for better maintainability.

The useEffect hook handles three different modes (edit, duplication, conversion) with similar logic. Consider:

  1. Extracting the mode determination logic into a separate function for better maintainability.
  2. Using type-safe query parameter handling.
+ const determineCreationMode = (query: ParsedUrlQuery): WizardMode | undefined => {
+   if (query.elementId) {
+     if (query.editMode) return query.editMode as WizardMode
+     if (query.duplicationMode) return query.duplicationMode as WizardMode
+     if (query.conversionMode) return query.conversionMode as WizardMode
+   }
+   return undefined
+ }

  useEffect((): void => {
    router.prefetch('/sessions/running')
    router.prefetch('/sessions')

-   if (router.query.elementId && router.query.editMode) {
-     setCreationMode(router.query.editMode as WizardMode)
-   } else if (router.query.elementId && router.query.duplicationMode) {
-     setCreationMode(router.query.duplicationMode as WizardMode)
-   } else if (router.query.elementId && router.query.conversionMode) {
-     setCreationMode(router.query.conversionMode as WizardMode)
-   }
+   setCreationMode(determineCreationMode(router.query))
  }, [router])
packages/graphql/src/public/schema.graphql (1)

366-376: Add documentation for the ElementBlock type and its fields.

Consider adding descriptions for the type and its fields to improve schema documentation and API clarity.

+"""
+Represents a block of elements within a live quiz, managing execution state and timing.
+"""
 type ElementBlock {
+  """
+  List of element instances contained in this block
+  """
   elements: [ElementInstance!]
+  """
+  Number of times this block has been executed
+  """
   execution: Int
+  """
+  Timestamp when this block expires
+  """
   expiresAt: Date
   id: Int!
   numOfParticipants: Int
   order: Int!
   randomSelection: Int
   status: ElementBlockStatus!
   timeLimit: Int
 }
packages/graphql/src/public/server.json (1)

37-37: Consider handling concurrent edits.

The EditLiveQuiz mutation should handle scenarios where multiple users attempt to edit the same quiz simultaneously. Consider implementing optimistic concurrency control using version numbers or timestamps.

packages/graphql/src/ops.schema.json (2)

10532-10882: Consider adding field descriptions for better documentation.

The LiveQuiz type is well-structured with comprehensive fields and proper type constraints. However, adding descriptions for fields would improve schema documentation and API usability.

Consider adding descriptions for key fields, especially for feature flags and configuration options. For example:

 {
   "name": "isConfusionFeedbackEnabled",
-  "description": null,
+  "description": "Controls whether participants can provide confusion feedback during the quiz",
   "type": {
     "kind": "NON_NULL",
     "name": null,
     "ofType": {
       "kind": "SCALAR",
       "name": "Boolean",
       "ofType": null
     }
   }
 }

Line range hint 12214-12409: Consider extracting common input fields into a shared type.

The createLiveQuiz and editLiveQuiz mutations share many input fields. Consider creating a shared input type to reduce duplication and improve maintainability.

Create a shared input type for common fields:

input LiveQuizInput {
  blocks: [ElementBlockInput!]!
  courseId: String
  description: String
  displayName: String!
  isConfusionFeedbackEnabled: Boolean!
  isGamificationEnabled: Boolean!
  isLiveQAEnabled: Boolean!
  isModerationEnabled: Boolean!
  maxBonusPoints: Int
  multiplier: Int!
  name: String!
  timeToZeroBonus: Int
}

# Then use it in mutations
type Mutation {
  createLiveQuiz(input: LiveQuizInput!): LiveQuiz
  editLiveQuiz(id: String!, input: LiveQuizInput!): LiveQuiz
}

Also applies to: 13444-13645

apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizWizard.tsx (1)

Line range hint 292-312: Update navigation URL to reflect LiveQuiz routes

In the completion step, the application navigates to /sessions/${data.createLiveQuiz!.id}/cockpit after starting the live quiz. Since we have transitioned from sessions to live quizzes, the URL should be updated to reflect this change to avoid confusion and ensure correct routing.

Suggested Change:

Update the URL path to /liveQuizzes/${data.createLiveQuiz!.id}/cockpit or the appropriate path for live quizzes.

Apply this diff to update the navigation:

 router.push(
-  `/sessions/${data.createLiveQuiz!.id}/cockpit`
+  `/liveQuizzes/${data.createLiveQuiz!.id}/cockpit`
)
🛑 Comments failed to post (21)
apps/frontend-control/src/components/sessions/EmbeddingModal.tsx (1)

99-99: ⚠️ Potential issue

Update LazyHMACLink usage after component changes.

Once the LazyHMACLink component is updated as suggested above, update these usages to use the new prop name:

-<LazyHMACLink sessionId={quizId} params={`questionIx=${ix}`} />
+<LazyHMACLink quizId={quizId} params={`questionIx=${ix}`} />

-<LazyHMACLink sessionId={quizId} params={`leaderboard=true`} />
+<LazyHMACLink quizId={quizId} params={`leaderboard=true`} />

Also applies to: 107-107

apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizQuestionsStep.tsx (1)

72-72: ⚠️ Potential issue

Avoid type assertion to 'any' for error prop.

Using any type assertion reduces type safety. Consider defining a proper type for the errors structure.

-error={errors.blocks as any}
+error={errors.blocks as FormikErrors<typeof block>[]}

Committable suggestion was skipped due to low confidence.

apps/frontend-manage/src/components/sessions/creation/liveQuiz/submitLiveQuizForm.tsx (2)

26-36: 🛠️ Refactor suggestion

Consider adding input validation.

While the transformation logic is correct, consider adding validation for required fields and array contents before processing.

Example implementation:

const blockSubmission = values.blocks.map((block: ElementBlockFormValues, ix) => {
  if (!block.elements?.length) {
    throw new Error(`Block ${ix} must contain at least one element`)
  }
  
  return {
    order: ix,
    timeLimit: block.timeLimit,
    elements: block.elements.map((element, ix) => {
      if (!element.id) {
        throw new Error(`Element at index ${ix} must have an ID`)
      }
      return { elementId: element.id, order: ix }
    }),
  }
})

7-14: ⚠️ Potential issue

Improve type safety and naming consistency.

  1. The interface name LiveSessionFormProps should be updated to LiveQuizFormProps for consistency.
  2. The GraphQL mutation types should be properly typed instead of using any.

Apply this change:

-interface LiveSessionFormProps {
+interface LiveQuizFormProps {
   id?: string
   editMode: boolean
   values: LiveQuizFormValues
-  createLiveQuiz: any
-  editLiveQuiz: any
+  createLiveQuiz: MutationFunction<CreateLiveQuizMutation, CreateLiveQuizMutationVariables>
+  editLiveQuiz: MutationFunction<EditLiveQuizMutation, EditLiveQuizMutationVariables>
   setIsWizardCompleted: (isCompleted: boolean) => void
   setErrorToastOpen: (isOpen: boolean) => void
 }

Committable suggestion was skipped due to low confidence.

apps/frontend-manage/src/components/sessions/EmbeddingModal.tsx (1)

106-113: 🛠️ Refactor suggestion

Improve element name handling

Consider the following improvements:

  1. Extract the name truncation logic to a reusable utility function
  2. Define the maximum length (25) as a named constant
  3. Add optional chaining for safer property access
+const MAX_ELEMENT_NAME_LENGTH = 25;
+const truncateName = (name: string) => 
+  name.length > MAX_ELEMENT_NAME_LENGTH 
+    ? `${name.substring(0, MAX_ELEMENT_NAME_LENGTH)}...` 
+    : name;

 {elements?.map((element, ix) => {
   return (
     <div key={element.id}>
       <div className="font-bold">
         {ix + 1}{' '}
-        {element.elementData.name.length > 25
-          ? `${element.elementData.name.substring(0, 25)}...`
-          : element.elementData.name}
+        {truncateName(element.elementData?.name ?? '')}
       </div>
📝 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.

        const MAX_ELEMENT_NAME_LENGTH = 25;
        const truncateName = (name: string) => 
          name.length > MAX_ELEMENT_NAME_LENGTH 
            ? `${name.substring(0, MAX_ELEMENT_NAME_LENGTH)}...` 
            : name;

        {elements?.map((element, ix) => {
          return (
            <div key={element.id}>
              <div className="font-bold">
                {ix + 1}{' '}
                {truncateName(element.elementData?.name ?? '')}
              </div>
packages/graphql/src/schema/liveQuiz.ts (2)

38-40: ⚠️ Potential issue

Add field validation for game mechanics parameters.

Consider adding validation for these numeric fields:

  • pointsMultiplier should probably be positive
  • maxBonusPoints should be non-negative
  • timeToZeroBonus should be positive

Would you like me to help implement field validation using the builder pattern?


92-95: 💡 Codebase verification

⚠️ Potential issue

Based on the search results, I can now generate the final response:

Add input validation for timing and selection parameters in ElementBlockInput

The codebase currently lacks validation for these numeric fields. Add the following constraints to the ElementBlockInput type:

  • timeLimit: Add validate: { min: 1 } to ensure positive values
  • randomSelection: Add validate: { min: 1 } to ensure valid selection count
  • execution: Add JSDoc comment explaining its purpose and appropriate validation

Example implementation in schema definition:

timeLimit: t.int({ 
  required: false,
  validate: { min: 1 }
}),
randomSelection: t.int({
  required: false, 
  validate: { min: 1 }
}),
🔗 Analysis chain

Consider adding validation for timing and selection parameters.

The following fields might benefit from validation constraints:

  • timeLimit should be positive when defined
  • randomSelection should be within reasonable bounds
  • execution purpose is unclear and might need documentation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for any existing validation logic
rg "timeLimit.*validate"
rg "randomSelection.*validate"

Length of output: 59


Script:

#!/bin/bash
# Let's check the broader context of these fields usage and validation
# First, let's look for the model/type definitions
ast-grep --pattern 'interface $_LiveQuiz {
  $$$
  timeLimit?: $_
  $$$
}'

# Check for any validation logic in nearby files
rg -A 5 -B 5 "timeLimit|randomSelection|execution" packages/graphql/src/schema/

# Look for any validation decorators or schema definitions
rg "@.*validate|@.*constraint|@.*check" packages/graphql/src/

# Check if there's a validation middleware or similar
rg -g "*.ts" "validate|constraint|check" packages/graphql/src/

Length of output: 16890


Script:

#!/bin/bash
# Let's check for input validation in the mutation handlers
rg -A 10 "createLiveQuiz|updateLiveQuiz|createElementBlock" packages/graphql/src/

# Look for any resolver implementations related to these fields
ast-grep --pattern 'createLiveQuiz($$$) {
  $$$
}'

# Check for any validation utilities or helpers
rg -g "*.ts" "validateTimeLimit|validateQuiz|validateBlock" packages/graphql/src/

Length of output: 59435

apps/frontend-control/src/components/sessions/SessionLists.tsx (1)

109-109: ⚠️ Potential issue

Inconsistent state variable and prop naming.

While the prop has been renamed to quizId, the state variable sessionId is still using the old terminology. This creates confusion and makes the code harder to maintain.

Apply this change to maintain consistency:

- const [sessionId, setSessionId] = useState('')
+ const [quizId, setQuizId] = useState('')

  // Update all usage of sessionId to quizId
- setSessionId(session.id)
+ setQuizId(session.id)

  <EmbeddingModal
    open={embedOpen}
    setOpen={(newValue: boolean) => setEmbedOpen(newValue)}
-   quizId={sessionId}
+   quizId={quizId}
  />

Committable suggestion was skipped due to low confidence.

packages/graphql/src/services/liveQuizzes.ts (3)

273-276: ⚠️ Potential issue

Add null check for block elements access.

Accessing block.elements[0] directly could throw if the elements array is empty.

- numOfParticipants: block.elements[0]
-   ? block.elements[0].results.total +
-     block.elements[0].anonymousResults.total
-   : 0,
+ numOfParticipants: block.elements.length > 0
+   ? block.elements[0].results.total +
+     block.elements[0].anonymousResults.total
+   : 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.

      numOfParticipants: block.elements.length > 0
        ? block.elements[0].results.total +
          block.elements[0].anonymousResults.total
        : 0,

103-106: ⚠️ Potential issue

Performance: Optimize elementMap creation.

The spread operator in the reduce accumulator creates a new object on each iteration, leading to O(n²) complexity.

Replace with direct property assignment:

- const elementMap = dbElements.reduce<Record<number, Element>>(
-   (acc, elem) => ({ ...acc, [elem.id]: elem }),
-   {}
- )
+ const elementMap = dbElements.reduce<Record<number, Element>>((acc, elem) => {
+   acc[elem.id] = elem
+   return acc
+ }, {})
📝 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.

  const elementMap = dbElements.reduce<Record<number, Element>>((acc, elem) => {
    acc[elem.id] = elem
    return acc
  }, {})
🧰 Tools
🪛 Biome

[error] 104-104: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)


269-284: 🛠️ Refactor suggestion

Optimize data transformation and maintain naming consistency.

The function uses legacy 'session' terminology and could benefit from performance optimizations.

- return user?.liveQuizzes.map((session) => ({
+ return user?.liveQuizzes.map((quiz) => ({
-   ...session,
+   ...quiz,
-   blocks: session.blocks.map((block) => ({
+   blocks: quiz.blocks.map((block) => {
+     const firstElement = block.elements[0]
+     return {
      ...block,
-     numOfParticipants: block.elements[0]
-       ? block.elements[0].results.total +
-         block.elements[0].anonymousResults.total
+     numOfParticipants: firstElement
+       ? firstElement.results.total +
+         firstElement.anonymousResults.total
        : 0,
-   })),
+   }}),
-   course: session.course ? session.course : undefined,
+   course: quiz.course ?? undefined,
-   numOfBlocks: session._count?.blocks,
+   numOfBlocks: quiz._count?.blocks,
    numOfInstances: session.blocks.reduce(
      (acc, block) => acc + block._count?.elements,
      0
    ),
  }))

Committable suggestion was skipped due to low confidence.

apps/frontend-manage/src/components/sessions/creation/ElementCreation.tsx (1)

201-214: 🛠️ Refactor suggestion

Consider simplifying initial values logic.

The nested ternary operations for initial values could be simplified:

const getInitialValues = (data: any, mode: WizardMode) => {
  if (!data?.liveQuiz) return undefined
  
  return mode === WizardMode.LiveQuiz
    ? { ...data.liveQuiz, name: `${data.liveQuiz.name} (Copy)` }
    : data.liveQuiz
}

// Usage
initialValues={getInitialValues(dataLiveQuiz, duplicationMode)}
apps/frontend-manage/src/components/sessions/LiveQuiz.tsx (1)

67-86: 🛠️ Refactor suggestion

Add type safety to cache update logic

The cache update logic could benefit from explicit typing:

    update(cache) {
-     const data = cache.readQuery({
+     const data = cache.readQuery<{
+       userLiveQuizzes: LiveQuizType[]
+     }>({
        query: GetUserLiveQuizzesDocument,
      })

Committable suggestion was skipped due to low confidence.

packages/graphql/src/schema/query.ts (1)

480-488: 💡 Codebase verification

Migration from liveSession to liveQuiz is incomplete

Several files still contain active references to the old liveSession schema and functionality:

  • packages/graphql/src/services/sessions.ts: Contains multiple active Prisma queries using liveSession model
  • packages/graphql/src/services/feedbacks.ts: Still using liveSession for feedback queries
  • packages/graphql/src/services/accounts.ts: Contains liveSession creation logic
  • Frontend components in apps/frontend-pwa/src/components/liveSession/ still use old naming
  • Frontend pages still reference liveSession in tab values and component imports

The migration requires:

  1. Updating all Prisma queries from liveSession to liveQuiz
  2. Migrating frontend components from liveSession to liveQuiz naming
  3. Updating tab values and references in frontend pages
🔗 Analysis chain

Verify complete migration of live session functionality

The field migration from liveSession to liveQuiz looks correct. However, we should verify that all consumers of this field have been updated.


Let's examine the actual usage of liveSession in the files to determine if they are stale references or active code:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining references to liveSession
# and verify all consumers are updated to use liveQuiz

echo "Checking for remaining references to liveSession..."
rg -l 'liveSession'

echo "Verifying consumers are updated to use liveQuiz..."
rg -l 'liveQuiz'

Length of output: 3185


Script:

#!/bin/bash
# Check actual usage of liveSession in relevant files
echo "Checking liveSession usage in frontend files..."
rg -A 2 -B 2 'liveSession' 'apps/frontend-pwa/src/pages/session/[id].tsx' 'apps/frontend-manage/src/pages/courses/[id].tsx'

echo -e "\nChecking liveSession usage in GraphQL services..."
rg -A 2 -B 2 'liveSession' 'packages/graphql/src/services/feedbacks.ts' 'packages/graphql/src/services/accounts.ts' 'packages/graphql/src/services/sessions.ts'

echo -e "\nChecking if these are just in comments or actual code..."
rg -n 'liveSession' --type ts

Length of output: 17820

packages/graphql/src/public/schema.graphql (2)

1019-1019: 🛠️ Refactor suggestion

Consolidate duplicate input parameters into a shared input type.

Both createLiveQuiz and editLiveQuiz mutations share many parameters. Consider creating a shared input type.

+"""
+Common input fields for live quiz operations
+"""
+input LiveQuizInput {
+  blocks: [ElementBlockInput!]!
+  courseId: String
+  description: String
+  displayName: String!
+  isConfusionFeedbackEnabled: Boolean!
+  isGamificationEnabled: Boolean!
+  isLiveQAEnabled: Boolean!
+  isModerationEnabled: Boolean!
+  maxBonusPoints: Int
+  multiplier: Int!
+  name: String!
+  timeToZeroBonus: Int
+}

-  createLiveQuiz(blocks: [ElementBlockInput!]!, courseId: String, description: String, displayName: String!, isConfusionFeedbackEnabled: Boolean!, isGamificationEnabled: Boolean!, isLiveQAEnabled: Boolean!, isModerationEnabled: Boolean!, maxBonusPoints: Int, multiplier: Int!, name: String!, timeToZeroBonus: Int): LiveQuiz
+  createLiveQuiz(input: LiveQuizInput!): LiveQuiz

-  editLiveQuiz(blocks: [ElementBlockInput!]!, courseId: String, description: String, displayName: String!, id: String!, isConfusionFeedbackEnabled: Boolean!, isGamificationEnabled: Boolean!, isLiveQAEnabled: Boolean!, isModerationEnabled: Boolean!, maxBonusPoints: Int, multiplier: Int!, name: String!, timeToZeroBonus: Int): LiveQuiz
+  editLiveQuiz(id: String!, input: LiveQuizInput!): LiveQuiz

Also applies to: 1038-1038


943-967: 🛠️ Refactor suggestion

Consider adding constraints and improving field organization.

The LiveQuiz type could benefit from:

  1. Non-nullable timestamps for audit fields
  2. Logical grouping of access control fields
  3. Constraints on numeric fields
 type LiveQuiz {
   accessMode: LiveQuizAccessMode!
   blocks: [ElementBlock!]
   course: Course
-  createdAt: Date!
-  description: String
+  createdAt: Date! @deprecated(reason: "Use audit.createdAt")
+  description: String @deprecated(reason: "Use metadata.description")
   displayName: String!
   finishedAt: Date
   id: ID!
+  """
+  Access control configuration
+  """
   isConfusionFeedbackEnabled: Boolean!
   isGamificationEnabled: Boolean!
   isLiveQAEnabled: Boolean!
   isModerationEnabled: Boolean!
-  maxBonusPoints: Int!
+  maxBonusPoints: Int! @constraint(min: 0)
   name: String!
   namespace: String!
   numOfBlocks: Int
   numOfInstances: Int
   pinCode: Int
-  pointsMultiplier: Int!
+  pointsMultiplier: Int! @constraint(min: 1)
   startedAt: Date
   status: PublicationStatus!
-  timeToZeroBonus: Int!
-  updatedAt: Date
+  timeToZeroBonus: Int! @constraint(min: 0)
+  updatedAt: Date!
+  """
+  Audit metadata
+  """
+  audit: AuditMetadata!
+  """
+  Display metadata
+  """
+  metadata: DisplayMetadata!
 }

+"""
+Audit information for tracking entity changes
+"""
+type AuditMetadata {
+  createdAt: Date!
+  updatedAt: Date!
+}

+"""
+Display-related metadata
+"""
+type DisplayMetadata {
+  description: String
+  displayName: String!
+  name: String!
+}

Committable suggestion was skipped due to low confidence.

packages/graphql/src/public/server.json (1)

151-151: 🛠️ Refactor suggestion

Consider implementing pagination for blocks.

The GetSingleLiveQuiz query returns all blocks and their elements in a single request. For quizzes with many blocks or elements, consider implementing pagination to improve performance and reduce payload size.

Example implementation:

type PaginatedBlocks {
  blocks: [Block!]!
  pageInfo: PageInfo!
}

type PageInfo {
  hasNextPage: Boolean!
  endCursor: String
}

type Query {
  getSingleLiveQuiz(id: String!, first: Int, after: String): LiveQuiz
}
apps/frontend-manage/src/pages/sessions/index.tsx (3)

20-20: ⚠️ Potential issue

Handle error state from GraphQL query

Currently, the error state from the useQuery hook is not handled. If the query fails, users may not receive any feedback. Consider adding error handling to provide a better user experience.

Apply this diff to handle the error state:

- const { loading, data } = useQuery(GetUserLiveQuizzesDocument)
+ const { loading, error, data } = useQuery(GetUserLiveQuizzesDocument)

- if (!data || loading) {
+ if (loading) {
    return (
      <Layout displayName="Sessions">
        <Loader />
      </Layout>
    )
+ } else if (error) {
+   return (
+     <Layout displayName="Sessions">
+       <UserNotification
+         type="error"
+         message="Failed to load live quizzes."
+       />
+     </Layout>
+   )
  }

Also applies to: 46-52


23-26: 🛠️ Refactor suggestion

Consider renaming variables to reflect 'live quizzes'

The variables runningSessions, scheduledSessions, preparedSessions, and completedSessions still use 'session' terminology, although the data now represents live quizzes. For consistency and clarity, consider renaming these variables to runningLiveQuizzes, scheduledLiveQuizzes, preparedLiveQuizzes, and completedLiveQuizzes.

Also applies to: 29-32, 35-38, 41-44


62-62: 🛠️ Refactor suggestion

Ensure consistent naming in map functions

In the mapping functions, the parameter is named session, but it represents a live quiz. For clarity, consider renaming the parameter to quiz.

Apply this diff:

- {runningSessions.map((session) => (
-   <Session key={session.id} quiz={session} />
+ {runningLiveQuizzes.map((quiz) => (
+   <Session key={quiz.id} quiz={quiz} />

Repeat similar changes for scheduledSessions, preparedSessions, and completedSessions.

Also applies to: 72-72, 82-82, 92-92

apps/frontend-manage/src/components/sessions/creation/liveQuiz/LiveQuizWizard.tsx (1)

197-214: ⚠️ Potential issue

Handle potential null values and ensure type safety

There are a few potential issues in the initialization of blocks:

  1. Use of non-null assertion (!) on block.elements

    Using block.elements! assumes that elements is always defined, which might not be the case. This could lead to runtime errors if elements is null or undefined.

  2. Parsing element.elementData.id

    The id is being parsed with parseInt, but if element.elementData.id is already a number, this may be unnecessary.

  3. Missing radix parameter in parseInt

    It's best practice to provide the radix parameter when using parseInt to avoid unexpected results.

Apply this diff to address the issues:

 blocks: initialValues?.blocks
   ? initialValues.blocks.map((block) => {
       return {
         timeLimit: block.timeLimit ?? undefined,
-        elements: block.elements!.map((element) => {
+        elements: block.elements?.map((element) => {
           return {
-            id: parseInt(element.elementData.id),
+            id: parseInt(element.elementData.id, 10),
             title: element.elementData.name,
             type: element.elementData.type,
             hasSampleSolution:
               'options' in element.elementData
                 ? (element.elementData.options.hasSampleSolution ?? false)
                 : true,
           }
-        })
+        }) || [],
       }
     })
   : formDefaultValues.blocks,
📝 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.

    blocks: initialValues?.blocks
      ? initialValues.blocks.map((block) => {
          return {
            timeLimit: block.timeLimit ?? undefined,
            elements: block.elements?.map((element) => {
              return {
                id: parseInt(element.elementData.id, 10),
                title: element.elementData.name,
                type: element.elementData.type,
                hasSampleSolution:
                  'options' in element.elementData
                    ? (element.elementData.options.hasSampleSolution ?? false)
                    : true,
              }
            }) || [],
          }
        })
      : formDefaultValues.blocks,

Copy link

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 19

🧹 Outside diff range and nitpick comments (13)
packages/graphql/src/graphql/ops/QGetUserRunningLiveQuizzes.graphql (1)

1-6: Consider adding pagination for scalability.

For users who might have many running live quizzes, consider implementing pagination using cursor-based or offset-based pagination. This would improve performance and reduce unnecessary data transfer.

Example implementation:

query GetUserRunningLiveQuizzes($first: Int, $after: String) {
  userRunningLiveQuizzes(first: $first, after: $after) {
    edges {
      node {
        id
        name
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
packages/graphql/src/schema/liveQuiz.ts (2)

38-40: Consider adding JSDoc comments for gamification fields.

The gamification-related fields (pointsMultiplier, maxBonusPoints, timeToZeroBonus) would benefit from documentation explaining their purpose and relationships.

Add JSDoc comments like this:

+  /** Multiplier applied to base points for time-based scoring */
   pointsMultiplier: t.exposeInt('pointsMultiplier'),
+  /** Maximum bonus points available at the start of the quiz */
   maxBonusPoints: t.exposeInt('maxBonusPoints'),
+  /** Time in seconds until bonus points reduce to zero */
   timeToZeroBonus: t.exposeInt('timeToZeroBonus'),

94-115: Document field purposes and units.

Several fields would benefit from clear documentation:

  • timeLimit: Specify the time unit (seconds/minutes)
  • randomSelection: Document the purpose and valid values
  • execution: Clarify the meaning and usage

Add documentation like this:

+  /** Time limit for the block in seconds */
   timeLimit: t.exposeInt('timeLimit', { nullable: true }),
+  /** Number of elements to randomly select from the pool */
   randomSelection: t.exposeInt('randomSelection', { nullable: true }),
+  /** Execution count/attempt number */
   execution: t.exposeInt('execution', { nullable: true }),
apps/frontend-control/src/pages/session/[id].tsx (2)

38-51: Cache update implementation looks good with room for improvement

The cache update logic correctly manages the removal of ended sessions. The implementation includes proper null safety checks and uses modern JavaScript features appropriately.

Consider adding type safety for the cache read operation:

 update(cache, res) {
   const data = cache.readQuery({
     query: GetUserRunningLiveQuizzesDocument,
+    // Add type parameter to ensure type safety
+  }) ?? { userRunningLiveQuizzes: [] };
   cache.writeQuery({
     query: GetUserRunningLiveQuizzesDocument,
     data: {
       userRunningLiveQuizzes:
         data?.userRunningLiveQuizzes?.filter(
           (q) => q.id !== res.data?.endSession?.id
         ) ?? [],
     },
   })
 }

Line range hint 54-60: Consider making polling interval configurable

The polling interval is currently hardcoded to 1000ms. Consider making this configurable through environment variables or constants to allow for different environments (development/production) to use different intervals.

+ // Define at component or environment level
+ const POLLING_INTERVAL = process.env.NEXT_PUBLIC_POLLING_INTERVAL ?? 1000;

  useQuery(GetControlSessionDocument, {
    variables: {
      id: router.query.id as string,
    },
-   pollInterval: 1000,
+   pollInterval: POLLING_INTERVAL,
    skip: !router.query.id,
  })
apps/frontend-manage/src/components/sessions/LiveQuiz.tsx (2)

Line range hint 335-351: Enhance accessibility for interactive elements

The link elements could benefit from improved accessibility attributes.

 <Link
   href={`/questions/${instance.elementData!.elementId}`}
   className="text-sm hover:text-slate-700"
   key={instance.id}
   legacyBehavior
   passHref
 >
   <a
     data-cy={`open-question-live-quiz-${instance.id}`}
     target="_blank"
     rel="noopener noreferrer"
+    aria-label={`Open question: ${instance.elementData?.name}`}
+    role="link"
   >

122-122: Address TODO comment regarding Tailwind styles

The comment indicates potential issues with Tailwind CSS imports that should be resolved.

Would you like me to help investigate the Tailwind configuration and propose a solution?

packages/graphql/src/schema/query.ts (1)

Line range hint 1-724: Consider documenting the migration strategy.

The file shows a partial migration from sessions to live quizzes. To ensure a smooth transition:

  1. Document the migration strategy and timeline
  2. Create a tracking issue for remaining session-related queries
  3. Consider adding deprecation notices on session-related queries that will be migrated
  4. Update client-side documentation to reflect these changes

This will help maintain consistency and provide clear guidance for other developers working on the migration.

packages/graphql/src/public/schema.graphql (2)

366-389: Well-structured block management implementation!

The ElementBlock type and its related types provide a solid foundation for managing quiz blocks. The separation between the main type and input type is clean, and the status transitions are logical.

Consider these architectural recommendations:

  1. The execution field might benefit from documentation explaining its purpose and valid values
  2. Consider adding validation rules for timeLimit and randomSelection in your resolvers
  3. Think about adding a metadata field of type Json for future extensibility without schema changes

Line range hint 366-1449: Consider implementing subscription types for live quiz updates.

The schema effectively handles the transition from sessions to live quizzes, but real-time updates might be beneficial.

Consider adding these subscription types:

extend type Subscription {
  liveQuizBlockUpdated(quizId: String!): ElementBlock!
  liveQuizStatusChanged(quizId: String!): LiveQuiz!
  participantJoinedLiveQuiz(quizId: String!): Int! # Returns updated participant count
}

This would enable real-time updates for:

  1. Block status changes (ACTIVE → EXECUTED)
  2. Quiz status updates
  3. Participant count updates
packages/graphql/src/ops.schema.json (3)

4308-4541: LGTM! Consider adding field descriptions.

The ElementBlock related type definitions are well-structured with appropriate fields and constraints. The status transitions (SCHEDULED -> ACTIVE -> EXECUTED) provide a clear lifecycle for blocks.

Consider adding descriptions to fields to improve schema documentation. For example:

  • execution: Explain what this integer represents
  • expiresAt: Document the expiration behavior
  • randomSelection: Clarify the purpose and valid values

Line range hint 12258-12453: Ensure courseId is required for quiz creation.

The createLiveQuiz mutation correctly requires most fields but courseId is optional. This could lead to orphaned quizzes.

Make courseId required:

   "name": "courseId",
   "type": {
-    "kind": "SCALAR",
+    "kind": "NON_NULL",
+    "name": null,
+    "ofType": {
+      "kind": "SCALAR",
+      "name": "String",
+      "ofType": null
+    }
   }

Line range hint 13488-13699: Add optimistic locking for concurrent edits.

The editLiveQuiz mutation lacks protection against concurrent modifications.

Consider adding a version field to prevent lost updates:

 "name": "editLiveQuiz",
 "args": [
+  {
+    "name": "version",
+    "description": "Current version for optimistic locking",
+    "type": {
+      "kind": "NON_NULL",
+      "name": null,
+      "ofType": {
+        "kind": "SCALAR",
+        "name": "Int",
+        "ofType": null
+      }
+    }
+  },
🛑 Comments failed to post (19)
packages/graphql/src/graphql/ops/QGetUserRunningLiveQuizzes.graphql (1)

1-6: 💡 Codebase verification

Based on the gathered evidence, I can now provide a final response:

Consider adding displayName field to the query

The current query fetches only id and name, but the GraphQL schema shows that displayName is a required field (!) that might be needed for UI display purposes. The components using this query primarily use it for:

  • Navigation links in the header
  • Session management in the cockpit
  • Cache updates after session operations

While the minimal fields work for basic functionality, consider adding:

  • displayName - Required field that might be needed for consistent display across the UI
  • status - Could be useful for UI state management (optional)
🔗 Analysis chain

LGTM! Verify if minimal fields are sufficient.

The query structure is clean and follows GraphQL conventions. However, please verify if just id and name fields are sufficient for all UI requirements where this query will be used.

Let's check where this query is used and what fields are needed:


Let's examine how these components use the query data to verify if the fields are sufficient.


Let's check the actual UI components to see how they render the quiz data and if there are any props or interfaces that might indicate additional field requirements.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of GetUserRunningLiveQuizzes query
rg -l "GetUserRunningLiveQuizzes" 

# Search for UI components that might display running live quizzes
rg -l "RunningLiveQuiz" 

Length of output: 1424


Script:

#!/bin/bash
# Check LiveQuiz component implementation
rg -A 10 "GetUserRunningLiveQuizzes" "apps/frontend-manage/src/components/sessions/LiveQuiz.tsx"

# Check Header component usage
rg -A 10 "GetUserRunningLiveQuizzes" "apps/frontend-manage/src/components/common/Header.tsx"

# Check cockpit page implementation
rg -A 10 "GetUserRunningLiveQuizzes" "apps/frontend-manage/src/pages/sessions/[id]/cockpit.tsx"

# Check control page implementation
rg -A 10 "GetUserRunningLiveQuizzes" "apps/frontend-control/src/pages/session/[id].tsx"

Length of output: 3512


Script:

#!/bin/bash
# Check LiveQuizElement and RunningLiveQuizLink components for field usage
rg -A 10 "interface|type.*Props" "apps/frontend-manage/src/components/courses/LiveQuizElement.tsx" "apps/frontend-manage/src/components/courses/actions/RunningLiveQuizLink.tsx"

# Check if there are any TypeScript interfaces defining LiveQuiz type
ast-grep --pattern 'interface LiveQuiz {
  $$$
}'

# Check schema definition for LiveQuiz type
rg -A 10 "type LiveQuiz" "packages/graphql/src/public/schema.graphql"

Length of output: 2803

packages/graphql/src/schema/liveQuiz.ts (1)

52-68: 💡 Codebase verification

The commented-out fields are part of an ongoing migration from LiveSession to LiveQuiz

Based on the evidence from the migration file (20241030111355_live_quiz_blocks) and Prisma schema, these fields are actively being migrated from the old LiveSession model to the new LiveQuiz model. The schema shows that:

  • activeBlock is implemented and referenced in the LiveQuiz model
  • feedbacks and confusionFeedbacks relations are present in both old and new models
  • The migration is actively moving these features to the new implementation

The commented-out code in liveQuiz.ts should be uncommented as these features are part of the active schema.

🔗 Analysis chain

Verify the status of commented-out blocks.

Several blocks of code are commented out (activeBlock, feedbacks, confusionFeedbacks, confusionSummary). These might be planned for future implementation or remnants of the migration.

Let's check if these features are implemented elsewhere or if they're planned for future:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for related implementations or TODOs
echo "Searching for related implementations..."
rg -l "activeBlock|feedbacks|confusionFeedbacks|confusionSummary" --type typescript

echo "Searching for related TODOs..."
rg -i "todo.*(activeBlock|feedback|confusion)" --type typescript

Length of output: 384


Script:

#!/bin/bash
# Search for related implementations without file type restriction
echo "Searching for related implementations..."
rg -l "activeBlock|feedbacks|confusionFeedbacks|confusionSummary"

echo "Searching for related TODOs..."
rg -i "todo.*(activeBlock|feedback|confusion)"

# Search for any confusion or feedback related types/interfaces
echo "Searching for type definitions..."
ast-grep --pattern 'interface $_ {
  $$$
  (activeBlock|feedbacks|confusionFeedbacks|confusionSummary)
  $$$
}'

# Look for any references to these fields in GraphQL schemas
echo "Searching in GraphQL schemas..."
fd -e ts -e graphql . --exec grep -l "activeBlock\|feedbacks\|confusionFeedbacks\|confusionSummary" {}

Length of output: 5753


Script:

#!/bin/bash
# Let's examine the actual implementation in key files to understand if these features are active
echo "Checking types definition..."
cat packages/types/src/index.ts | grep -A 10 -B 10 "activeBlock\|feedbacks\|confusionFeedbacks"

echo "Checking GraphQL schema..."
cat packages/graphql/src/public/schema.graphql | grep -A 10 -B 10 "type LiveQuiz"

echo "Checking recent migration..."
cat "packages/prisma/src/prisma/migrations/20241030111355_live_quiz_blocks/migration.sql"

echo "Checking Prisma schema..."
cat packages/prisma/src/prisma/schema/quiz.prisma

Length of output: 17372

apps/frontend-manage/src/components/common/Header.tsx (2)

103-103: ⚠️ Potential issue

Add null safety to length checks.

The length checks should handle potential undefined values more robustly.

-  quizzes?.length !== 0
+  (quizzes?.length ?? 0) > 0

-  disabled={quizzes?.length === 0}
+  disabled={!quizzes || quizzes.length === 0}

Also applies to: 108-108


28-29: 🛠️ Refactor suggestion

Consider adding error handling for the GraphQL query.

While the data fetching changes look good, consider handling potential query errors to improve reliability.

-  const { data } = useQuery(GetUserRunningLiveQuizzesDocument)
+  const { data, error } = useQuery(GetUserRunningLiveQuizzesDocument)
+  if (error) {
+    console.error('Failed to fetch running live quizzes:', error)
+    // Consider showing a user-friendly error message
+  }

Committable suggestion was skipped due to low confidence.

apps/frontend-control/src/pages/session/[id].tsx (1)

9-9: 💡 Codebase verification

Migration from sessions to live quizzes requires attention in i18n files

The rename from GetUserRunningSessionsDocument to GetUserRunningLiveQuizzesDocument is consistent in GraphQL operations, but there are still references to "sessions" in the i18n message files that should be updated to maintain terminology consistency:

  • packages/i18n/messages/de.ts: Contains multiple references to "Sessionen"
  • packages/i18n/messages/en.ts: Contains mixed terminology ("Running Sessions" and "Running Live Quizzes")
🔗 Analysis chain

Verify complete migration of session references

The rename from sessions to live quizzes appears correct. Let's verify all related references have been updated consistently.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining references to old session document
rg -i "GetUserRunningSessions" 

# Search for potential missed session terminology that should be updated to live quiz
rg -i "running.*session" --type ts --type tsx

Length of output: 106


Script:

#!/bin/bash
# Search for any remaining references to old session document with correct file extensions
rg -i "GetUserRunningSessions" -t ts -t typescript -t jsx -t tsx

# Search for potential missed session terminology that should be updated to live quiz
rg -i "running.*session" -t ts -t typescript -t jsx -t tsx

# Search for the new document name to verify its usage
rg -i "GetUserRunningLiveQuizzes" -t ts -t typescript -t jsx -t tsx

Length of output: 298


Script:

#!/bin/bash
# Search for any remaining references to old session document
rg -g '*.{ts,tsx,js,jsx}' -i "GetUserRunningSessions"

# Search for potential missed session terminology that should be updated to live quiz
rg -g '*.{ts,tsx,js,jsx}' -i "running.*session"

# Search for the new document name to verify its usage
rg -g '*.{ts,tsx,js,jsx}' -i "GetUserRunningLiveQuizzes"

Length of output: 33927

packages/graphql/src/services/liveQuizzes.ts (6)

32-49: ⚠️ Potential issue

Add validation for multiplier and bonus points

The function should validate that multiplier is positive and maxBonusPoints/timeToZeroBonus are valid when gamification is enabled.

Add validation at the beginning of the function:

export async function manipulateLiveQuiz(
  { multiplier, maxBonusPoints, timeToZeroBonus, isGamificationEnabled, ...args }: ManipulateLiveQuizArgs,
  ctx: ContextWithUser
) {
+  if (multiplier <= 0) {
+    throw new GraphQLError('Multiplier must be positive')
+  }
+  
+  if (isGamificationEnabled) {
+    if (maxBonusPoints !== null && maxBonusPoints <= 0) {
+      throw new GraphQLError('Max bonus points must be positive when gamification is enabled')
+    }
+    if (timeToZeroBonus !== null && timeToZeroBonus <= 0) {
+      throw new GraphQLError('Time to zero bonus must be positive when gamification is enabled')
+    }
+  }

Also applies to: 108-166


74-81: ⚠️ Potential issue

Wrap database operations in transaction

The block deletion and quiz update operations should be atomic to prevent partial updates in case of failures.

Wrap the operations in a transaction:

+ const element = await ctx.prisma.$transaction(async (tx) => {
    if (id) {
-     await ctx.prisma.liveQuiz.update({
+     await tx.liveQuiz.update({
        where: { id },
        data: {
          blocks: {
            deleteMany: {},
          },
        },
      })
    }

-   return await ctx.prisma.liveQuiz.upsert({
+   return await tx.liveQuiz.upsert({
      where: { id: id ?? uuidv4() },
      create: createOrUpdateJSON,
      update: createOrUpdateJSON,
      include: {
        // ... existing include options
      },
    })
+ })

Also applies to: 168-187


294-296: ⚠️ Potential issue

Add isDeleted check for consistency

The where clause should include isDeleted check to maintain consistency with other queries.

Add isDeleted check:

  where: {
    status: PublicationStatus.PUBLISHED,
+   isDeleted: false,
  },
📝 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.

        where: {
          status: PublicationStatus.PUBLISHED,
          isDeleted: false,
        },

103-106: ⚠️ Potential issue

Performance: Optimize element map reduction

The spread operator in the reduce accumulator can lead to O(n²) time complexity.

Replace with direct property assignment:

- const elementMap = dbElements.reduce<Record<number, Element>>(
-   (acc, elem) => ({ ...acc, [elem.id]: elem }),
-   {}
- )
+ const elementMap = dbElements.reduce<Record<number, Element>>((acc, elem) => {
+   acc[elem.id] = elem
+   return acc
+ }, {})
📝 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.

  const elementMap = dbElements.reduce<Record<number, Element>>((acc, elem) => {
    acc[elem.id] = elem
    return acc
  }, {})
🧰 Tools
🪛 Biome

[error] 104-104: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)


273-276: ⚠️ Potential issue

Add null check for participant count calculation

The current implementation assumes elements[0] exists without proper null checking.

Add proper null checking:

- numOfParticipants: block.elements[0]
-   ? block.elements[0].results.total +
-     block.elements[0].anonymousResults.total
-   : 0,
+ numOfParticipants: block.elements[0]?.results?.total ?? 0 +
+   block.elements[0]?.anonymousResults?.total ?? 0,

Committable suggestion was skipped due to low confidence.


209-226: ⚠️ Potential issue

Update variable name and add isDeleted check

The variable name 'session' is inconsistent with the new live quiz terminology, and the query is missing an isDeleted check.

Apply these changes:

- const session = await ctx.prisma.liveQuiz.findUnique({
+ const liveQuiz = await ctx.prisma.liveQuiz.findUnique({
    where: {
      id,
      ownerId: ctx.user.sub,
+     isDeleted: false
    },
    // ... rest of the query
  })

- return session
+ return liveQuiz

Committable suggestion was skipped due to low confidence.

apps/frontend-manage/src/components/sessions/LiveQuiz.tsx (2)

103-115: 🛠️ Refactor suggestion

Consider enhancing timestamp formatting and handling

The current timestamp handling could be improved for better user experience and maintainability.

-  const timeStamp: Record<PublicationStatus, string | null> = {
+  const getFormattedTimestamp = (status: PublicationStatus): string => {
+    const timestamp = {
       [PublicationStatus.Draft]: quiz.createdAt,
       [PublicationStatus.Scheduled]: quiz.createdAt,
       [PublicationStatus.Published]: quiz.startedAt,
       [PublicationStatus.Ended]: quiz.finishedAt,
       [PublicationStatus.Graded]: quiz.finishedAt,
-  }
+    }[status];
+    
+    return timestamp ? dayjs(timestamp).format('YYYY-MM-DD HH:mm') : '-';
+  };

Committable suggestion was skipped due to low confidence.


34-50: 🛠️ Refactor suggestion

Consider enhancing type safety with TypeScript utilities

While the type structure is comprehensive, consider using TypeScript's utility types to make it more maintainable.

-  quiz: Pick<
-    LiveQuizType,
-    | 'id'
-    | 'name'
-    | 'displayName'
-    | 'status'
-    | 'numOfBlocks'
-    | 'numOfInstances'
-    | 'createdAt'
-    | 'startedAt'
-    | 'finishedAt'
-    | 'blocks'
-  >
+type LiveQuizProps = Pick<LiveQuizType, 
+  'id' | 'name' | 'displayName' | 'status' | 'numOfBlocks' |
+  'numOfInstances' | 'createdAt' | 'startedAt' | 'finishedAt' | 'blocks'
+>;
+
+interface Props {
+  quiz: LiveQuizProps;
+}
📝 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.

function LiveQuiz({
  quiz,
}: Props) {
  type LiveQuizProps = Pick<LiveQuizType, 
    'id' | 'name' | 'displayName' | 'status' | 'numOfBlocks' |
    'numOfInstances' | 'createdAt' | 'startedAt' | 'finishedAt' | 'blocks'
  >;

  interface Props {
    quiz: LiveQuizProps;
  }
packages/graphql/src/schema/query.ts (1)

480-488: 💡 Codebase verification

Based on the gathered information, I can now provide the final response:

Migration to LiveQuiz requires attention in multiple areas

The review comment's concern is valid and supported by evidence:

  • Multiple TODO comments across the codebase explicitly mention pending live quiz migration tasks
  • Several Session-type queries still exist that need migration:
    • cockpitSession
    • controlSession
    • session
    • sessionEvaluation
    • unassignedSessions
    • runningSessions
    • runningSessionsCourse

The migration from Session to LiveQuiz appears to be a work in progress that requires consistent updates across the entire codebase to avoid maintaining parallel systems.

🔗 Analysis chain

Consider consistent migration of all session-related queries.

While this field is correctly migrated, there are other session-related queries (e.g., cockpitSession, sessionEvaluation) that still have TODO comments about migration. Consider migrating all session-related queries consistently to avoid maintaining two parallel systems.


Let me gather more information about session-related queries using a different approach.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find all TODO comments related to live quiz migration
echo "Checking for remaining migration TODOs..."
rg "TODO.*migration.*live.*quiz" --type ts

# List all remaining session-related queries
echo "Checking for remaining session-related queries..."
ast-grep --pattern 'type: Session,'

Length of output: 1373


Script:

#!/bin/bash
# Search for Session type usage in GraphQL schema
echo "Checking for Session type usage in schema..."
rg "Session[,\)]" --type ts -A 2 packages/graphql/src/schema/

# Search for session-related resolver functions
echo "Checking for session-related resolvers..."
rg "session|Session" --type ts -A 2 packages/graphql/src/schema/query.ts

Length of output: 6033

packages/graphql/src/public/schema.graphql (2)

1024-1024: ⚠️ Potential issue

Verify the optional courseId parameter.

The courseId parameter is marked as optional (String instead of String!), but a LiveQuiz should typically be associated with a course. This might lead to inconsistent data states.

Consider making courseId required:

-  createLiveQuiz(blocks: [ElementBlockInput!]!, courseId: String, description: String, displayName: String!, isConfusionFeedbackEnabled: Boolean!, isGamificationEnabled: Boolean!, isLiveQAEnabled: Boolean!, isModerationEnabled: Boolean!, maxBonusPoints: Int, multiplier: Int!, name: String!, timeToZeroBonus: Int): LiveQuiz
+  createLiveQuiz(blocks: [ElementBlockInput!]!, courseId: String!, description: String, displayName: String!, isConfusionFeedbackEnabled: Boolean!, isGamificationEnabled: Boolean!, isLiveQAEnabled: Boolean!, isModerationEnabled: Boolean!, maxBonusPoints: Int, multiplier: Int!, name: String!, timeToZeroBonus: Int): LiveQuiz
📝 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.

  createLiveQuiz(blocks: [ElementBlockInput!]!, courseId: String!, description: String, displayName: String!, isConfusionFeedbackEnabled: Boolean!, isGamificationEnabled: Boolean!, isLiveQAEnabled: Boolean!, isModerationEnabled: Boolean!, maxBonusPoints: Int, multiplier: Int!, name: String!, timeToZeroBonus: Int): LiveQuiz

1043-1043: 🛠️ Refactor suggestion

⚠️ Potential issue

Maintain consistency with createLiveQuiz and improve parameter organization.

The mutation has the same optional courseId issue as createLiveQuiz. Additionally, consider organizing the parameters more logically.

Apply these improvements:

-  editLiveQuiz(blocks: [ElementBlockInput!]!, courseId: String, description: String, displayName: String!, id: String!, isConfusionFeedbackEnabled: Boolean!, isGamificationEnabled: Boolean!, isLiveQAEnabled: Boolean!, isModerationEnabled: Boolean!, maxBonusPoints: Int, multiplier: Int!, name: String!, timeToZeroBonus: Int): LiveQuiz
+  editLiveQuiz(
+    id: String!,
+    courseId: String!,
+    # Basic info
+    name: String!,
+    displayName: String!,
+    description: String,
+    # Configuration
+    blocks: [ElementBlockInput!]!,
+    multiplier: Int!,
+    maxBonusPoints: Int,
+    timeToZeroBonus: Int,
+    # Feature flags
+    isConfusionFeedbackEnabled: Boolean!,
+    isGamificationEnabled: Boolean!,
+    isLiveQAEnabled: Boolean!,
+    isModerationEnabled: Boolean!
+  ): LiveQuiz
📝 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.

  editLiveQuiz(
    id: String!,
    courseId: String!,
    # Basic info
    name: String!,
    displayName: String!,
    description: String,
    # Configuration
    blocks: [ElementBlockInput!]!,
    multiplier: Int!,
    maxBonusPoints: Int,
    timeToZeroBonus: Int,
    # Feature flags
    isConfusionFeedbackEnabled: Boolean!,
    isGamificationEnabled: Boolean!,
    isLiveQAEnabled: Boolean!,
    isModerationEnabled: Boolean!
  ): LiveQuiz
packages/graphql/src/public/server.json (1)

151-158: 🛠️ Refactor suggestion

Consider performance optimizations for quiz retrieval.

The implementation provides comprehensive quiz data but could benefit from:

  1. Implementing pagination to handle large result sets efficiently
  2. Adding caching directives for frequently accessed data

Consider adding pagination arguments to the query:

-query GetUserLiveQuizzes {
+query GetUserLiveQuizzes($first: Int, $after: String) {
   userLiveQuizzes(first: $first, after: $after) {
+    pageInfo {
+      hasNextPage
+      endCursor
+    }
     id
     name
     # ... other fields
   }
 }

And add caching directives:

-query GetUserLiveQuizzes {
+query GetUserLiveQuizzes @cacheControl(maxAge: 300) {
   userLiveQuizzes {
     # ... fields
   }
 }

Committable suggestion was skipped due to low confidence.

packages/graphql/src/ops.schema.json (2)

10532-10906: 🛠️ Refactor suggestion

Consider enhancing the access control model.

The LiveQuiz type is well-structured with comprehensive fields for quiz management. However, the binary PUBLIC/RESTRICTED access mode might be limiting.

Consider expanding the access control model to support more granular options:

-  "name": "LiveQuizAccessMode",
-  "enumValues": [
-    { "name": "PUBLIC" },
-    { "name": "RESTRICTED" }
-  ]
+  "name": "LiveQuizAccessMode",
+  "enumValues": [
+    { "name": "PUBLIC" },
+    { "name": "RESTRICTED_WITH_CODE" },
+    { "name": "RESTRICTED_BY_EMAIL" },
+    { "name": "RESTRICTED_BY_DOMAIN" }
+  ]
📝 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.

        "kind": "ENUM",
        "name": "LiveQuizAccessMode",
        "description": null,
        "isOneOf": null,
        "fields": null,
        "inputFields": null,
        "interfaces": null,
        "enumValues": [
          {
            "name": "PUBLIC",
            "description": null,
            "isDeprecated": false,
            "deprecationReason": null
          },
          {
            "name": "RESTRICTED_WITH_CODE",
            "description": null,
            "isDeprecated": false,
            "deprecationReason": null
          },
          {
            "name": "RESTRICTED_BY_EMAIL",
            "description": null,
            "isDeprecated": false,
            "deprecationReason": null
          },
          {
            "name": "RESTRICTED_BY_DOMAIN",
            "description": null,
            "isDeprecated": false,
            "deprecationReason": null
          }
        ],
        "possibleTypes": null

21773-21792: 🛠️ Refactor suggestion

Add pagination to list queries.

The userLiveQuizzes query returns an unbounded list which could cause performance issues with large datasets.

Consider implementing cursor-based pagination:

 "name": "userLiveQuizzes",
 "args": [
+  {
+    "name": "first",
+    "type": {
+      "kind": "NON_NULL",
+      "name": null,
+      "ofType": {
+        "kind": "SCALAR",
+        "name": "Int",
+        "ofType": null
+      }
+    }
+  },
+  {
+    "name": "after",
+    "type": {
+      "kind": "SCALAR",
+      "name": "String",
+      "ofType": null
+    }
+  }
 ],
 "type": {
-  "kind": "LIST",
+  "kind": "OBJECT",
+  "name": "LiveQuizConnection",
   "fields": [
     {
-      "name": null,
+      "name": "edges",
       "type": {
-        "kind": "NON_NULL",
-        "ofType": {
-          "kind": "OBJECT",
-          "name": "LiveQuiz"
-        }
+        "kind": "LIST",
+        "ofType": {
+          "kind": "OBJECT",
+          "name": "LiveQuizEdge",
+          "fields": [
+            {
+              "name": "cursor",
+              "type": { "kind": "NON_NULL", "name": "String" }
+            },
+            {
+              "name": "node",
+              "type": { "kind": "NON_NULL", "name": "LiveQuiz" }
+            }
+          ]
+        }
       }
+    },
+    {
+      "name": "pageInfo",
+      "type": {
+        "kind": "NON_NULL",
+        "name": "PageInfo"
+      }
     }
   ]
 }
📝 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.

          {
            "name": "userLiveQuizzes",
            "description": null,
            "args": [
              {
                "name": "first",
                "type": {
                  "kind": "NON_NULL",
                  "name": null,
                  "ofType": {
                    "kind": "SCALAR",
                    "name": "Int",
                    "ofType": null
                  }
                }
              },
              {
                "name": "after",
                "type": {
                  "kind": "SCALAR",
                  "name": "String",
                  "ofType": null
                }
              }
            ],
            "type": {
              "kind": "OBJECT",
              "name": "LiveQuizConnection",
              "fields": [
                {
                  "name": "edges",
                  "type": {
                    "kind": "LIST",
                    "ofType": {
                      "kind": "OBJECT",
                      "name": "LiveQuizEdge",
                      "fields": [
                        {
                          "name": "cursor",
                          "type": { "kind": "NON_NULL", "name": "String" }
                        },
                        {
                          "name": "node",
                          "type": { "kind": "NON_NULL", "name": "LiveQuiz" }
                        }
                      ]
                    }
                  }
                },
                {
                  "name": "pageInfo",
                  "type": {
                    "kind": "NON_NULL",
                    "name": "PageInfo"
                  }
                }
              ]
            },
            "isDeprecated": false,
            "deprecationReason": null
          },

@sjschlapbach sjschlapbach merged commit 524bb29 into v3-new-live-quiz Oct 31, 2024
10 of 12 checks passed
@sjschlapbach sjschlapbach deleted the live-quiz-creation branch October 31, 2024 11:40
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
5.0% Duplication on New Code (required ≤ 3%)

See analysis details on SonarCloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

1 participant