Skip to content

feat: Randomize row order matrix questions#3233

Merged
Dhruwang merged 15 commits intoformbricks:mainfrom
SaiSawant1:randomize-row-order-matrix-questions
Oct 5, 2024
Merged

feat: Randomize row order matrix questions#3233
Dhruwang merged 15 commits intoformbricks:mainfrom
SaiSawant1:randomize-row-order-matrix-questions

Conversation

@SaiSawant1
Copy link
Contributor

@SaiSawant1 SaiSawant1 commented Oct 1, 2024

What does this PR do?

Allows user to randomize row ordering in matrix question.

Fixes #3156
swappy-20241001_004140

How should this be tested?

  • Test A
  • Test B

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read How we Code at Formbricks
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand bits
  • Ran pnpm build
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues
  • First PR at Formbricks? Please sign the CLA! Without it we wont be able to merge it 🙏

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Formbricks Docs if changes were necessary

Summary by CodeRabbit

  • New Features

    • Introduced a shuffle option for matrix questions, allowing users to select how options are displayed (e.g., keep current order, randomize all, randomize all except last).
    • Enhanced configurability of multiple choice and ranking questions with a new selection component for shuffle options.
    • Added a new ShuffleOptionSelect component for streamlined selection of shuffle methods.
  • Bug Fixes

    • Improved validation logic for matrix questions to ensure proper handling of the new shuffle option.
  • Documentation

    • Updated types and validation rules related to the new shuffle functionality for clarity and consistency.

@vercel
Copy link

vercel bot commented Oct 1, 2024

@SaiSawant1 is attempting to deploy a commit to the formbricks Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 1, 2024

Walkthrough

The changes introduce a new feature for matrix questions in surveys, allowing users to manage the order of question options through a shuffle mechanism. This includes the addition of a shuffle option in the MatrixQuestionForm, updates to the rendering logic in MatrixQuestion, and enhancements to utility functions for shuffling rows. The modifications also extend the type definitions to accommodate the new functionality, ensuring robust validation and flexibility in survey design.

Changes

File Change Summary
apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx Added a shuffle option feature with a ShuffleOptionSelect component for matrix questions, linking it to the question.shuffleOption property.
apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MultipleChoiceQuestionForm.tsx Replaced the Select component with ShuffleOptionSelect for managing shuffle options in multiple choice questions.
apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RankingQuestionForm.tsx Replaced the Select component with ShuffleOptionSelect for managing shuffle options in ranking questions.
packages/surveys/src/components/questions/MatrixQuestion.tsx Implemented logic to shuffle matrix rows based on the shuffleOption, using useMemo to conditionally render original or shuffled rows.
packages/surveys/src/lib/utils.ts Introduced getShuffledRowIndices function to handle row shuffling based on the new TShuffleOption, and refactored existing functions.
packages/types/surveys/types.ts Added shuffleOption property to ZSurveyMatrixQuestion type for improved flexibility in survey question handling and validation.
packages/ui/components/ShuffleOptionSelect/index.tsx Added a new ShuffleOptionSelect component for selecting shuffle options, integrating with the survey question forms.

Assessment against linked issues

Objective Addressed Explanation
Add the randomize order feature for matrix questions (#[3156])

🐰 In a world of questions, oh so bright,
Shuffle them gently, left and right.
Rows dance and twirl, a playful sight,
Bias be gone, all feels just right!
With every choice, let fairness take flight! 🌼


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.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 1, 2024

Thank you for following the naming conventions for pull request titles! 🙏

@SaiSawant1 SaiSawant1 changed the title Randomize row order matrix questions feat: Randomize row order matrix questions Oct 1, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (5)
packages/surveys/src/lib/utils.ts (2)

22-34: LGTM: New getShuffledRows function implemented correctly.

The new getShuffledRows function correctly implements the shuffling logic based on the provided shuffleOption. It handles both "all" and "exceptLast" cases as expected.

Consider extracting the shuffling logic for the "exceptLast" case into a separate function for improved readability:

const shuffleExceptLast = (array: any[]) => {
  const lastElement = array.pop();
  if (lastElement) {
    shuffle(array);
    array.push(lastElement);
  }
};

export const getShuffledRows = (rows: TI18nString[], shuffleOption: TShuffleOption): TI18nString[] => {
  const rowsCopy = [...rows];
  if (shuffleOption === "all") {
    shuffle(rowsCopy);
  } else if (shuffleOption === "exceptLast") {
    shuffleExceptLast(rowsCopy);
  }
  return rowsCopy;
};

This change would make the main function more concise and easier to read.


Line range hint 36-59: LGTM: getShuffledChoicesIds function updated correctly.

The modifications to the getShuffledChoicesIds function correctly implement the new shuffling logic using the TShuffleOption type. The function maintains proper handling of the "other" option and correctly implements both "all" and "exceptLast" shuffling options.

While reviewing this function, I noticed a minor typo in the calculateElementIdx function (which is not part of the changes in this PR). The parameter currentQustionIdx should be currentQuestionIdx. Consider fixing this in a future PR or as a separate commit.

packages/surveys/src/components/questions/MatrixQuestion.tsx (1)

46-51: Approve with suggestions: Row shuffling implementation using useMemo.

The useMemo hook correctly implements the row shuffling functionality based on the shuffleOption. However, there are a couple of points to consider:

  1. The dependency array includes question.rows[question.rows.length - 1].id, which might cause unnecessary re-renders if the last row's ID changes frequently. Consider using a more stable dependency, such as a unique identifier for the entire question or the shuffleOption itself.

  2. The eslint-disable comment suggests there might be a linting issue with the dependencies. It's worth reviewing if all necessary dependencies are included without over-specifying.

Consider refactoring the dependency array to:

}, [question.shuffleOption, question.id]);

This change assumes that question.id is a stable identifier for the question and its rows. If you need to react to changes in the rows themselves, consider adding a separate rowVersion or lastUpdated field to the question object that you can use in the dependency array.

apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx (2)

88-104: LGTM: shuffleOptionsTypes object well-structured.

The shuffleOptionsTypes object is clear and aligns with the PR objective. However, consider the necessity of the 'show' property, as it's always set to true.

If the 'show' property isn't used elsewhere in the codebase, consider simplifying the object structure:

const shuffleOptionsTypes = {
  none: {
    id: "none",
    label: "Keep current order",
  },
  all: {
    id: "all",
    label: "Randomize all",
  },
  exceptLast: {
    id: "exceptLast",
    label: "Randomize all except last option",
  },
};

239-263: LGTM: Shuffle options Select component implemented correctly.

The Select component for shuffle options is well-implemented and functions as expected. However, consider the placement of this component within the form.

The shuffle options Select is currently placed at the end of the form, which might not be immediately visible to users. Consider moving it to a more prominent position, such as near the top of the form or just below the question headline. This could improve user experience by making the shuffle option more discoverable.

For example, you could place it after the question headline:

<QuestionFormInput
  id="headline"
  value={question.headline}
  label={"Question*"}
  // ... other props ...
/>

<div className="mt-3">
  <Label htmlFor="shuffleOption">Row Order</Label>
  <Select
    defaultValue={question.shuffleOption}
    value={question.shuffleOption}
    onValueChange={(e: TShuffleOption) => {
      updateQuestion(questionIdx, { shuffleOption: e });
    }}>
    {/* ... Select content ... */}
  </Select>
</div>

{/* ... rest of the form ... */}

This placement would make the shuffle option more visible and accessible to users.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between b7f4097 and 31eae02.

📒 Files selected for processing (4)
  • apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx (3 hunks)
  • packages/surveys/src/components/questions/MatrixQuestion.tsx (2 hunks)
  • packages/surveys/src/lib/utils.ts (2 hunks)
  • packages/types/surveys/types.ts (1 hunks)
🔇 Additional comments (8)
packages/surveys/src/lib/utils.ts (2)

2-3: LGTM: New type imports added.

The addition of TI18nString and TShuffleOption types is appropriate for the changes made in this file. These types will help improve type safety and code clarity in the modified functions.


Line range hint 1-59: Overall changes look good and align with PR objectives.

The modifications introduced in this PR successfully implement the functionality to randomize row order in matrix questions. The changes include:

  1. New type definitions (TI18nString and TShuffleOption) for improved type safety.
  2. A new getShuffledRows function to handle row shuffling.
  3. Updates to the existing getShuffledChoicesIds function to use the new TShuffleOption type and implement the new shuffling logic.

These changes are well-implemented, maintain the existing code structure, and add the new functionality without breaking existing behavior. The use of more specific types also improves the overall type safety of the code.

To ensure that these changes don't introduce any unintended side effects, please run the following verification script:

This script will help ensure that the new shuffling logic is used consistently across the codebase and that there are no remaining instances of the old string-based shuffleOption usage.

packages/surveys/src/components/questions/MatrixQuestion.tsx (2)

8-8: LGTM: New import for row shuffling functionality.

The import of getShuffledRows from "@/lib/utils" is correctly placed and aligns with the PR objective of randomizing row order in matrix questions.


Line range hint 1-214: Summary: Successful implementation of row shuffling in MatrixQuestion component.

The changes effectively implement the row shuffling functionality for matrix questions, aligning with the PR objective. The implementation is minimal and focused, only modifying the necessary parts of the component. The rest of the component's logic, including rendering and event handling, remains unchanged, which helps maintain the existing functionality.

A few points to consider for future improvements:

  1. Ensure that the shuffling behavior is thoroughly tested, especially with different shuffleOption values and row configurations.
  2. Consider adding documentation or comments explaining the shuffling behavior and its impact on the component's rendering.
  3. If not already done, update any relevant unit or integration tests to cover the new shuffling functionality.

To ensure the changes don't introduce any regressions, please run the following verification script:

This script checks for the correct export of getShuffledRows, the definition of shuffleOption in types, and any remaining TODOs related to shuffling.

apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx (3)

6-6: LGTM: Import statement updated correctly.

The addition of TShuffleOption to the import statement is appropriate for implementing the new shuffling feature.


10-16: LGTM: Select component imports added.

The addition of Select component imports is appropriate for implementing the new shuffle options UI element.


Line range hint 1-263: Overall: Good implementation of row randomization feature.

The changes successfully implement the new feature for randomizing row order in matrix questions. The code is well-structured and aligns with the PR objectives. A few minor suggestions were made:

  1. Consider simplifying the shuffleOptionsTypes object if the 'show' property isn't used elsewhere.
  2. Evaluate the placement of the shuffle options Select component for better user experience.

These suggestions aside, the implementation is solid and ready for integration.

packages/types/surveys/types.ts (1)

575-575: Ensure validation logic includes shuffleOption

Verify that the validation functions properly handle the new shuffleOption property to ensure it conforms to expected values and that any associated behaviors are correctly implemented.

Run the following script to check if shuffleOption is included in validation logic:

type: z.literal(TSurveyQuestionTypeEnum.Matrix),
rows: z.array(ZI18nString),
columns: z.array(ZI18nString),
shuffleOption: ZShuffleOption.optional(),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider setting a default value for shuffleOption

Since shuffleOption is optional, providing a default value such as "none" can ensure consistent behavior when the property is not specified.

Apply this diff to set a default value:

-shuffleOption: ZShuffleOption.optional(),
+shuffleOption: ZShuffleOption.optional().default("none"),
📝 Committable suggestion

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

Suggested change
shuffleOption: ZShuffleOption.optional(),
shuffleOption: ZShuffleOption.optional().default("none"),

Copy link
Member

@Dhruwang Dhruwang left a comment

Choose a reason for hiding this comment

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

Hey @SaiSawant1 , thanks for the PR. Good work 🙌🏻🚀

Found out few smaller issues while testing

  1. Shuffle option select collides with Add column button
Screenshot 2024-10-01 at 4 29 05 PM
  1. If a shuffle option is selected, changes to row does not get reflected.(I tried adding a new row)
Screen-Recording-2024-10-01-at-4.29.36.PM.mp4
  1. When I select "Randomise all except last", it should not change last option
Screen-Recording-2024-10-01-at-4.30.12.PM.mp4

Rest looks good 😊✅

const isMediaAvailable = question.imageUrl || question.videoUrl;
useTtc(question.id, ttc, setTtc, startTime, setStartTime, question.id === currentQuestionId);

question.rows = useMemo(() => {
Copy link
Member

Choose a reason for hiding this comment

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

Kindly avoid mutating this object directly. Use a constant instead 😊

</Button>
</div>
<div className="flex flex-1 items-center justify-end gap-2">
<Select
Copy link
Member

@Dhruwang Dhruwang Oct 1, 2024

Choose a reason for hiding this comment

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

Since we have the same functionality for multi select and ranking, can you create a reusable component for Shuffle option select in packages/ui/ShuffleOptionSelect 😊

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure

@Dhruwang
Copy link
Member

Dhruwang commented Oct 3, 2024

Hey @SaiSawant1 , whats the status here ? 😊

@SaiSawant1
Copy link
Contributor Author

Hey @SaiSawant1 , whats the status here ? 😊

almost done.

packages/ui/components/ShuffleOptionSelect
which is imported in MatrixQuestionForm, MultipleChoiceQuestionForm,
RankingQuestionForm
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (10)
packages/ui/components/ShuffleOptionSelect/index.tsx (1)

37-68: LGTM: Component implementation is well-structured. Consider adding error handling.

The ShuffleOptionSelect component is well-implemented, following React best practices. The use of destructured props and efficient rendering of SelectItem components are commendable.

A minor suggestion for improvement:

Consider adding error handling or a fallback UI for cases where shuffleOptionsTypes might be empty or undefined. This could prevent potential runtime errors and improve the component's robustness.

Example:

{Object.values(shuffleOptionsTypes).length > 0 ? (
  Object.values(shuffleOptionsTypes).map(
    // ... existing code
  )
) : (
  <SelectItem value="none" disabled>No options available</SelectItem>
)}

This change ensures that the component always renders something, even if there are no shuffle options available.

packages/surveys/src/lib/utils.ts (4)

21-35: LGTM: New function getRandomRowIdx implemented correctly.

The function successfully implements the shuffling logic for both "all" and "exceptLast" options, aligning with the PR objective. It correctly uses the existing shuffle function and handles the "exceptLast" case properly.

Consider adding input validation for the n parameter to ensure it's a positive integer:

if (n <= 0 || !Number.isInteger(n)) {
  throw new Error("n must be a positive integer");
}

37-49: LGTM: New function getShuffledRows implemented correctly.

The function successfully implements the shuffling logic for both "all" and "exceptLast" options, aligning with the PR objective. It correctly uses the existing shuffle function and handles the "exceptLast" case properly.

Consider refactoring to reuse the logic from getRandomRowIdx:

export const getShuffledRows = (rows: TI18nString[], shuffleOption: TShuffleOption): TI18nString[] => {
  const shuffledIndices = getRandomRowIdx(rows.length, shuffleOption);
  return shuffledIndices.map(index => rows[index]);
};

This refactoring would reduce code duplication and improve maintainability.


51-54: LGTM: Function getShuffledChoicesIds updated correctly.

The function has been successfully updated to use the new TShuffleOption type and implement the shuffling logic for both "all" and "exceptLast" options. It maintains the existing functionality for handling the "other" option.

For consistency with getShuffledRows, consider using the spread operator when creating shuffledChoices:

const shuffledChoices = otherOption ? choices.filter((choice) => choice.id !== "other") : [...choices];

This change would make the code more consistent across similar functions.


Remaining usages of the old shuffle method detected.

The new shuffling functions (getRandomRowIdx, getShuffledRows, and getShuffledChoicesIds) are correctly integrated and used in the codebase. However, the old shuffle method is still being utilized within utils.ts. Please refactor the code to replace all instances of the old shuffle function with the new shuffling functions to maintain consistency and improve code maintainability.

🔗 Analysis chain

Line range hint 1-124: Overall implementation looks good, with room for minor improvements.

The changes successfully implement the row randomization feature for matrix questions, aligning with the PR objective. The new functions and modifications to existing ones are consistent with the codebase's style and structure.

Consider grouping related functions together for better code organization. For example, you could place getRandomRowIdx, getShuffledRows, and getShuffledChoicesIds in a section dedicated to shuffling operations. This would improve the file's readability and make it easier to maintain related functionality.

To ensure that the changes don't introduce any unintended side effects, please run the following verification script:

This script will help identify any places where the new functions are used and ensure that the old shuffling method with a string option is no longer in use.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that the new shuffling functions are used correctly throughout the codebase.

# Test: Search for usages of the new and modified functions
echo "Searching for usages of getRandomRowIdx:"
rg --type typescript 'getRandomRowIdx'

echo "Searching for usages of getShuffledRows:"
rg --type typescript 'getShuffledRows'

echo "Searching for usages of getShuffledChoicesIds:"
rg --type typescript 'getShuffledChoicesIds'

# Test: Check for any remaining usages of the old shuffling method
echo "Checking for any remaining usages of the old shuffling method:"
rg --type typescript 'shuffle.*option.*string'

Length of output: 731


Script:

#!/bin/bash
# Description: Verify that the new shuffling functions are used correctly throughout the codebase.

# Test: Search for usages of the new and modified functions within .ts and .tsx files
echo "Searching for usages of getRandomRowIdx:"
rg --glob '*.ts' --glob '*.tsx' 'getRandomRowIdx'

echo "Searching for usages of getShuffledRows:"
rg --glob '*.ts' --glob '*.tsx' 'getShuffledRows'

echo "Searching for usages of getShuffledChoicesIds:"
rg --glob '*.ts' --glob '*.tsx' 'getShuffledChoicesIds'

# Test: Check for any remaining usages of the old shuffling method without relying on file type
echo "Checking for any remaining usages of the old shuffling method:"
rg 'shuffle\([^)]*\)' --glob '*.ts' --glob '*.tsx'

Length of output: 2196

apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RankingQuestionForm.tsx (2)

219-224: LGTM: ShuffleOptionSelect implementation looks good

The addition of the ShuffleOptionSelect component successfully implements the randomization feature for ranking questions, aligning with the PR objectives. The reuse of existing shuffle option types maintains consistency across the application.

Consider adding a comment explaining the purpose of the ShuffleOptionSelect component for better code readability.

You could add a comment above the ShuffleOptionSelect component to explain its purpose:

+            {/* ShuffleOptionSelect allows users to choose how to randomize the order of ranking options */}
            <ShuffleOptionSelect
              shuffleOptionsTypes={shuffleOptionsTypes}
              updateQuestion={updateQuestion}
              shuffleOption={question.shuffleOption}
              questionIdx={questionIdx}
            />

Line range hint 1-230: Consider extracting shuffleOptionsTypes for better reusability

The overall implementation of the RankingQuestionForm component is well-structured and successfully integrates the new randomization feature while preserving existing functionality. However, to improve code organization and reusability, consider extracting the shuffleOptionsTypes object to a separate file or a higher-level component.

You could create a new file, e.g., shuffleOptions.ts, and move the shuffleOptionsTypes definition there:

// shuffleOptions.ts
export const shuffleOptionsTypes = {
  none: {
    id: "none",
    label: "Keep current order",
    show: true,
  },
  all: {
    id: "all",
    label: "Randomize all",
    show: true, // You might want to make this dynamic based on the context
  },
};

Then, import and use it in the RankingQuestionForm component:

import { shuffleOptionsTypes } from './shuffleOptions';

// ... rest of the component code

// Use shuffleOptionsTypes directly in the ShuffleOptionSelect component
<ShuffleOptionSelect
  shuffleOptionsTypes={shuffleOptionsTypes}
  // ... other props
/>

This change would make the shuffle options more reusable across different components and easier to maintain.

apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MultipleChoiceQuestionForm.tsx (1)

Line range hint 54-69: LGTM: Enhanced shuffle options with improved flexibility.

The updates to the shuffleOptionsTypes object provide more flexibility in randomization options, which aligns well with the PR objectives. The condition for showing the "all" option based on the absence of an "other" choice is a good safeguard against potential conflicts.

Consider extracting the shuffleOptionsTypes object to a separate constant or configuration file to improve code organization and maintainability, especially if these options might be reused elsewhere in the application.

packages/types/surveys/types.ts (2)

576-576: LGTM! Consider adding a comment for clarity.

The addition of the shuffleOption property to ZSurveyMatrixQuestion is well-implemented and aligns with the PR objective. It uses the existing ZShuffleOption type and provides a sensible default value, maintaining backwards compatibility.

Consider adding a brief comment explaining the purpose of the shuffleOption property, e.g.:

// Determines how the rows of the matrix question should be shuffled (if at all)
shuffleOption: ZShuffleOption.optional().default("none"),

Line range hint 1-1024: Overall, the change is well-implemented and consistent. Consider standardizing shuffle options across question types.

The addition of the shuffleOption to ZSurveyMatrixQuestion is isolated and doesn't negatively impact the rest of the file. It's consistent with similar implementations in ZSurveyMultipleChoiceQuestion and ZSurveyRankingQuestion.

For improved consistency and maintainability, consider creating a reusable interface or type for questions that support shuffling. This could be applied to ZSurveyMatrixQuestion, ZSurveyMultipleChoiceQuestion, and ZSurveyRankingQuestion. For example:

interface IShuffleable {
  shuffleOption: z.ZodOptional<typeof ZShuffleOption>;
}

// Then use it in the question types:
export const ZSurveyMatrixQuestion = ZSurveyQuestionBase.extend({
  // ... other properties
}).extend<IShuffleable>({
  shuffleOption: ZShuffleOption.optional().default("none"),
});

This approach would make it easier to manage and extend shuffle functionality across different question types in the future.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between b078655 and 16170a7.

📒 Files selected for processing (7)
  • apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx (3 hunks)
  • apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MultipleChoiceQuestionForm.tsx (2 hunks)
  • apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RankingQuestionForm.tsx (2 hunks)
  • packages/surveys/src/components/questions/MatrixQuestion.tsx (3 hunks)
  • packages/surveys/src/lib/utils.ts (2 hunks)
  • packages/types/surveys/types.ts (1 hunks)
  • packages/ui/components/ShuffleOptionSelect/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx
  • packages/surveys/src/components/questions/MatrixQuestion.tsx
🔇 Additional comments (9)
packages/ui/components/ShuffleOptionSelect/index.tsx (3)

1-7: LGTM: Imports are appropriate and well-structured.

The imports are correctly specified, importing necessary types from the surveys package and UI components from the local Select component. This demonstrates good code organization and type safety practices.


27-35: LGTM: ShuffleOptionSelectInterface is well-defined.

The interface is clear, properly typed, and uses appropriate TypeScript features. The use of Partial<...> in the updateQuestion function signature allows for flexible updates, which is a good practice.


1-68: Summary: Component aligns well with PR objectives, with minor suggestions for improvement.

The ShuffleOptionSelect component effectively implements the functionality to allow users to randomize the order of rows in matrix questions, as outlined in the PR objectives. It's designed to be flexible, supporting different question types (matrix, multiple-choice, ranking) through its interface.

The implementation is generally well-structured and follows React and TypeScript best practices. The suggested improvements (simplifying type definitions and adding error handling) are minor and aimed at enhancing maintainability and robustness.

This component contributes significantly to addressing issue #3156 by providing a user interface for selecting shuffle options, which will help reduce bias in survey responses.

packages/surveys/src/lib/utils.ts (1)

2-3: LGTM: New type imports added.

The addition of TI18nString and TShuffleOption types aligns with the PR objective of implementing row randomization for matrix questions.

apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RankingQuestionForm.tsx (2)

10-10: LGTM: Import changes align with new ShuffleOptionSelect component

The changes in imports are consistent with the introduction of the ShuffleOptionSelect component and the removal of the Select component. This refactoring aligns well with the PR objectives of implementing row randomization for matrix questions.

Also applies to: 14-14


Line range hint 1-230: Overall: Good implementation of randomization feature

The changes in this file successfully implement the randomization feature for ranking questions, meeting the PR objectives. The code maintains good structure, preserves existing functionality, and integrates well with the current component design. The suggested minor improvements in code organization and documentation will further enhance maintainability and readability.

Great job on this implementation!

apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MultipleChoiceQuestionForm.tsx (3)

22-22: LGTM: Import of ShuffleOptionSelect aligns with PR objectives.

The introduction of the ShuffleOptionSelect component and the removal of the generic Select component align well with the PR's objective of randomizing row order in matrix questions. This change suggests a more specialized and tailored approach to handling shuffle options in the survey editor.


Line range hint 1-354: Overall LGTM: Randomization feature successfully implemented.

The changes in this file successfully implement the randomization feature for matrix questions, addressing the objectives outlined in the PR and the linked issue #3156. The introduction of the ShuffleOptionSelect component and the updates to the shuffle options provide the necessary functionality to randomize row order in matrix questions.

Key points:

  1. The new ShuffleOptionSelect component is well-integrated into the existing MultipleChoiceQuestionForm.
  2. The shuffleOptionsTypes object provides flexible options for randomization, including safeguards for the "other" choice.
  3. The implementation aligns with the goal of reducing bias in survey responses.

The code changes appear to be correct and do not introduce any apparent issues or conflicts with the existing functionality. However, thorough testing of the new randomization feature in various scenarios is recommended to ensure it behaves as expected across different question configurations.


287-292: LGTM: ShuffleOptionSelect implementation looks correct.

The implementation of the ShuffleOptionSelect component appears to be correct, with all necessary props being passed. This change provides more specific control over the shuffle options UI and behavior, which aligns with the PR objectives.

To ensure the component is working as expected, please verify the following:

  1. The shuffle options are displayed correctly in the UI.
  2. Selecting different shuffle options updates the question state correctly.
  3. The "all" option is hidden when an "other" choice is present.

You can use the following script to check for the component's usage and prop passing:

✅ Verification successful

Verified: ShuffleOptionSelect usage is correct.

The ShuffleOptionSelect component is properly imported and utilized in MultipleChoiceQuestionForm.tsx, with the shuffleOption prop correctly passed as question.shuffleOption. No issues were found in the implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify ShuffleOptionSelect usage and prop passing

# Check for ShuffleOptionSelect usage
echo "Checking ShuffleOptionSelect usage:"
rg --type typescript 'ShuffleOptionSelect' apps/web/app

# Check for shuffleOption prop in MultipleChoiceQuestionForm
echo "Checking shuffleOption prop:"
rg --type typescript 'shuffleOption={question\.shuffleOption}' apps/web/app

Length of output: 453


Script:

#!/bin/bash
# Description: Verify ShuffleOptionSelect usage and prop passing

# Check for ShuffleOptionSelect usage
echo "Checking ShuffleOptionSelect usage:"
rg --type ts 'ShuffleOptionSelect' apps/web/app

# Check for shuffleOption prop in MultipleChoiceQuestionForm
echo "Checking shuffleOption prop:"
rg --type ts 'shuffleOption=\{question\.shuffleOption\}' apps/web/app

Length of output: 1909

Comment on lines +9 to +25
type ShuffleOptionsTypes = {
none?: {
id: string;
label: string;
show: boolean;
};
all?: {
id: string;
label: string;
show: boolean;
};
exceptLast?: {
id: string;
label: string;
show: boolean;
};
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider simplifying the ShuffleOptionsTypes type.

The current structure of ShuffleOptionsTypes is verbose and repetitive. Consider simplifying it to reduce duplication and improve maintainability.

Here's a suggested refactoring:

type ShuffleOptionType = {
  id: string;
  label: string;
  show: boolean;
};

type ShuffleOptionsTypes = {
  none?: ShuffleOptionType;
  all?: ShuffleOptionType;
  exceptLast?: ShuffleOptionType;
};

This approach reduces repetition and makes it easier to add new shuffle options in the future.

@SaiSawant1
Copy link
Contributor Author

Hey @Dhruwang , I made all the changes you requested and also created the packages/ui/components/ShuffleOptionsSelect component. However, I don't know if I followed the correct coding practices while creating the component. I tried to look at other components but couldn't get any systematic approach to follow please suggest any changes to follow a particular pattern.
ALSO sorry for the poor video quality of my videos and for taking so much time to push the changes.

random_matrix.mp4

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
packages/ui/components/ShuffleOptionSelect/index.tsx (2)

21-29: Well-defined interface with a minor improvement suggestion.

The ShuffleOptionSelectInterface is comprehensive and includes all necessary properties for the component. However, to improve readability and maintainability, consider extracting the updateQuestion function type into a separate type definition.

Consider refactoring the updateQuestion function type:

type UpdateQuestionFunction = (
  questionIdx: number,
  updatedAttributes: Partial<TSurveyMatrixQuestion | TSurveyMultipleChoiceQuestion | TSurveyRankingQuestion>
) => void;

interface ShuffleOptionSelectInterface {
  shuffleOption: TShuffleOption | undefined;
  updateQuestion: UpdateQuestionFunction;
  questionIdx: number;
  shuffleOptionsTypes: ShuffleOptionsTypes;
}

This change would make the interface more readable and easier to maintain.


31-62: Well-implemented component with a minor suggestion.

The ShuffleOptionSelect component is well-structured and follows React best practices. It effectively uses the Select component from the UI library and correctly implements the shuffle option selection functionality.

Consider adding a default placeholder text for better user experience:

- <SelectValue placeholder="Select ordering" />
+ <SelectValue placeholder={shuffleOption ? undefined : "Select ordering"} />

This change ensures that the placeholder is only shown when no option is selected, improving the component's usability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 16170a7 and d97e90a.

📒 Files selected for processing (1)
  • packages/ui/components/ShuffleOptionSelect/index.tsx (1 hunks)
🔇 Additional comments (3)
packages/ui/components/ShuffleOptionSelect/index.tsx (3)

1-7: Imports look good!

The necessary types and UI components are imported correctly. There are no unused imports, which is good for maintaining a clean codebase.


9-19: Well-structured type definitions!

The ShuffleOptionType and ShuffleOptionsTypes are clearly defined and provide a good structure for the shuffle options. The implementation follows the previous refactoring suggestion, which has resulted in a more maintainable and flexible type structure.


1-62: Overall, excellent implementation of the ShuffleOptionSelect component!

This new component aligns well with the PR objectives of allowing users to randomize the order of rows in matrix questions. The implementation is flexible enough to handle different types of questions (matrix, multiple-choice, ranking) as required.

Key strengths:

  1. Well-structured type definitions and interface
  2. Clean and efficient component implementation
  3. Proper use of UI library components for consistency

The minor suggestions provided in the review comments will further enhance the component's maintainability and user experience. Great job on addressing the feature request outlined in issue #3156!

@SaiSawant1 SaiSawant1 requested a review from Dhruwang October 3, 2024 21:19
Copy link
Member

@Dhruwang Dhruwang left a comment

Choose a reason for hiding this comment

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

Thank you for the changes, looks good 😊🚀✅

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (5)
packages/ui/components/ShuffleOptionSelect/index.tsx (1)

31-62: Well-implemented component with a minor suggestion

The ShuffleOptionSelect component is well-structured and follows React best practices. The use of the Select component from your UI library promotes consistency, and the conditional rendering of SelectItem components is a good approach.

One minor suggestion for improved type safety:

onValueChange={(e: TShuffleOption) => {
  updateQuestion(questionIdx, { shuffleOption: e });
}}

Consider using a more specific type for e instead of TShuffleOption. Since shuffleOptionsTypes defines the possible values, you could use:

onValueChange={(e: keyof ShuffleOptionsTypes) => {
  updateQuestion(questionIdx, { shuffleOption: e });
}}

This ensures that only valid shuffle options can be selected.

packages/surveys/src/lib/utils.ts (2)

21-35: LGTM: New function implements shuffling logic correctly.

The getShuffledRowIndices function correctly implements the shuffling logic based on the provided option, handling both "all" and "exceptLast" cases as required. This aligns well with the PR objectives for randomizing row order in matrix questions.

Consider adding a check for edge cases where n is 0 or negative to ensure robust behavior:

 export const getShuffledRowIndices = (n: number, shuffleOption: TShuffleOption): number[] => {
+  if (n <= 0) return [];
   // Rest of the function remains the same
 };

37-40: LGTM: Function updated to use new shuffling logic.

The getShuffledChoicesIds function has been successfully updated to use the TShuffleOption type and implement the new shuffling logic. The changes are consistent with the PR objectives and maintain the existing functionality for handling the "other" option.

Consider refactoring to reuse logic from getShuffledRowIndices for consistency and maintainability:

export const getShuffledChoicesIds = (
  choices: TSurveyQuestionChoice[],
  shuffleOption: TShuffleOption
): string[] => {
  const otherOption = choices.find((choice) => choice.id === "other");
  const choicesWithoutOther = otherOption ? choices.filter((choice) => choice.id !== "other") : choices;
  
  const shuffledIndices = getShuffledRowIndices(choicesWithoutOther.length, shuffleOption);
  const shuffledChoices = shuffledIndices.map(index => choicesWithoutOther[index]);
  
  if (otherOption) shuffledChoices.push(otherOption);
  
  return shuffledChoices.map((choice) => choice.id);
};

This refactoring would centralize the shuffling logic in getShuffledRowIndices, making the code more DRY and easier to maintain.

apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx (2)

82-98: LGTM with suggestions: shuffleOptionsTypes object

The shuffleOptionsTypes object is well-structured and covers the main scenarios for shuffling options. However, consider the following suggestions:

  1. If the 'show' property is always true, you might want to remove it to simplify the object structure.
  2. If this object is or will be used in multiple components, consider moving it to a separate constants file for better maintainability.

Example of simplified structure:

const shuffleOptionsTypes = {
  none: { id: "none", label: "Keep current order" },
  all: { id: "all", label: "Randomize all" },
  exceptLast: { id: "exceptLast", label: "Randomize all except last option" },
};

233-240: LGTM with suggestion: ShuffleOptionSelect implementation

The ShuffleOptionSelect component is correctly implemented and placed within the form structure. All necessary props are passed, which aligns with the PR objectives to allow users to randomize the order of rows in matrix questions.

Suggestion: Consider adding a brief comment above this section to explain its purpose and how it relates to the matrix question functionality. This will help other developers understand the context quickly.

Example:

{/* Shuffle options for randomizing matrix question rows */}
<div className="mt-3 flex flex-1 items-center justify-end gap-2">
  <ShuffleOptionSelect
    // ... (rest of the code)
  />
</div>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d97e90a and ad0ad90.

📒 Files selected for processing (4)
  • apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx (3 hunks)
  • packages/surveys/src/components/questions/MatrixQuestion.tsx (3 hunks)
  • packages/surveys/src/lib/utils.ts (2 hunks)
  • packages/ui/components/ShuffleOptionSelect/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/surveys/src/components/questions/MatrixQuestion.tsx
🔇 Additional comments (7)
packages/ui/components/ShuffleOptionSelect/index.tsx (3)

1-7: Imports look good!

The necessary types and UI components are imported correctly. There are no unused imports, which is a good practice.


1-62: Overall, a well-implemented component with minor improvement opportunities

The ShuffleOptionSelect component is well-structured and achieves its purpose of allowing users to select shuffle options for survey questions. It follows React best practices and makes good use of the existing UI components.

To further enhance the code:

  1. Consider simplifying the interface definitions as suggested earlier.
  2. Improve type safety in the onValueChange handler.
  3. Ensure that the shuffleOptionsTypes prop is consistently provided with the expected structure.

These minor improvements will make the component more robust and easier to maintain in the future.


9-29: 🛠️ Refactor suggestion

Well-structured interfaces, but consider simplification

The interfaces are well-defined and provide clear type information. However, as suggested in a previous review, consider simplifying the ShuffleOptionsTypes interface to reduce repetition:

type ShuffleOptionType = {
  id: string;
  label: string;
  show: boolean;
};

type ShuffleOptionsTypes = {
  [key in 'none' | 'all' | 'exceptLast']?: ShuffleOptionType;
};

This approach would make it easier to add or modify shuffle options in the future while maintaining type safety.

packages/surveys/src/lib/utils.ts (2)

2-2: LGTM: Import statement updated correctly.

The addition of TShuffleOption to the import statement is consistent with its usage in the new and modified functions. This change aligns well with the PR objectives of implementing row order randomization for matrix questions.


Line range hint 1-40: Overall: Implementation successfully adds row randomization functionality.

The changes in this file successfully implement the functionality for randomizing row order in matrix questions, aligning well with the PR objectives. The code quality is good, with clear and logical implementations of the shuffling algorithms.

A few minor suggestions have been made for improvement:

  1. Adding edge case handling for the getShuffledRowIndices function.
  2. Considering a refactor of getShuffledChoicesIds to reuse logic from getShuffledRowIndices.

These suggestions aim to enhance the robustness and maintainability of the code. Great job on implementing this feature!

apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm.tsx (2)

10-10: LGTM: Import statement for ShuffleOptionSelect

The import statement for the ShuffleOptionSelect component is correctly placed and follows proper naming conventions. Good job on using a shared UI component for better code reusability.


Line range hint 1-243: Summary: Successfully implemented row randomization for matrix questions

Great job on implementing the feature to randomize row order in matrix questions! The changes align well with the PR objectives and enhance the survey design capabilities as requested in issue #3156. The code is well-structured, follows React best practices, and integrates smoothly with the existing MatrixQuestionForm component.

A few minor suggestions have been made to further improve code quality and maintainability. Overall, this implementation effectively addresses the need for reducing bias in survey responses by allowing row randomization in matrix questions.

Next steps:

  1. Address the minor suggestions if you agree with them.
  2. Ensure that you've signed the Contributor License Agreement (CLA) as mentioned in the PR checklist.
  3. Double-check that all tests pass and the code builds without warnings.

Once these steps are completed, this PR should be ready for final review and merging. Great work on enhancing the survey functionality!

@Dhruwang Dhruwang enabled auto-merge October 4, 2024 11:51
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between ad0ad90 and 5301fbd.

📒 Files selected for processing (1)
  • packages/surveys/src/components/questions/RankingQuestion.tsx (2 hunks)
🔇 Additional comments (1)
packages/surveys/src/components/questions/RankingQuestion.tsx (1)

8-8: Importing getShuffledChoicesIds

The addition of getShuffledChoicesIds to the import statement from @/lib/utils is correct and allows for the new shuffling functionality.

question.choices.filter((c) => !value.includes(c.id))
);
const [unsortedItems, setUnsortedItems] = useState<TSurveyQuestionChoice[]>(() => {
if (!question.shuffleOption || question.shuffleOption === "none" || sortedItems.length > 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Verify the condition for shuffling

The condition sortedItems.length > 1 might unintentionally prevent shuffling after more than one item is sorted. If the intent is to stop shuffling once the user has started sorting, consider changing the condition to sortedItems.length > 0.

Comment on lines +58 to +60
return shuffledChoiceIds
.map((choiceId) => question.choices.find((choice) => choice.id === choiceId))
.filter((choice) => choice !== undefined);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize choice lookup to improve performance

Using find within a map can lead to inefficient lookups, especially with larger datasets. To enhance performance, consider creating a lookup map for question.choices.

Apply this diff to optimize the choice mapping:

+  const choicesMap = useMemo(() => {
+    const map = new Map<string, TSurveyQuestionChoice>();
+    question.choices.forEach((choice) => {
+      map.set(choice.id, choice);
+    });
+    return map;
+  }, [question.choices]);

   return shuffledChoiceIds
-    .map((choiceId) => question.choices.find((choice) => choice.id === choiceId))
+    .map((choiceId) => choicesMap.get(choiceId))
     .filter((choice): choice is TSurveyQuestionChoice => choice !== undefined);

Committable suggestion was skipped due to low confidence.

@Dhruwang Dhruwang added this pull request to the merge queue Oct 5, 2024
Merged via the queue into formbricks:main with commit d8cb1c5 Oct 5, 2024
@oss-gg
Copy link

oss-gg bot commented Oct 5, 2024

Awarding SaiSawant1: 150 points 🕹️ Well done! Check out your new contribution on oss.gg/SaiSawant1

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Randomize row order for Matrix questions

2 participants