Skip to content

feat: custom data fetching for surveys table#3318

Merged
mattinannt merged 13 commits intomainfrom
chore-central-data-fetching-for-surveys-table
Oct 10, 2024
Merged

feat: custom data fetching for surveys table#3318
mattinannt merged 13 commits intomainfrom
chore-central-data-fetching-for-surveys-table

Conversation

@Dhruwang
Copy link
Member

@Dhruwang Dhruwang commented Oct 7, 2024

What does this PR do?

Fixes https://github.com/formbricks/internal/issues/383 #2844

How should this be tested?

Test surveys table

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

Release Notes

  • New Features

    • Introduced a new SurveyCard component for displaying survey information.
    • Added functionality for retrieving and managing survey lists and items.
    • Added a new SurveyTypeIndicator component for visual representation of survey types.
  • Improvements

    • Updated components to use a centralized UI library for better modularity.
    • Streamlined survey retrieval logic and simplified component structures.
    • Enhanced type safety and validation for survey-related data structures.
    • Removed orientation features from survey components, simplifying usability.
  • Bug Fixes

    • Resolved issues related to survey data retrieval and display.
  • Chores

    • Reorganized import paths for better maintainability and clarity.

@vercel
Copy link

vercel bot commented Oct 7, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
formbricks-cloud ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 10, 2024 2:17pm
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
formbricks-docs ⬜️ Ignored (Inspect) Visit Preview Oct 10, 2024 2:17pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 7, 2024

Walkthrough

The changes in this pull request involve significant modifications across various files related to survey management. Key updates include the introduction of new functions for retrieving survey data, updates to import statements for enhanced modularity, and changes in type definitions for survey-related components. Several components have been restructured to reflect the new data structures, while some existing components have been introduced or removed. The overall organization of the codebase has been improved to streamline survey management functionalities.

Changes

File Path Change Summary
apps/web/app/(app)/environments/[environmentId]/surveys/actions.ts Updated import statements, renamed functions to utilize new sources, and modified function logic for survey retrieval.
apps/web/app/(app)/environments/[environmentId]/surveys/components/CopySurveyForm.tsx Updated import paths, changed survey prop type from TSurvey to TSurveyListItem, and refined handling of filteredData in the onSubmit function.
apps/web/app/(app)/environments/[environmentId]/surveys/components/CopySurveyModal.tsx Updated import paths and changed survey prop type from TSurvey to TSurveyListItem.
apps/web/app/(app)/environments/[environmentId]/surveys/components/SortOption.tsx Changed import path for DropdownMenuItem to an absolute path; no changes to functionality.
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCard.tsx Introduced new component SurveyCard to display survey information with props for survey details and actions.
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCopyOptions.tsx Updated import path for getProductsByEnvironmentIdAction and changed survey prop type from TSurvey to TSurveyListItem.
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyDropdownMenu.tsx Updated imports for new action functions and changed handleDeleteSurvey function parameter type from survey: TSurvey to surveyId: string.
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilterDropdown.tsx Updated import paths for UI components; no changes to functionality.
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilters.tsx Removed orientation prop and related state management, updated import paths for UI components.
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyList.tsx Removed orientation state, updated survey data type from TSurvey[] to TSurveyFilters[], and streamlined survey fetching logic.
apps/web/app/(app)/environments/[environmentId]/surveys/lib/surveys.ts Introduced new functions for survey management: getSurveys, getSurveysSortedByRelevance, and getSurvey, along with a surveySelect object.
apps/web/app/(app)/environments/[environmentId]/surveys/types/surveys.ts Introduced new Zod schema ZSurvey and TypeScript type TSurvey for enhanced type safety and validation.
packages/js/src/index.ts Added global interface declaration for Window object with new property formbricks, and introduced a formbricksApp proxy.
packages/types/surveys/types.ts Removed schema ZSurveyCopyFormValidation and type TSurveyCopyFormData, indicating a shift in survey copy form validation logic.

Possibly related PRs

Suggested labels

enhancement, customer request

Suggested reviewers

  • gupta-piyush19

Poem

🐰 In the meadow where surveys bloom,
New actions rise, dispelling gloom.
With cards and forms, we hop and play,
Managing data in a clearer way.
So let’s celebrate this code delight,
A brighter path in our coding 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 7, 2024

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

@Dhruwang Dhruwang requested a review from mattinannt October 7, 2024 12:11
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: 13

🧹 Outside diff range and nitpick comments (11)
apps/web/app/(app)/environments/[environmentId]/surveys/types/survey.ts (1)

4-24: LGTM: Well-structured schema with room for minor improvements.

The ZSurveyListItem schema is comprehensive and well-defined. Here are some observations and suggestions:

  1. The 'type' field uses an enum with a comment about future changes. Consider creating a separate issue to track this refactoring.

  2. The 'singleUse' object includes an 'isEncrypted' field. Ensure that this is handled securely in the application logic.

  3. Consider adding more specific constraints to fields like 'name' (e.g., maximum length) to further validate the data.

Would you like me to create a GitHub issue to track the 'type' field refactoring mentioned in the comment?

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCopyOptions.tsx (1)

Line range hint 17-50: LGTM! Consider enhancing error handling.

The overall structure and logic of the component remain sound. The use of a loading state with a spinner and the error handling with toast notifications are good practices.

Consider adding a fallback UI or retry mechanism in case of a failed product fetch. This could improve the user experience in error scenarios. For example:

if (getProductsByEnvironmentIdResponse?.data) {
  setProducts(getProductsByEnvironmentIdResponse?.data);
} else {
  const errorMessage = getFormattedErrorMessage(getProductsByEnvironmentIdResponse);
  toast.error(errorMessage);
  setProducts([]); // Set empty array to allow rendering of form without products
}
apps/web/app/(app)/environments/[environmentId]/surveys/actions.ts (3)

3-6: LGTM! Consider grouping related imports.

The import changes align well with the renamed functions and new functionality. Good job on keeping the imports up-to-date with the changes in the file.

Consider grouping related imports together for better readability. For example, you could move the deleteSurvey import next to the other survey-related imports:

import {
  getSurveyListItem,
  getSurveyListItems,
} from "@/app/(app)/environments/[environmentId]/surveys/lib/surveys";
import { copySurveyToOtherEnvironment, deleteSurvey } from "@formbricks/lib/survey/service";

Also applies to: 15-15


Line range hint 132-147: LGTM! Consider updating the schema name for consistency.

The renaming of getSurveysAction to getSurveyListItemsAction and the use of getSurveyListItems are consistent with the import changes. The addition of schema validation for filterCriteria in the authorization check is a good security improvement.

For consistency with the new function name, consider updating the schema name:

const ZGetSurveyListItemsAction = z.object({
  // ... existing properties ...
});

export const getSurveyListItemsAction = authenticatedActionClient
  .schema(ZGetSurveyListItemsAction)
  // ... rest of the function ...

Line range hint 89-101: LGTM! Consider adding error handling.

The addition of deleteSurveyAction is a good improvement, providing a way to delete surveys with proper authorization checks.

Consider adding error handling to provide more informative feedback:

export const deleteSurveyAction = authenticatedActionClient
  .schema(ZDeleteSurveyAction)
  .action(async ({ ctx, parsedInput }) => {
    await checkAuthorization({
      userId: ctx.user.id,
      organizationId: await getOrganizationIdFromSurveyId(parsedInput.surveyId),
      rules: ["survey", "delete"],
    });

    try {
      await deleteSurvey(parsedInput.surveyId);
      return { success: true };
    } catch (error) {
      console.error("Failed to delete survey:", error);
      throw new Error("Failed to delete survey. Please try again.");
    }
  });

This change will provide more context if the deletion fails and allow the client to handle the error appropriately.

apps/web/app/(app)/environments/[environmentId]/surveys/components/CopySurveyForm.tsx (1)

1-2: Approve import changes with a minor suggestion.

The updated imports from "@formbricks/ui/components" and the new type imports improve modularity and align with what seems to be a new component library structure. This is a positive change that should enhance code organization and maintainability.

Consider grouping related imports together for better readability. For example:

// React and form-related imports
import { useFieldArray, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

// Formbricks types and actions
import { copySurveyToOtherEnvironmentAction } from "@/app/(app)/environments/[environmentId]/surveys/actions";
import { TSurveyListItem } from "@/app/(app)/environments/[environmentId]/surveys/types/survey";
import { TSurveyCopyFormData, ZSurveyCopyFormValidation } from "@formbricks/types/surveys/types";
import { TProduct } from "@formbricks/types/product";

// UI components
import { Button } from "@formbricks/ui/components/Button";
import { Checkbox } from "@formbricks/ui/components/Checkbox";
import { FormControl, FormField, FormItem, FormProvider } from "@formbricks/ui/components/Form";
import { Label } from "@formbricks/ui/components/Label";
import { TooltipRenderer } from "@formbricks/ui/components/Tooltip";

// Other utilities
import toast from "react-hot-toast";

Also applies to: 7-12

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyList.tsx (3)

Line range hint 80-89: Add error handling for initial survey fetch

Currently, the component does not handle errors that may occur during the initial fetch of surveys. If the getSurveyListItemsAction fails, the user might not receive feedback, and isFetching may remain true. Consider adding error handling to manage fetch failures gracefully.

Apply this diff to add error handling:

 const fetchInitialSurveys = async () => {
   setIsFetching(true);
+  try {
     const res = await getSurveyListItemsAction({
       environmentId: environment.id,
       limit: surveysLimit,
       offset: undefined,
       filterCriteria: filters,
     });
     if (res?.data) {
       if (res.data.length < surveysLimit) {
         setHasMore(false);
       } else {
         setHasMore(true);
       }
       setSurveys(res.data);
     }
+  } catch (error) {
+    console.error("Failed to fetch surveys:", error);
+    // Optional: set an error state to display an error message to the user
+  } finally {
     setIsFetching(false);
+  }
 };

Line range hint 102-117: Add error handling for pagination fetch

Similar to the initial fetch, the fetchNextPage function lacks error handling for fetch failures. This could lead to unintended behavior if the request fails. Incorporate try-catch blocks to handle errors appropriately.

Apply this diff to add error handling:

 const fetchNextPage = useCallback(async () => {
   setIsFetching(true);
+  try {
     const res = await getSurveyListItemsAction({
       environmentId: environment.id,
       limit: surveysLimit,
       offset: surveys.length,
       filterCriteria: filters,
     });
     if (res?.data) {
       if (res.data.length === 0 || res.data.length < surveysLimit) {
         setHasMore(false);
       } else {
         setHasMore(true);
       }

       setSurveys([...surveys, ...res.data]);
     }
+  } catch (error) {
+    console.error("Failed to fetch more surveys:", error);
+    // Optional: set an error state to inform the user
+  } finally {
     setIsFetching(false);
+  }
 }, [environment.id, surveys, surveysLimit, filters]);

Line range hint 68-79: Optimize local storage handling with useEffect dependencies

In the useEffect hook for initializing survey filters, the check typeof window !== "undefined" is used to ensure code runs only on the client side. However, since this is a client-side component ("use client"; at the top), this check may be unnecessary. Additionally, consider adding a dependency array to prevent potential re-renders.

Apply this diff to optimize the useEffect hook:

 useEffect(() => {
-  if (typeof window !== "undefined") {
     const savedFilters = localStorage.getItem(FORMBRICKS_SURVEYS_FILTERS_KEY_LS);
     if (savedFilters) {
       const surveyParseResult = wrapThrows(() => JSON.parse(savedFilters))();

       if (!surveyParseResult.ok) {
         localStorage.removeItem(FORMBRICKS_SURVEYS_FILTERS_KEY_LS);
         setSurveyFilters(initialFilters);
       } else {
         setSurveyFilters(surveyParseResult.data);
       }
     }
     setIsFilterInitialized(true);
-  }
+}, []);
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyDropdownMenu.tsx (2)

Line range hint 59-67: Enhance error handling in handleDeleteSurvey

The catch block currently provides a generic error message:

catch (error) {
  toast.error("An error occured while deleting survey");
}

Consider including the error details to aid in debugging:

catch (error) {
  toast.error(`An error occurred while deleting survey: ${error.message}`);
}

Additionally, using a finally block can ensure setLoading(false) is called regardless of success or failure:

setLoading(true);
try {
  await deleteSurveyAction({ surveyId });
  deleteSurvey(surveyId);
  router.refresh();
  setDeleteDialogOpen(false);
  toast.success("Survey deleted successfully.");
} catch (error) {
  toast.error(`An error occurred while deleting survey: ${error.message}`);
} finally {
  setLoading(false);
}

Line range hint 86-92: Improve error feedback in duplicateSurveyAndRefresh

The catch block provides a generic error message:

catch (error) {
  toast.error("Failed to duplicate the survey.");
}

Include the error details for more informative feedback:

catch (error) {
  toast.error(`Failed to duplicate the survey: ${error.message}`);
}

Also, consider using a finally block to ensure setLoading(false) is always executed:

setLoading(true);
try {
  // existing code...
} catch (error) {
  toast.error(`Failed to duplicate the survey: ${error.message}`);
} finally {
  setLoading(false);
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 7207a4d and db064e2.

📒 Files selected for processing (17)
  • apps/web/app/(app)/environments/[environmentId]/surveys/actions.ts (6 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/CopySurveyForm.tsx (2 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/CopySurveyModal.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SortOption.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCard.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCopyOptions.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyDropdownMenu.tsx (5 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilterDropdown.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilters.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyList.tsx (5 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/lib/surveys.ts (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/loading.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/types/survey.ts (1 hunks)
  • packages/lib/localStorage.ts (0 hunks)
  • packages/lib/survey/service.ts (0 hunks)
  • packages/ui/components/SurveysList/components/SurveyCard.tsx (0 hunks)
💤 Files with no reviewable changes (3)
  • packages/lib/localStorage.ts
  • packages/lib/survey/service.ts
  • packages/ui/components/SurveysList/components/SurveyCard.tsx
🧰 Additional context used
🪛 Biome
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCard.tsx

[error] 49-49: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

🔇 Additional comments (31)
apps/web/app/(app)/environments/[environmentId]/surveys/types/survey.ts (3)

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

The imports are correctly defined and align with the file's purpose. The zod library is imported for schema definition, and ZSurveyStatus is imported from a custom types file, which is good for maintaining consistency across the project.


26-26: LGTM: Type export is correctly defined.

The TSurveyListItem type is correctly inferred from the ZSurveyListItem schema using z.infer. This approach ensures type consistency between the schema and the TypeScript type, which is a best practice when using Zod with TypeScript.


1-26: Overall assessment: Well-implemented schema and type definition for survey list items.

This new file introduces a robust Zod schema (ZSurveyListItem) and corresponding TypeScript type (TSurveyListItem) for survey list items. The implementation is clean, well-structured, and follows good practices for type safety and data validation.

Key strengths:

  1. Comprehensive schema definition covering various aspects of a survey list item.
  2. Proper use of Zod for schema validation and TypeScript type inference.
  3. Flexibility in handling nullable fields like 'creator' and 'singleUse'.

Areas for potential improvement:

  1. Consider adding more specific constraints to some fields for tighter validation.
  2. Plan for the future refactoring of the 'type' field as mentioned in the comment.

These changes align well with the PR objectives of implementing custom data fetching for the surveys table, providing a solid foundation for type-safe operations on survey data throughout the application.

apps/web/app/(app)/environments/[environmentId]/surveys/loading.tsx (1)

1-1: Approve import path change, but verify consistency.

The updated import path for the SurveyLoading component reflects a change in the project structure, which could improve organization. However, it's crucial to ensure this change is applied consistently across the entire project.

Please run the following script to verify the consistency of this import change:

Consider updating the project documentation to reflect this structural change if it represents a significant shift in component organization.

✅ Verification successful

Import path change verified successfully

The updated import path for the SurveyLoading component is consistently applied across the codebase. No remaining instances of the old import path were found.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining old import paths and new import paths for SurveyLoading

echo "Checking for old import paths:"
rg --type typescript --type tsx "from\s+['\"]@formbricks/ui/components/SurveysList/components/SurveyLoading['\"]"

echo "Checking for new import paths:"
rg --type typescript --type tsx "from\s+['\"]@/app/\(app\)/environments/\[environmentId\]/surveys/components/SurveyLoading['\"]"

Length of output: 466


Script:

#!/bin/bash
# Description: Check for any remaining old import paths and new import paths for SurveyLoading with corrected file types

echo "Checking for old import paths:"
rg --type ts --type tsx "from\s+['\"]@formbricks/ui/components/SurveysList/components/SurveyLoading['\"]"

echo "Checking for new import paths:"
rg --type ts --type tsx "from\s+['\"]@/app/\(app\)/environments/\[environmentId\]/surveys/components/SurveyLoading['\"]"

Length of output: 436


Script:

#!/bin/bash
# Description: Check for any remaining old import paths and new import paths for SurveyLoading using file extensions

echo "Checking for old import paths:"
rg "from\s+['\"]@formbricks/ui/components/SurveysList/components/SurveyLoading['\"]" --glob "*.ts" --glob "*.tsx"

echo "Checking for new import paths:"
rg "from\s+['\"]@/app/\(app\)/environments/\[environmentId\]/surveys/components/SurveyLoading['\"]" --glob "*.ts" --glob "*.tsx"

Length of output: 571

apps/web/app/(app)/environments/[environmentId]/surveys/components/CopySurveyModal.tsx (4)

1-1: Improved imports for better modularity and type consistency.

The changes in the import statements are beneficial:

  1. Importing TSurveyListItem type ensures consistency with the new data structure for surveys.
  2. Importing Modal from @formbricks/ui promotes the use of a shared UI component library, enhancing consistency and maintainability across the project.

These changes align well with best practices for modular and maintainable code.

Also applies to: 3-3


Line range hint 1-37: Summary: Improved type safety and modularity with potential compatibility concerns.

The changes to CopySurveyModal.tsx enhance type safety and modularity through:

  1. Updated imports for better consistency with the project structure.
  2. A more specific type (TSurveyListItem) for the survey prop.

These improvements are commendable. However, to ensure these changes don't introduce issues:

  1. Verify that all uses of the survey prop within this component and its children (especially SurveyCopyOptions) are compatible with the new TSurveyListItem type.
  2. Confirm that SurveyCopyOptions component is updated to work with TSurveyListItem.
  3. Ensure no critical data is lost in the transition from TSurvey to TSurveyListItem.

After addressing these points, the changes will significantly contribute to the maintainability and type safety of the codebase.


Line range hint 13-37: Verify SurveyCopyOptions compatibility with TSurveyListItem.

The component structure remains unchanged, which is good for maintaining consistency. However, it's crucial to ensure that the SurveyCopyOptions component is compatible with the new TSurveyListItem type.

Please verify:

  1. That the SurveyCopyOptions component's props are updated to accept TSurveyListItem instead of TSurvey.
  2. That all uses of the survey prop within SurveyCopyOptions are compatible with TSurveyListItem.

Run the following script to check the SurveyCopyOptions component:

#!/bin/bash
# Description: Check SurveyCopyOptions component for compatibility with TSurveyListItem

# Test: Check SurveyCopyOptions props
ast-grep --lang typescript --pattern 'interface $_Props {
  $$$
  survey: $_
  $$$
}' apps/web/app/\(app\)/environments/\[environmentId\]/surveys/components/SurveyCopyOptions.tsx

# Test: Look for uses of survey prop that might be incompatible with TSurveyListItem
ast-grep --lang typescript --pattern '$_.$prop' apps/web/app/\(app\)/environments/\[environmentId\]/surveys/components/SurveyCopyOptions.tsx | grep -v -E '(id|name|environmentId)'

Review the results to ensure SurveyCopyOptions is fully compatible with the new TSurveyListItem type.


9-9: Verify internal usage of the updated survey prop type.

The change from TSurvey to TSurveyListItem for the survey prop is consistent with the updated import. This suggests a shift to using a more specific data structure for surveys in list contexts.

To ensure this change doesn't introduce any issues, please verify:

  1. That all uses of the survey prop within this component and its children are compatible with the TSurveyListItem type.
  2. That no required fields from the previous TSurvey type are missing in TSurveyListItem.

Run the following script to check for potential issues:

If the script returns results, please review them to ensure compatibility with the new TSurveyListItem type.

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCopyOptions.tsx (3)

1-2: LGTM! Verify the new import paths.

The import statements have been updated to reflect changes in the project structure. The addition of TSurveyListItem import aligns with the updated prop type.

Run the following script to verify the import paths:

#!/bin/bash
# Description: Verify the existence of the imported files and their exports.

# Test 1: Check if the file containing getProductsByEnvironmentIdAction exists
fd --type f --full-path ".*environments/\[environmentId\]/surveys/actions.(ts|tsx|js|jsx)$" --exec grep -q "export.*getProductsByEnvironmentIdAction" {} \; && echo "getProductsByEnvironmentIdAction import path is valid" || echo "getProductsByEnvironmentIdAction import path may be incorrect"

# Test 2: Check if the file containing TSurveyListItem exists
fd --type f --full-path ".*environments/\[environmentId\]/surveys/types/survey.(ts|tsx|js|jsx)$" --exec grep -q "export.*TSurveyListItem" {} \; && echo "TSurveyListItem import path is valid" || echo "TSurveyListItem import path may be incorrect"

11-11: LGTM! Ensure consistency of TSurveyListItem usage.

The survey prop type has been updated from TSurvey to TSurveyListItem, which aligns with the new import. This change suggests a modification in the structure of survey data being passed to the component.

Run the following script to verify the consistent usage of TSurveyListItem:

#!/bin/bash
# Description: Check for consistent usage of TSurveyListItem across the codebase.

# Test 1: Find all occurrences of TSurveyListItem
echo "Occurrences of TSurveyListItem:"
rg --type typescript --type tsx "TSurveyListItem"

# Test 2: Check for any remaining usage of TSurvey that might need updating
echo "Potential instances of TSurvey that might need updating:"
rg --type typescript --type tsx "TSurvey(?!ListItem)"

Line range hint 1-50: Summary: Changes look good, verify integration with related components.

The changes to SurveyCopyOptions.tsx primarily involve updates to type definitions and import paths. The core functionality of the component remains intact. These modifications appear to be part of a broader refactoring effort to enhance type safety and improve project structure.

To ensure these changes integrate well with the rest of the application, consider running the following verification steps:

  1. Compile the entire project to check for any type-related errors.
  2. Run the application and navigate to the surveys page to verify that the SurveyCopyOptions component renders correctly.
  3. Test the survey copying functionality to ensure it still works as expected with the updated types.
✅ Verification successful

Action Required: Re-run Verification Scripts with Corrected Commands

Please execute the following corrected shell scripts to gather the necessary information for verification:


Verification Complete: No Issues Found

The import paths for SurveyCopyOptions.tsx and TSurveyListItem are valid and correctly integrated within the codebase. No discrepancies or issues were identified in the verification process.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the integration of SurveyCopyOptions with related components.

# Test 1: Find components that use SurveyCopyOptions
echo "Components using SurveyCopyOptions:"
rg --type typescript --type tsx "import.*SurveyCopyOptions|from ['\"].*SurveyCopyOptions['\"]"

# Test 2: Check for any usage of TSurvey type that might need updating to TSurveyListItem
echo "Potential usage of TSurvey type that might need updating:"
rg --type typescript --type tsx "TSurvey(?!ListItem)"

Length of output: 951


Script:

#!/bin/bash
# Description: Verify the integration of SurveyCopyOptions with related components after correcting previous script errors.

# Test 1: Find components that use SurveyCopyOptions
echo "Components using SurveyCopyOptions:"
rg --pcre2 -g "*.ts" -g "*.tsx" "import.*SurveyCopyOptions|from ['\"].*SurveyCopyOptions['\"]"

# Test 2: Check for any usage of TSurvey type that might need updating to TSurveyListItem
echo "Potential usage of TSurvey type that might need updating to TSurveyListItem:"
rg --pcre2 -g "*.ts" -g "*.tsx" "TSurvey(?!ListItem)"

Length of output: 214461


Script:

#!/bin/bash
# Description: Verify the import paths for TSurveyListItem.

fd --type f --full-path ".*environments/\[environmentId\]/surveys/types/survey\.(ts|tsx|js|jsx)$" --exec grep -q "export.*TSurveyListItem" {} \; && echo "TSurveyListItem import path is valid" || echo "TSurveyListItem import path may be incorrect"

Length of output: 232

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilterDropdown.tsx (2)

3-9: Improved import structure for UI components

The changes to the import statements are a positive improvement:

  1. Centralizing UI component imports from @formbricks/ui/components enhances maintainability and consistency across the application.
  2. This approach makes it easier to manage and update UI components globally.
  3. The standardization aligns with best practices for organizing a component library.

These changes do not affect the component's functionality and structure, making this a low-risk, high-value improvement.


Line range hint 11-71: Component functionality preserved with new imports

I've reviewed the rest of the SurveyFilterDropdown component, and I can confirm:

  1. The component's logic and structure remain unchanged.
  2. The imported UI components are used correctly throughout the component.
  3. The props interface and component functionality are preserved.

This indicates that the import changes have been successfully integrated without affecting the component's behavior. The component should continue to function as expected with the new centralized UI component imports.

apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx (2)

Line range hint 2-89: LGTM: Rest of the file is consistent with the import change

The usage of the SurveysList component in the rest of the file remains consistent with the previous implementation. The props passed to the component are unchanged, which suggests that the interface of the relocated SurveysList component has been maintained.

No other parts of the file appear to be affected by the import change, indicating that the relocation was likely a straightforward move without requiring additional modifications to the component's usage or surrounding code.


1-1: Verify the relocation of SurveysList component

The import statement for the SurveysList component has been updated to a more specific path within the app structure. This change could indicate a move towards environment-specific implementations of the SurveysList.

While this change may provide more flexibility for customization, it's important to consider the following:

  1. Ensure that the new SurveysList component maintains the same interface as the previous one to avoid breaking changes in this file.
  2. Consider the implications for reusability across different parts of the application. If this component is used elsewhere, those imports may need to be updated as well.
  3. Evaluate if this change aligns with the project's overall architecture and component organization strategy.

To ensure consistency across the codebase, run the following script to check for other usages of the SurveysList component:

This will help identify any other files that may need to be updated due to this change.

✅ Verification successful

Relocation of SurveysList Component Verified

The SurveysList component has been successfully relocated to a more specific path within page.tsx. No other imports of SurveysList were found in the codebase, ensuring that this change does not affect other parts of the application.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other imports of SurveysList
rg --type typescript --type tsx 'import.*SurveysList.*from' -g '!**/page.tsx'

Length of output: 114


Script:

#!/bin/bash
# Search for other imports of SurveysList in TypeScript and TSX files, excluding page.tsx
rg --type ts --type tsx 'import\s+.*SurveysList\s+from' -g '!**/page.tsx'

Length of output: 103


Script:

#!/bin/bash
# Search for other imports of SurveysList in TypeScript and TSX files, excluding page.tsx
rg 'import\s+.*SurveysList\s+from' -g '*.ts' -g '*.tsx' -g '!**/page.tsx'

Length of output: 75

apps/web/app/(app)/environments/[environmentId]/surveys/actions.ts (1)

Line range hint 24-33: LGTM! Verify client-side usage of the updated function.

The renaming of getSurveyAction to getSurveyListItemAction and the use of getSurveyListItem are consistent with the import changes. The authorization check is correctly maintained.

Please ensure that all client-side code calling this function has been updated to handle the new data structure (survey list item instead of full survey). Run the following script to check for any remaining usage of the old function name:

✅ Verification successful

Adjust the search command to correctly target TypeScript files:


No remaining usages of getSurveyAction found.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining usage of getSurveyAction
rg 'getSurveyAction' --type typescript --type tsx

Length of output: 84


Script:

# Search for any remaining usage of getSurveyAction in .ts and .tsx files
rg 'getSurveyAction' -g '*.ts' -g '*.tsx'

Length of output: 41

apps/web/app/(app)/environments/[environmentId]/surveys/components/CopySurveyForm.tsx (3)

Line range hint 22-159: Approve unchanged logic with testing recommendation.

The core functionality and structure of the component remain largely unchanged, which is good for maintaining consistency. However, given the changes in imports and types, it's crucial to ensure that the existing functionality still works as expected.

Please perform a comprehensive test of the CopySurveyForm component to verify that:

  1. The form renders correctly with the new imports.
  2. The checkbox functionality for selecting environments works as expected.
  3. The form submission process, including the onSubmit function, operates correctly with the new TSurveyListItem type.
  4. The copySurveyToOtherEnvironmentAction is called with the correct parameters.
  5. Error handling and success messages are displayed appropriately.

Consider adding or updating unit tests to cover these scenarios if they don't already exist.


Line range hint 1-159: Overall assessment: Positive changes with verification needed.

The changes to the CopySurveyForm component, primarily focusing on updating imports and types, appear to be part of a larger refactoring effort to improve modularity and type safety. These modifications are generally positive and should enhance the maintainability of the codebase.

Key points:

  1. The shift to using "@formbricks/ui/components" suggests an improved component library structure.
  2. The change from TSurvey to TSurveyListItem indicates a refinement in the data structure being used.
  3. The core functionality of the component remains intact, which is good for maintaining consistency.

Recommendations:

  1. Verify that all instances of TSurvey that should now be TSurveyListItem have been updated throughout the codebase.
  2. Conduct thorough testing of the CopySurveyForm component to ensure it functions correctly with the new imports and types.
  3. Consider updating or adding unit tests to cover the component's functionality, especially focusing on areas affected by the type changes.
  4. Review the larger refactoring effort to ensure consistency across all affected components and files.

Please confirm that these changes are part of a coordinated refactoring effort and that all necessary updates have been made across the codebase to support these changes.


21-21: Approve type change with verification request.

The change from TSurvey to TSurveyListItem for the survey prop is noted. This suggests a shift in the expected structure of the survey data being passed to the component.

Please verify that this type change is consistent with how the component is used throughout the application. Run the following script to check for any potential issues:

Ensure that all occurrences of TSurvey that should now be TSurveyListItem have been updated accordingly.

✅ Verification successful

Approve type change with .

The change from TSurvey to TSurveyListItem for the survey prop in CopySurveyForm.tsx has been verified and does not impact other parts of the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential issues with TSurveyListItem usage

# Test: Search for TSurveyListItem usage
echo "Searching for TSurveyListItem usage:"
rg --type typescript "TSurveyListItem"

# Test: Search for potential mismatches between TSurvey and TSurveyListItem
echo "Searching for potential mismatches:"
rg --type typescript "TSurvey(?!ListItem)"

Length of output: 512


Script:

#!/bin/bash
# Description: Check for potential issues with TSurveyListItem usage

# Test: Search for TSurveyListItem usage
echo "Searching for TSurveyListItem usage:"
rg "TSurveyListItem" --glob "*.{ts,tsx}"

# Test: Search for potential mismatches between TSurvey and TSurveyListItem using PCRE2
echo "Searching for potential mismatches:"
rg --pcre2 "TSurvey(?!ListItem)" --glob "*.{ts,tsx}"

Length of output: 217186

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCard.tsx (1)

123-123: Verify the necessity of calling .toString() on date fields

The .toString() calls on survey.createdAt and survey.updatedAt may be unnecessary if these fields are already strings or acceptable formats for convertDateString and timeSince functions. Removing .toString() could simplify the code.

Also applies to: 126-126

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyList.tsx (3)

125-127: Ensure handleDuplicateSurvey works with TSurveyListItem

The handleDuplicateSurvey function now accepts a parameter of type TSurveyListItem. Confirm that the duplication logic is appropriate for this type and that all necessary properties are correctly handled when updating the surveys state.


149-163: Verify that SurveyCard accepts updated survey type

Ensure that the SurveyCard component is updated to accept survey of type TSurveyListItem. This includes verifying that all required properties used within SurveyCard are present in TSurveyListItem.


44-44: Verify compatibility of TSurveyListItem with existing usage

The state surveys has been updated to use TSurveyListItem[] instead of TSurvey[]. Ensure that all usages of surveys within this component and any child components, such as SurveyCard, are compatible with TSurveyListItem. This includes verifying that all required properties are accessible and that no type errors will occur.

To verify, you can search for all references to surveys and check for type compatibility:

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilters.tsx (1)

8-14: Good use of shared UI components for modularity

Importing Button, DropdownMenu, and SearchBar from @formbricks/ui/components improves code modularity and promotes reusability across the application.

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyDropdownMenu.tsx (7)

3-7: Imports updated to reflect new action functions

The imports have been correctly updated to include copySurveyToOtherEnvironmentAction, deleteSurveyAction, and getSurveyListItemAction, replacing getSurveyAction. This ensures the component uses the latest action functions.


8-8: Updated type import to TSurveyListItem

The import statement now includes TSurveyListItem, aligning with the updated data structure for surveys. This change ensures type consistency within the component.


29-29: Prop type for survey updated to TSurveyListItem

The survey prop in SurveyDropDownMenuProps interface has been updated from TSurvey to TSurveyListItem. Verify that all usages of survey within the component are compatible with TSurveyListItem and that no required properties are missing.


35-35: Updated duplicateSurvey function signature

The duplicateSurvey function now accepts a TSurveyListItem instead of TSurvey. Ensure that this change is reflected wherever duplicateSurvey is implemented and that it functions correctly with the new type.


56-60: Simplified handleDeleteSurvey function parameter

The handleDeleteSurvey function now accepts a surveyId string directly instead of a survey object. This simplifies the function and makes it more focused on the unique identifier needed for deletion.


Line range hint 81-85: Updated duplication logic to use getSurveyListItemAction

The duplication process now uses getSurveyListItemAction, ensuring that the duplicated survey data aligns with the expected TSurveyListItem type. This update maintains consistency in how survey data is handled post-duplication.


213-213: Correct onDelete handler in DeleteDialog

The onDelete prop now correctly passes survey.id to handleDeleteSurvey, matching the updated function signature that expects a surveyId.

@@ -1,5 +1,5 @@
import { TSortOption, TSurveyFilters } from "@formbricks/types/surveys/types";
import { DropdownMenuItem } from "../../DropdownMenu";
import { DropdownMenuItem } from "@formbricks/ui/components/DropdownMenu";
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Inconsistent import paths for DropdownMenuItem detected.

The SortOption.tsx component uses an absolute import:

import { DropdownMenuItem } from "@formbricks/ui/components/DropdownMenu";

However, other components in the codebase still use relative imports for DropdownMenu components. To maintain consistency, it's recommended to standardize the import paths across the codebase.

🔗 Analysis chain

LGTM! Consider verifying import consistency across the codebase.

The change from a relative import to an absolute import for DropdownMenuItem is a good practice. It can improve maintainability and reduce issues related to relative path changes.

To ensure consistency across the codebase, please run the following script:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for inconsistent imports of DropdownMenuItem

# Test 1: Search for relative imports of DropdownMenuItem
echo "Checking for relative imports of DropdownMenuItem:"
rg --type typescript "from ['\"]\.\..*DropdownMenu['\"]"

# Test 2: Search for absolute imports of DropdownMenuItem
echo "Checking for absolute imports of DropdownMenuItem:"
rg --type typescript "from ['\"]@formbricks/ui/components/DropdownMenu['\"]"

# The results will help identify if there are any inconsistent imports across the codebase.

Length of output: 437


Script:

#!/bin/bash
# Description: Check for inconsistent imports of DropdownMenuItem using glob patterns

# Test 1: Search for relative imports of DropdownMenuItem
echo "Checking for relative imports of DropdownMenuItem:"
rg "from ['\"]\.\./.*DropdownMenu['\"]" --glob "*.ts" --glob "*.tsx"

# Test 2: Search for absolute imports of DropdownMenuItem
echo "Checking for absolute imports of DropdownMenuItem:"
rg "from ['\"]@formbricks/ui/components/DropdownMenu['\"]" --glob "*.ts" --glob "*.tsx"

# The results will help identify if there are any inconsistent imports across the codebase.

Length of output: 3518

Comment on lines +34 to +40
const surveyStatusLabel = useMemo(() => {
if (survey.status === "inProgress") return "In Progress";
else if (survey.status === "scheduled") return "Scheduled";
else if (survey.status === "completed") return "Completed";
else if (survey.status === "draft") return "Draft";
else if (survey.status === "paused") return "Paused";
}, [survey]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add a default case to handle unexpected survey statuses

In the surveyStatusLabel computation, if survey.status does not match any of the specified statuses, surveyStatusLabel will be undefined. Consider adding a default case to handle unexpected or new statuses to prevent potential issues.

Apply this diff to add a default case:

  const surveyStatusLabel = useMemo(() => {
    if (survey.status === "inProgress") return "In Progress";
    else if (survey.status === "scheduled") return "Scheduled";
    else if (survey.status === "completed") return "Completed";
    else if (survey.status === "draft") return "Draft";
    else if (survey.status === "paused") return "Paused";
+   else return "Unknown Status";
  }, [survey]);
📝 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
const surveyStatusLabel = useMemo(() => {
if (survey.status === "inProgress") return "In Progress";
else if (survey.status === "scheduled") return "Scheduled";
else if (survey.status === "completed") return "Completed";
else if (survey.status === "draft") return "Draft";
else if (survey.status === "paused") return "Paused";
}, [survey]);
const surveyStatusLabel = useMemo(() => {
if (survey.status === "inProgress") return "In Progress";
else if (survey.status === "scheduled") return "Scheduled";
else if (survey.status === "completed") return "Completed";
else if (survey.status === "draft") return "Draft";
else if (survey.status === "paused") return "Paused";
else return "Unknown Status";
}, [survey]);

if (survey.singleUse?.enabled) {
const generateSingleUseIdResponse = await generateSingleUseIdAction({
surveyId: survey.id,
isEncrypted: survey.singleUse?.isEncrypted ? true : false,
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 7, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify the boolean expression in isEncrypted parameter

The ternary operator survey.singleUse?.isEncrypted ? true : false is unnecessary. You can simplify the expression by using the double negation operator or the nullish coalescing operator.

Apply this diff to simplify the expression:

- isEncrypted: survey.singleUse?.isEncrypted ? true : false,
+ isEncrypted: !!survey.singleUse?.isEncrypted,

Alternatively:

- isEncrypted: survey.singleUse?.isEncrypted ? true : false,
+ isEncrypted: survey.singleUse?.isEncrypted ?? 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.

Suggested change
isEncrypted: survey.singleUse?.isEncrypted ? true : false,
isEncrypted: !!survey.singleUse?.isEncrypted,
🧰 Tools
🪛 Biome

[error] 49-49: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +131 to +145
<div className="col-span-1 place-self-end">
<SurveyDropDownMenu
survey={survey}
key={`surveys-${survey.id}`}
environmentId={environment.id}
environment={environment}
otherEnvironment={otherEnvironment!}
webAppUrl={WEBAPP_URL}
singleUseId={singleUseId}
isSurveyCreationDeletionDisabled={isSurveyCreationDeletionDisabled}
duplicateSurvey={duplicateSurvey}
deleteSurvey={deleteSurvey}
/>
</div>
</Link>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid nesting interactive elements inside a Link component

Wrapping the SurveyDropDownMenu, which contains interactive elements like buttons, inside a Link component can lead to accessibility issues and unexpected behavior. It's recommended to move SurveyDropDownMenu outside of the Link to prevent nesting interactive elements.

Apply this diff to adjust the component structure:

<Link
  href={linkHref}
  key={survey.id}
  className="relative grid w-full grid-cols-8 place-items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-all ease-in-out hover:scale-[101%]">
  {/* ... other content ... */}
-  <div className="col-span-1 place-self-end">
-    <SurveyDropDownMenu
-      survey={survey}
-      /* other props */
-    />
-  </div>
</Link>
+
+<div className="col-span-1 place-self-end">
+  <SurveyDropDownMenu
+    survey={survey}
+    /* other props */
+  />
+</div>

Ensure to adjust the layout if necessary to maintain the desired appearance.

📝 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
<div className="col-span-1 place-self-end">
<SurveyDropDownMenu
survey={survey}
key={`surveys-${survey.id}`}
environmentId={environment.id}
environment={environment}
otherEnvironment={otherEnvironment!}
webAppUrl={WEBAPP_URL}
singleUseId={singleUseId}
isSurveyCreationDeletionDisabled={isSurveyCreationDeletionDisabled}
duplicateSurvey={duplicateSurvey}
deleteSurvey={deleteSurvey}
/>
</div>
</Link>
<Link
href={linkHref}
key={survey.id}
className="relative grid w-full grid-cols-8 place-items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-all ease-in-out hover:scale-[101%]">
{/* ... other content ... */}
</Link>
<div className="col-span-1 place-self-end">
<SurveyDropDownMenu
survey={survey}
key={`surveys-${survey.id}`}
environmentId={environment.id}
environment={environment}
otherEnvironment={otherEnvironment!}
webAppUrl={WEBAPP_URL}
singleUseId={singleUseId}
isSurveyCreationDeletionDisabled={isSurveyCreationDeletionDisabled}
duplicateSurvey={duplicateSurvey}
deleteSurvey={deleteSurvey}
/>
</div>

Comment on lines +140 to +147
<div className="mt-6 grid w-full grid-cols-8 place-items-center gap-3 px-6 text-sm text-slate-800">
<div className="col-span-1 place-self-start">Name</div>
<div className="col-span-1">Status</div>
<div className="col-span-1">Responses</div>
<div className="col-span-1">Type</div>
<div className="col-span-1">Created at</div>
<div className="col-span-1">Updated at</div>
<div className="col-span-1">Created by</div>
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

Adjust grid columns to match header items

The grid is defined with grid-cols-8, but there are only 7 header items. This mismatch may cause layout issues. Update the grid to have the correct number of columns.

Apply this diff to adjust the grid columns:

 <div className="mt-6 grid w-full grid-cols-8 place-items-center gap-3 px-6 text-sm text-slate-800">
-  <div className="col-span-1 place-self-start">Name</div>
+  <div className="col-span-1 col-start-1 place-self-start">Name</div>
   <div className="col-span-1">Status</div>
   <div className="col-span-1">Responses</div>
   <div className="col-span-1">Type</div>
   <div className="col-span-1">Created at</div>
   <div className="col-span-1">Updated at</div>
   <div className="col-span-1">Created by</div>
+  <!-- If an additional column is needed, it can be added here -->
 </div>

Alternatively, update the grid definition:

-<div className="mt-6 grid w-full grid-cols-8 place-items-center gap-3 px-6 text-sm text-slate-800">
+<div className="mt-6 grid w-full grid-cols-7 place-items-center gap-3 px-6 text-sm text-slate-800">

Committable suggestion was skipped due to low confidence.

): Promise<TSurveyListItem[]> =>
cache(
async () => {
validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]);
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

Ensure 'limit' and 'offset' are non-negative integers

The current validation checks if limit and offset are optional numbers but does not enforce that they are non-negative integers. Negative values for limit or offset could cause issues with pagination and database queries.

Apply this diff to enhance validation:

 validateInputs(
   [environmentId, ZId],
-  [limit, ZOptionalNumber],
-  [offset, ZOptionalNumber]
+  [limit, ZOptionalNumber.min(0).int()],
+  [offset, ZOptionalNumber.min(0).int()]
 );
📝 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
validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]);
validateInputs([environmentId, ZId], [limit, ZOptionalNumber.min(0).int()], [offset, ZOptionalNumber.min(0).int()]);

return null;
}

return { ...surveyPrisma, responseCount: surveyPrisma?._count.responses };
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

Unnecessary optional chaining operator

Since surveyPrisma is confirmed to be non-null in the preceding check (if (!surveyPrisma) { return null; }), the optional chaining operator ? in surveyPrisma?._count.responses is unnecessary.

Simplify the code:

 return { ...surveyPrisma, responseCount: surveyPrisma?._count.responses };
+return { ...surveyPrisma, responseCount: surveyPrisma._count.responses };
📝 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
return { ...surveyPrisma, responseCount: surveyPrisma?._count.responses };
return { ...surveyPrisma, responseCount: surveyPrisma._count.responses };

): Promise<TSurveyListItem[]> =>
cache(
async () => {
validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]);
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

Ensure 'limit' and 'offset' are non-negative integers

Similar to the earlier comment, ensure that limit and offset are validated to be non-negative integers to prevent issues in pagination logic.

Apply this diff:

 validateInputs(
   [environmentId, ZId],
-  [limit, ZOptionalNumber],
-  [offset, ZOptionalNumber]
+  [limit, ZOptionalNumber.min(0).int()],
+  [offset, ZOptionalNumber.min(0).int()]
 );
📝 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
validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]);
validateInputs(
[environmentId, ZId],
[limit, ZOptionalNumber.min(0).int()],
[offset, ZOptionalNumber.min(0).int()]
);

Comment on lines +134 to +136
if (offset !== undefined && limit && inProgressSurveys.length < limit) {
const remainingLimit = limit - inProgressSurveys.length;
const newOffset = Math.max(0, offset - inProgressSurveyCount);
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

Ensure correct handling of 'limit' and 'offset' in pagination logic

In the condition if (offset !== undefined && limit && inProgressSurveys.length < limit), using limit directly may cause issues if limit is 0 (since 0 is falsy in JavaScript). This could inadvertently skip the block when limit is zero.

Modify the condition to explicitly check for numeric values:

- if (offset !== undefined && limit && inProgressSurveys.length < limit) {
+ if (typeof offset === "number" && typeof limit === "number" && inProgressSurveys.length < limit) {

Alternatively, consider setting default values for limit and offset earlier in the function to ensure they are always numbers.

📝 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
if (offset !== undefined && limit && inProgressSurveys.length < limit) {
const remainingLimit = limit - inProgressSurveys.length;
const newOffset = Math.max(0, offset - inProgressSurveyCount);
if (typeof offset === "number" && typeof limit === "number" && inProgressSurveys.length < limit) {
const remainingLimit = limit - inProgressSurveys.length;
const newOffset = Math.max(0, offset - inProgressSurveyCount);

Comment on lines +107 to +109
offset && offset > inProgressSurveyCount
? []
: await prisma.survey.findMany({
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

Clarify condition when offset exceeds in-progress survey count

The condition offset && offset > inProgressSurveyCount may not handle cases where offset is zero or undefined properly. This can lead to unexpected behavior in pagination.

Consider revising the condition for clarity and correctness:

 const inProgressSurveys =
-  offset && offset > inProgressSurveyCount
+  (offset ?? 0) >= inProgressSurveyCount
     ? []
     : await prisma.survey.findMany({ /* ... */ });

This ensures that when offset is undefined or 0, it defaults to 0 for the comparison.

📝 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
offset && offset > inProgressSurveyCount
? []
: await prisma.survey.findMany({
(offset ?? 0) >= inProgressSurveyCount
? []
: await prisma.survey.findMany({

Copy link
Member

@mattinannt mattinannt left a comment

Choose a reason for hiding this comment

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

@Dhruwang thanks for the PR 🚀 Already looks pretty good but going through the code I saw a few things that needs change :-)

if (survey.singleUse?.enabled) {
const generateSingleUseIdResponse = await generateSingleUseIdAction({
surveyId: survey.id,
isEncrypted: survey.singleUse?.isEncrypted ? true : false,
Copy link
Member

Choose a reason for hiding this comment

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


const [singleUseId, setSingleUseId] = useState<string | undefined>();

useEffect(() => {
Copy link
Member

Choose a reason for hiding this comment

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

can we also generate this singleUseId in the dropdown itself and make this whole component a server component?

Copy link
Member Author

Choose a reason for hiding this comment

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

SurveyCard is called by SurveyList which is a client component so, this would automatically make this a client component 😊

await deleteSurveyAction({ surveyId: survey.id });
deleteSurvey(survey.id);
await deleteSurveyAction({ surveyId });
deleteSurvey(surveyId);
Copy link
Member

Choose a reason for hiding this comment

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

What is this doing? Again could maybe a router.refresh be a better choice here instead of prop drilling?

Copy link
Member Author

Choose a reason for hiding this comment

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

Same as above

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: 6

🧹 Outside diff range and nitpick comments (6)
apps/web/app/(app)/environments/[environmentId]/surveys/types/surveys.ts (1)

4-24: LGTM: Well-structured survey schema with a minor suggestion.

The ZSurvey schema is comprehensive and well-defined. It covers all essential properties of a survey object.

Consider creating a separate ZSurveyType enum for the 'type' field to make future updates easier. This would allow you to replace the inline enum with ZSurveyType and manage survey types in a centralized location.

export const ZSurveyType = z.enum(["link", "app", "website", "web"]);

// Then in ZSurvey
type: ZSurveyType,

This approach will simplify the process of removing the "web" type in the future.

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCard.tsx (1)

25-35: Consider removing redundant variable

The isSurveyCreationDeletionDisabled variable is redundant as it's simply assigning the value of isViewer. You can use isViewer directly where needed, improving code clarity.

Apply this diff to remove the redundant variable:

- const isSurveyCreationDeletionDisabled = isViewer;

Make sure to replace any usage of isSurveyCreationDeletionDisabled with isViewer in the rest of the component.

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilters.tsx (1)

Line range hint 1-190: LGTM: Overall component structure and functionality improved

The changes to the SurveyFilters component maintain its core functionality while improving code organization and consistency. The use of shared UI components and the removal of orientation-related code have resulted in a cleaner, more focused component.

Consider adding or updating unit tests to cover the modified functionality, especially focusing on the behavior of filters without the orientation feature. This will help ensure the changes haven't introduced any regressions.

Would you like assistance in generating test cases for the updated SurveyFilters component?

apps/web/app/(app)/environments/[environmentId]/surveys/lib/surveys.ts (1)

173-206: Approve getSurvey implementation with a minor suggestion

The getSurvey function is well-implemented with appropriate caching, input validation, and error handling.

There's an unnecessary optional chaining operator in the return statement (line 199):

return { ...surveyPrisma, responseCount: surveyPrisma?._count.responses };

Since surveyPrisma is guaranteed to be non-null at this point (due to the check on line 195), the optional chaining is not needed. You can simplify it to:

return { ...surveyPrisma, responseCount: surveyPrisma._count.responses };

This small change improves code clarity without affecting functionality.

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyDropdownMenu.tsx (2)

3-7: LGTM! Consider grouping related imports.

The changes to the imports reflect the updated functionality for copying and deleting surveys. This is a good improvement in terms of modularity and separation of concerns.

Consider grouping related imports together for better readability. For example:

import {
  copySurveyToOtherEnvironmentAction,
  deleteSurveyAction,
  getSurveyAction
} from "@/app/(app)/environments/[environmentId]/surveys/actions";

56-60: LGTM! Consider improving error handling.

The changes to handleDeleteSurvey improve the function's simplicity and modularity by working directly with the surveyId and using deleteSurveyAction.

Consider improving the error handling by using the getFormattedErrorMessage function for consistency:

} catch (error) {
  const errorMessage = getFormattedErrorMessage(error);
  toast.error(errorMessage || "An error occurred while deleting survey");
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between db064e2 and 3a978b3.

📒 Files selected for processing (14)
  • apps/web/app/(app)/environments/[environmentId]/surveys/actions.ts (2 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/CopySurveyForm.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/CopySurveyModal.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SortOption.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCard.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCopyOptions.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyDropdownMenu.tsx (4 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilterDropdown.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilters.tsx (2 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyList.tsx (2 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/lib/surveys.ts (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/types/surveys.ts (1 hunks)
  • packages/js/src/index.ts (0 hunks)
  • packages/types/surveys/types.ts (0 hunks)
💤 Files with no reviewable changes (2)
  • packages/js/src/index.ts
  • packages/types/surveys/types.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/web/app/(app)/environments/[environmentId]/surveys/actions.ts
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/CopySurveyForm.tsx
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SortOption.tsx
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCopyOptions.tsx
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilterDropdown.tsx
🧰 Additional context used
🪛 Biome
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCard.tsx

[error] 51-51: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

🔇 Additional comments (22)
apps/web/app/(app)/environments/[environmentId]/surveys/types/surveys.ts (4)

1-2: LGTM: Imports are correct and necessary.

The imports for Zod and the pre-defined survey status type are appropriate for the file's purpose.


26-26: LGTM: Correct type alias definition.

The TSurvey type alias is correctly defined using Zod's infer utility, which is a best practice when working with Zod schemas.


28-35: LGTM: Well-defined schema for survey copy form validation.

The ZSurveyCopyFormValidation schema is correctly structured to validate survey copy operations across multiple products and environments. It provides a clear definition of the expected data structure for this operation.


37-37: LGTM: Correct type alias for survey copy form data.

The TSurveyCopyFormData type alias is properly defined using Zod's infer utility, consistent with the approach used for TSurvey.

apps/web/app/(app)/environments/[environmentId]/surveys/components/CopySurveyModal.tsx (1)

5-5: LGTM: Import statement updated for better modularity.

The change from a relative import to an absolute import from the @formbricks/ui package for the Modal component is a good practice. It enhances modularity and makes the code more maintainable.

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCard.tsx (3)

1-24: LGTM: Imports and component interface are well-structured

The "use client" directive, import statements, and SurveyCardProps interface are correctly implemented and provide a solid foundation for the component.


1-141: Overall assessment: Well-implemented component with room for minor improvements

The SurveyCard component is well-structured and effectively uses React hooks for state management and memoization. It provides a comprehensive display of survey information and interactions.

Key strengths:

  1. Proper use of "use client" directive for client-side rendering.
  2. Well-defined props interface.
  3. Effective use of useMemo for computed values.
  4. Good error handling in the singleUseId fetching logic.

Areas for improvement:

  1. Moving the SurveyTypeIndicator to a separate file for better organization.
  2. Restructuring the layout to place the SurveyDropDownMenu outside the Link component for improved accessibility.
  3. Simplifying some expressions and removing redundant variables.
  4. Adding a default case in the survey status label computation.

Implementing the suggested changes will further enhance the component's readability, maintainability, and accessibility.

🧰 Tools
🪛 Biome

[error] 51-51: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


36-42: ⚠️ Potential issue

Add a default case to handle unexpected survey statuses

The surveyStatusLabel computation doesn't handle unexpected or new statuses, which could lead to undefined labels. Consider adding a default case to ensure all possible statuses are covered.

Apply this diff to add a default case:

  const surveyStatusLabel = useMemo(() => {
    if (survey.status === "inProgress") return "In Progress";
    else if (survey.status === "scheduled") return "Scheduled";
    else if (survey.status === "completed") return "Completed";
    else if (survey.status === "draft") return "Draft";
    else if (survey.status === "paused") return "Paused";
+   else return "Unknown Status";
  }, [survey]);

Likely invalid or redundant comment.

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyList.tsx (6)

Line range hint 88-126: Approve updated data fetching logic and suggest pagination testing

The new data fetching logic using getSurveysAction and incorporating filters is well-implemented. The pagination mechanism has been successfully adapted to work with the new filtering system.

To ensure the pagination works correctly with various filter combinations, consider adding the following test cases:

  1. Fetch surveys with no filters applied
  2. Fetch surveys with a single filter (e.g., by name)
  3. Fetch surveys with multiple filters
  4. Test pagination by loading more surveys with different filter combinations

You may want to create a separate test file or add these scenarios to your existing test suite.


Line range hint 128-131: Verify survey duplication logic with TSurveyFilters

The handleDuplicateSurvey function signature has been updated to use TSurveyFilters instead of TSurvey. While this is consistent with earlier type changes, it's important to ensure that the duplication logic itself has been updated to handle the new type correctly.

Please review the implementation of the survey duplication functionality (which might be in a separate file) to confirm that it properly handles the TSurveyFilters type. If necessary, update the duplication logic to work with the new type.


Line range hint 1-190: Summary of SurveyList.tsx changes

Overall, the changes to the SurveyList component enhance its functionality by introducing better filtering capabilities and a more comprehensive survey display. Here are the key points to address:

  1. Verify type consistency between TSurvey and TSurveyFilters throughout the component.
  2. Run a thorough type-check to ensure no type-related errors were introduced.
  3. Consider adding test cases for pagination with various filter combinations.
  4. Fix the grid column mismatch in the survey card layout.
  5. Review and update the survey duplication logic to ensure it works correctly with the new TSurveyFilters type.

After addressing these points, the component should be well-structured and fully functional with its new features.


139-163: ⚠️ Potential issue

Fix grid column mismatch

The grid layout for survey cards has been updated to include more information, which is an improvement. However, there's a mismatch between the number of grid columns and header items.

To fix this, apply the following change:

-<div className="mt-6 grid w-full grid-cols-8 place-items-center gap-3 px-6 text-sm text-slate-800">
+<div className="mt-6 grid w-full grid-cols-7 place-items-center gap-3 px-6 text-sm text-slate-800">

This will align the grid columns with the number of header items, ensuring proper layout.


Line range hint 39-86: Approve filter-related changes and suggest type verification

The addition of filter-related state and effects enhances the component's functionality. The change in the surveys state type from TSurvey[] to TSurveyFilters[] is noted.

To ensure type consistency throughout the component, run the following type-check:

#!/bin/bash
# Description: Perform a type-check on the SurveyList component

# Test: Run TypeScript compiler in noEmit mode
echo "Running TypeScript type-check:"
npx tsc --noEmit apps/web/app/\(app\)/environments/\[environmentId\]/surveys/components/SurveyList.tsx

3-15: Verify type consistency between TSurvey and TSurveyFilters

The import of both TSurvey and TSurveyFilters suggests a potential type inconsistency. Ensure that these types are used correctly throughout the component, especially in state declarations and function parameters.

To verify the type usage, run the following script:

✅ Verification successful

Types TSurvey and TSurveyFilters are used consistently

The imports and usage of both TSurvey and TSurveyFilters in SurveyList.tsx are consistent. Each type is utilized appropriately in state declarations and function parameters without any overlap or inconsistency.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the usage of TSurvey and TSurveyFilters types

# Test: Search for TSurvey and TSurveyFilters usage
echo "Searching for TSurvey usage:"
rg --type typescript 'TSurvey(?!Filters)' apps/web/app/\(app\)/environments/\[environmentId\]/surveys/
echo "\nSearching for TSurveyFilters usage:"
rg --type typescript 'TSurveyFilters' apps/web/app/\(app\)/environments/\[environmentId\]/surveys/

Length of output: 550


Script:

#!/bin/bash
# Description: Check the usage of TSurvey and TSurveyFilters types without specifying file type

# Searching for TSurvey usage excluding TSurveyFilters
echo "Searching for TSurvey usage (excluding TSurveyFilters):"
rg 'TSurvey(?!Filters)' apps/web/app/\(app\)/environments/\[environmentId\]/surveys/ -A2

# Searching for TSurveyFilters usage
echo "\nSearching for TSurveyFilters usage:"
rg 'TSurveyFilters' apps/web/app/\(app\)/environments/\[environmentId\]/surveys/ -A2

Length of output: 4012


Script:

#!/bin/bash
# Description: Check the usage of TSurvey and TSurveyFilters types without using unsupported regex

# Searching for TSurvey usage
echo "Searching for TSurvey usage:"
rg 'TSurvey\b' apps/web/app/\(app\)/environments/\[environmentId\]/surveys/ -A2

# Searching for TSurveyFilters usage
echo "\nSearching for TSurveyFilters usage:"
rg 'TSurveyFilters\b' apps/web/app/\(app\)/environments/\[environmentId\]/surveys/ -A2

Length of output: 43234


Script:

#!/bin/bash
# Description: Extract usage of TSurvey and TSurveyFilters in SurveyList.tsx

# Extract lines with TSurvey
echo "Extracting TSurvey usages in SurveyList.tsx:"
rg 'TSurvey\b' apps/web/app/\(app\)/environments/\[environmentId\]/surveys/components/SurveyList.tsx -A2

# Extract lines with TSurveyFilters
echo "\nExtracting TSurveyFilters usages in SurveyList.tsx:"
rg 'TSurveyFilters\b' apps/web/app/\(app\)/environments/\[environmentId\]/surveys/components/SurveyList.tsx -A2

Length of output: 1390

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilters.tsx (4)

1-16: LGTM: Import statements have been improved

The changes to the import statements look good. The shift to using shared UI components from @formbricks/ui/components enhances maintainability and consistency across the project. The addition of the "use client" directive is correct for a Client Component in Next.js.

Note: The previous suggestion to move SortOption and initialFilters to the shared UI library has been partially addressed. Consider if SortOption could also be moved to the shared library in the future for further consistency.


70-73: LGTM: typeOptions moved inside component

Moving the typeOptions array inside the component is acceptable. This change localizes the options to where they are used, which is fine if they're not needed elsewhere in the application.


Line range hint 1-190: Verify impact of removing orientation functionality

The removal of all orientation-related state management and UI elements significantly simplifies the component, which is good for maintainability. This change is consistent with the updates to the SurveyFilterProps interface and aligns with the PR objectives.

Please verify that the removal of orientation functionality doesn't negatively impact any user-facing features or other components that might have depended on this functionality. Consider running the following script to check for any remaining references to orientation in related files:

#!/bin/bash
# Description: Check for any remaining references to orientation in survey-related files

# Test: Search for orientation references in survey-related files
rg --type typescript --type tsx 'orientation' apps/web/app/\(app\)/environments/\[environmentId\]/surveys/

Line range hint 19-23: LGTM: SurveyFilterProps interface simplified

The removal of orientation and setOrientation props from the SurveyFilterProps interface simplifies the component's API, which is good. This change is consistent with the removal of orientation-related functionality.

Please verify that all components using SurveyFilters have been updated to remove the orientation and setOrientation props. Run the following script to check for any remaining usage:

✅ Verification successful

Please run the following script to verify any remaining usage of orientation props in SurveyFilters:


Please run the following script to verify any remaining usage of orientation props in SurveyFilters:


Verified: orientation props successfully removed from SurveyFilters

The orientation and setOrientation props have been fully removed from the SurveyFilters interface, and no remaining usages were found in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining usage of orientation props in SurveyFilters

# Test: Search for SurveyFilters usage with orientation props
rg --type typescript --type tsx 'SurveyFilters.*orientation'

Length of output: 139


Script:

#!/bin/bash
# Description: Re-check for any remaining usage of orientation props in SurveyFilters with corrected file types

# Test: Search for SurveyFilters usage with orientation props using correct file type identifiers
rg --type ts --type tsx 'SurveyFilters.*orientation'

Length of output: 82


Script:

#!/bin/bash
# Description: Search for any remaining usage of orientation props in SurveyFilters within .ts and .tsx files

# Test: Search for SurveyFilters usage with orientation props using glob patterns for file extensions
rg --glob "*.ts" --glob "*.tsx" 'SurveyFilters\..*orientation'

Length of output: 64

apps/web/app/(app)/environments/[environmentId]/surveys/lib/surveys.ts (1)

1-34: LGTM: Imports and surveySelect object are well-defined

The imports cover all necessary modules and types for the file's functionality. The surveySelect object is comprehensive, including all relevant fields for survey data retrieval, which will ensure consistent data selection across different query operations.

apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyDropdownMenu.tsx (3)

17-17: LGTM! Improved import syntax.

The change to use named import syntax for DeleteDialog improves code consistency and clarity.


18-24: LGTM! Consistent import syntax improvement.

The change to use named import syntax for DropdownMenu components is consistent with the previous import changes and improves code readability.


213-213: LGTM! Consistent with function signature change.

The update to pass survey.id directly to handleDeleteSurvey is consistent with the earlier changes and improves code clarity.

Comment on lines +73 to +88
const SurveyTypeIndicator = ({ type }: { type: string }) => (
<div className="flex items-center space-x-2 text-sm text-slate-600">
{type === "app" && (
<>
<Code className="h-4 w-4" />
<span>App</span>
</>
)}
{type === "link" && (
<>
<Link2Icon className="h-4 w-4" />
<span> Link</span>
</>
)}
</div>
);
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 9, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider moving SurveyTypeIndicator to a separate file

The SurveyTypeIndicator component is currently nested within the SurveyCard component. For better organization and potential reuse, consider moving it to a separate file.

  1. Create a new file, e.g., SurveyTypeIndicator.tsx.
  2. Move the SurveyTypeIndicator component to this new file.
  3. Export the component from the new file.
  4. Import it in the SurveyCard.tsx file.

This separation will improve the overall structure of your code and make it easier to maintain and potentially reuse the SurveyTypeIndicator component.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +49 to +52
const generateSingleUseIdResponse = await generateSingleUseIdAction({
surveyId: survey.id,
isEncrypted: survey.singleUse?.isEncrypted ? true : false,
});
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

Simplify the isEncrypted parameter

The ternary operator for isEncrypted is unnecessary. You can simplify this expression using the double negation operator.

Apply this diff to simplify the expression:

  const generateSingleUseIdResponse = await generateSingleUseIdAction({
    surveyId: survey.id,
-   isEncrypted: survey.singleUse?.isEncrypted ? true : false,
+   isEncrypted: !!survey.singleUse?.isEncrypted,
  });
📝 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
const generateSingleUseIdResponse = await generateSingleUseIdAction({
surveyId: survey.id,
isEncrypted: survey.singleUse?.isEncrypted ? true : false,
});
const generateSingleUseIdResponse = await generateSingleUseIdAction({
surveyId: survey.id,
isEncrypted: !!survey.singleUse?.isEncrypted,
});
🧰 Tools
🪛 Biome

[error] 51-51: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

Comment on lines +44 to +65
const [singleUseId, setSingleUseId] = useState<string | undefined>();

useEffect(() => {
const fetchSingleUseId = async () => {
if (survey.singleUse?.enabled) {
const generateSingleUseIdResponse = await generateSingleUseIdAction({
surveyId: survey.id,
isEncrypted: survey.singleUse?.isEncrypted ? true : false,
});
if (generateSingleUseIdResponse?.data) {
setSingleUseId(generateSingleUseIdResponse.data);
} else {
const errorMessage = getFormattedErrorMessage(generateSingleUseIdResponse);
toast.error(errorMessage);
}
} else {
setSingleUseId(undefined);
}
};

fetchSingleUseId();
}, [survey]);
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 moving fetchSingleUseId function outside useEffect

To improve readability and potentially reuse the function, consider moving fetchSingleUseId outside the useEffect hook. This separation of concerns can make the code more maintainable.

Apply this diff to move the function:

+ const fetchSingleUseId = async () => {
+   if (survey.singleUse?.enabled) {
+     const generateSingleUseIdResponse = await generateSingleUseIdAction({
+       surveyId: survey.id,
+       isEncrypted: survey.singleUse?.isEncrypted ? true : false,
+     });
+     if (generateSingleUseIdResponse?.data) {
+       setSingleUseId(generateSingleUseIdResponse.data);
+     } else {
+       const errorMessage = getFormattedErrorMessage(generateSingleUseIdResponse);
+       toast.error(errorMessage);
+     }
+   } else {
+     setSingleUseId(undefined);
+   }
+ };
+
  useEffect(() => {
-   const fetchSingleUseId = async () => {
-     // ... (move the entire function here)
-   };

    fetchSingleUseId();
  }, [survey]);
📝 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
const [singleUseId, setSingleUseId] = useState<string | undefined>();
useEffect(() => {
const fetchSingleUseId = async () => {
if (survey.singleUse?.enabled) {
const generateSingleUseIdResponse = await generateSingleUseIdAction({
surveyId: survey.id,
isEncrypted: survey.singleUse?.isEncrypted ? true : false,
});
if (generateSingleUseIdResponse?.data) {
setSingleUseId(generateSingleUseIdResponse.data);
} else {
const errorMessage = getFormattedErrorMessage(generateSingleUseIdResponse);
toast.error(errorMessage);
}
} else {
setSingleUseId(undefined);
}
};
fetchSingleUseId();
}, [survey]);
const [singleUseId, setSingleUseId] = useState<string | undefined>();
const fetchSingleUseId = async () => {
if (survey.singleUse?.enabled) {
const generateSingleUseIdResponse = await generateSingleUseIdAction({
surveyId: survey.id,
isEncrypted: survey.singleUse?.isEncrypted ? true : false,
});
if (generateSingleUseIdResponse?.data) {
setSingleUseId(generateSingleUseIdResponse.data);
} else {
const errorMessage = getFormattedErrorMessage(generateSingleUseIdResponse);
toast.error(errorMessage);
}
} else {
setSingleUseId(undefined);
}
};
useEffect(() => {
fetchSingleUseId();
}, [survey]);
🧰 Tools
🪛 Biome

[error] 51-51: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

Comment on lines +90 to +139
return (
<Link
href={linkHref}
key={survey.id}
className="relative grid w-full grid-cols-8 place-items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-all ease-in-out hover:scale-[101%]">
<div className="col-span-1 flex max-w-full items-center justify-self-start text-sm font-medium text-slate-900">
<div className="w-full truncate">{survey.name}</div>
</div>
<div
className={cn(
"col-span-1 flex w-fit items-center gap-2 rounded-full py-1 pl-1 pr-2 text-sm text-slate-800",
surveyStatusLabel === "Scheduled" && "bg-slate-200",
surveyStatusLabel === "In Progress" && "bg-emerald-50",
surveyStatusLabel === "Completed" && "bg-slate-200",
surveyStatusLabel === "Draft" && "bg-slate-100",
surveyStatusLabel === "Paused" && "bg-slate-100"
)}>
<SurveyStatusIndicator status={survey.status} /> {surveyStatusLabel}{" "}
</div>
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
{survey.responseCount}
</div>
<div className="col-span-1 flex justify-between">
<SurveyTypeIndicator type={survey.type} />
</div>

<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
{convertDateString(survey.createdAt.toString())}
</div>
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
{timeSince(survey.updatedAt.toString())}
</div>
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
{survey.creator ? survey.creator.name : "-"}
</div>
<div className="col-span-1 place-self-end">
<SurveyDropDownMenu
survey={survey}
key={`surveys-${survey.id}`}
environmentId={environment.id}
environment={environment}
otherEnvironment={otherEnvironment!}
webAppUrl={WEBAPP_URL}
singleUseId={singleUseId}
isSurveyCreationDeletionDisabled={isSurveyCreationDeletionDisabled}
duplicateSurvey={duplicateSurvey}
deleteSurvey={deleteSurvey}
/>
</div>
</Link>
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

Move SurveyDropDownMenu outside the Link component

The SurveyDropDownMenu is currently nested inside the Link component. This structure can lead to accessibility issues and unexpected behavior when interacting with the dropdown menu.

Consider restructuring the component to place the SurveyDropDownMenu outside the Link. Here's a suggested approach:

  1. Move the SurveyDropDownMenu outside the Link component.
  2. Adjust the layout to maintain the desired appearance.

Apply this diff as a starting point:

- <Link
-   href={linkHref}
-   key={survey.id}
-   className="relative grid w-full grid-cols-8 place-items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-all ease-in-out hover:scale-[101%]">
-   {/* ... other content ... */}
-   <div className="col-span-1 place-self-end">
-     <SurveyDropDownMenu
-       survey={survey}
-       key={`surveys-${survey.id}`}
-       environmentId={environment.id}
-       environment={environment}
-       otherEnvironment={otherEnvironment!}
-       webAppUrl={WEBAPP_URL}
-       singleUseId={singleUseId}
-       isSurveyCreationDeletionDisabled={isSurveyCreationDeletionDisabled}
-       duplicateSurvey={duplicateSurvey}
-       deleteSurvey={deleteSurvey}
-     />
-   </div>
- </Link>
+ <div className="relative flex w-full items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-all ease-in-out hover:scale-[101%]">
+   <Link
+     href={linkHref}
+     key={survey.id}
+     className="grid w-full grid-cols-7 place-items-center gap-3">
+     {/* ... other content ... */}
+   </Link>
+   <SurveyDropDownMenu
+     survey={survey}
+     key={`surveys-${survey.id}`}
+     environmentId={environment.id}
+     environment={environment}
+     otherEnvironment={otherEnvironment!}
+     webAppUrl={WEBAPP_URL}
+     singleUseId={singleUseId}
+     isSurveyCreationDeletionDisabled={isViewer}
+     duplicateSurvey={duplicateSurvey}
+     deleteSurvey={deleteSurvey}
+   />
+ </div>

Note: You may need to adjust the layout and styling to maintain the desired appearance after this change.

📝 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
return (
<Link
href={linkHref}
key={survey.id}
className="relative grid w-full grid-cols-8 place-items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-all ease-in-out hover:scale-[101%]">
<div className="col-span-1 flex max-w-full items-center justify-self-start text-sm font-medium text-slate-900">
<div className="w-full truncate">{survey.name}</div>
</div>
<div
className={cn(
"col-span-1 flex w-fit items-center gap-2 rounded-full py-1 pl-1 pr-2 text-sm text-slate-800",
surveyStatusLabel === "Scheduled" && "bg-slate-200",
surveyStatusLabel === "In Progress" && "bg-emerald-50",
surveyStatusLabel === "Completed" && "bg-slate-200",
surveyStatusLabel === "Draft" && "bg-slate-100",
surveyStatusLabel === "Paused" && "bg-slate-100"
)}>
<SurveyStatusIndicator status={survey.status} /> {surveyStatusLabel}{" "}
</div>
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
{survey.responseCount}
</div>
<div className="col-span-1 flex justify-between">
<SurveyTypeIndicator type={survey.type} />
</div>
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
{convertDateString(survey.createdAt.toString())}
</div>
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
{timeSince(survey.updatedAt.toString())}
</div>
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
{survey.creator ? survey.creator.name : "-"}
</div>
<div className="col-span-1 place-self-end">
<SurveyDropDownMenu
survey={survey}
key={`surveys-${survey.id}`}
environmentId={environment.id}
environment={environment}
otherEnvironment={otherEnvironment!}
webAppUrl={WEBAPP_URL}
singleUseId={singleUseId}
isSurveyCreationDeletionDisabled={isSurveyCreationDeletionDisabled}
duplicateSurvey={duplicateSurvey}
deleteSurvey={deleteSurvey}
/>
</div>
</Link>
return (
<div className="relative flex w-full items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-all ease-in-out hover:scale-[101%]">
<Link
href={linkHref}
key={survey.id}
className="grid w-full grid-cols-7 place-items-center gap-3">
<div className="col-span-1 flex max-w-full items-center justify-self-start text-sm font-medium text-slate-900">
<div className="w-full truncate">{survey.name}</div>
</div>
<div
className={cn(
"col-span-1 flex w-fit items-center gap-2 rounded-full py-1 pl-1 pr-2 text-sm text-slate-800",
surveyStatusLabel === "Scheduled" && "bg-slate-200",
surveyStatusLabel === "In Progress" && "bg-emerald-50",
surveyStatusLabel === "Completed" && "bg-slate-200",
surveyStatusLabel === "Draft" && "bg-slate-100",
surveyStatusLabel === "Paused" && "bg-slate-100"
)}>
<SurveyStatusIndicator status={survey.status} /> {surveyStatusLabel}{" "}
</div>
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
{survey.responseCount}
</div>
<div className="col-span-1 flex justify-between">
<SurveyTypeIndicator type={survey.type} />
</div>
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
{convertDateString(survey.createdAt.toString())}
</div>
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
{timeSince(survey.updatedAt.toString())}
</div>
<div className="col-span-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-600">
{survey.creator ? survey.creator.name : "-"}
</div>
</Link>
<SurveyDropDownMenu
survey={survey}
key={`surveys-${survey.id}`}
environmentId={environment.id}
environment={environment}
otherEnvironment={otherEnvironment!}
webAppUrl={WEBAPP_URL}
singleUseId={singleUseId}
isSurveyCreationDeletionDisabled={isViewer}
duplicateSurvey={duplicateSurvey}
deleteSurvey={deleteSurvey}
/>
</div>
)

Comment on lines +36 to +87
export const getSurveys = reactCache(
(
environmentId: string,
limit?: number,
offset?: number,
filterCriteria?: TSurveyFilterCriteria
): Promise<TSurvey[]> =>
cache(
async () => {
validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]);

try {
if (filterCriteria?.sortBy === "relevance") {
// Call the sortByRelevance function
return await getSurveysSortedByRelevance(environmentId, limit, offset ?? 0, filterCriteria);
}

// Fetch surveys normally with pagination and include response count
const surveysPrisma = await prisma.survey.findMany({
where: {
environmentId,
...buildWhereClause(filterCriteria),
},
select: surveySelect,
orderBy: buildOrderByClause(filterCriteria?.sortBy),
take: limit,
skip: offset,
});

return surveysPrisma.map((survey) => {
return {
...survey,
responseCount: survey._count.responses,
};
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
throw new DatabaseError(error.message);
}
throw error;
}
},
[`getSurveys-${environmentId}-${limit}-${offset}-${JSON.stringify(filterCriteria)}`],
{
tags: [
surveyCache.tag.byEnvironmentId(environmentId),
responseCache.tag.byEnvironmentId(environmentId),
],
}
)()
);
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

Approve implementation with a suggestion for cache key improvement

The getSurveys function is well-implemented, handling various scenarios including different sorting options. The use of caching and error handling is appropriate. However, there's a potential issue with the cache key.

The use of JSON.stringify(filterCriteria) in the cache key (line 79) may lead to inconsistent cache keys if the properties in filterCriteria are in different orders, even if they represent the same criteria. This can cause cache misses for logically equivalent filter criteria.

Consider using a serialization method that guarantees consistent property order, such as sorting the keys before stringifying:

const sortedFilterCriteria = filterCriteria ? 
  JSON.stringify(Object.keys(filterCriteria).sort().reduce((r, k) => (r[k] = filterCriteria[k], r), {})) : 
  '';

[`getSurveys-${environmentId}-${limit}-${offset}-${sortedFilterCriteria}`]

This will ensure that the cache key remains consistent for equivalent filter criteria, regardless of the order of properties.

Comment on lines +89 to +171
export const getSurveysSortedByRelevance = reactCache(
(
environmentId: string,
limit?: number,
offset?: number,
filterCriteria?: TSurveyFilterCriteria
): Promise<TSurvey[]> =>
cache(
async () => {
validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]);

try {
let surveys: TSurvey[] = [];
const inProgressSurveyCount = await getInProgressSurveyCount(environmentId, filterCriteria);

// Fetch surveys that are in progress first
const inProgressSurveys =
offset && offset > inProgressSurveyCount
? []
: await prisma.survey.findMany({
where: {
environmentId,
status: "inProgress",
...buildWhereClause(filterCriteria),
},
select: surveySelect,
orderBy: buildOrderByClause("updatedAt"),
take: limit,
skip: offset,
});

surveys = inProgressSurveys.map((survey) => {
return {
...survey,
responseCount: survey._count.responses,
};
});

// Determine if additional surveys are needed
if (offset !== undefined && limit && inProgressSurveys.length < limit) {
const remainingLimit = limit - inProgressSurveys.length;
const newOffset = Math.max(0, offset - inProgressSurveyCount);
const additionalSurveys = await prisma.survey.findMany({
where: {
environmentId,
status: { not: "inProgress" },
...buildWhereClause(filterCriteria),
},
select: surveySelect,
orderBy: buildOrderByClause("updatedAt"),
take: remainingLimit,
skip: newOffset,
});

surveys = [
...surveys,
...additionalSurveys.map((survey) => {
return {
...survey,
responseCount: survey._count.responses,
};
}),
];
}

return surveys;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
throw new DatabaseError(error.message);
}
throw error;
}
},
[`getSurveysSortedByRelevance-${environmentId}-${limit}-${offset}-${JSON.stringify(filterCriteria)}`],
{
tags: [
surveyCache.tag.byEnvironmentId(environmentId),
responseCache.tag.byEnvironmentId(environmentId),
],
}
)()
);
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

Improve offset and limit handling in getSurveysSortedByRelevance

The function's logic for prioritizing in-progress surveys and handling pagination is generally sound. However, there are a few areas that could be improved:

  1. In the condition for fetching in-progress surveys (lines 106-108), the handling of offset could be more robust:
const inProgressSurveys = offset && offset >= inProgressSurveyCount
  ? []
  : await prisma.survey.findMany({
      // ... existing query
    });

This ensures that when offset is equal to inProgressSurveyCount, an empty array is returned, which is more intuitive.

  1. In the condition for fetching additional surveys (line 128), the check for limit could lead to unexpected behavior if limit is 0:
if (typeof offset === 'number' && typeof limit === 'number' && inProgressSurveys.length < limit) {
  // ... existing logic for fetching additional surveys
}

This change ensures that the condition is only true when both offset and limit are numbers, and prevents unexpected behavior when limit is 0.

  1. Consider adding a check to ensure that limit and offset are non-negative integers in the validateInputs call (line 98):
validateInputs(
  [environmentId, ZId],
  [limit, ZOptionalNumber.refine((n) => n === undefined || (Number.isInteger(n) && n >= 0), "Must be a non-negative integer")],
  [offset, ZOptionalNumber.refine((n) => n === undefined || (Number.isInteger(n) && n >= 0), "Must be a non-negative integer")]
);

These changes will improve the robustness of the function and prevent potential edge case issues.

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

🧹 Outside diff range and nitpick comments (1)
apps/web/app/(app)/environments/[environmentId]/surveys/lib/surveys.ts (1)

175-208: Remove unnecessary optional chaining operator

The getSurvey function is well-implemented with proper error handling and caching. However, there's an unnecessary optional chaining operator on line 201.

Since surveyPrisma is confirmed to be non-null in the preceding check (if (!surveyPrisma) { return null; }), the optional chaining operator ? in surveyPrisma?._count.responses is unnecessary.

Simplify the code:

-return { ...surveyPrisma, responseCount: surveyPrisma?._count.responses };
+return { ...surveyPrisma, responseCount: surveyPrisma._count.responses };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 3a978b3 and 0480af5.

📒 Files selected for processing (1)
  • apps/web/app/(app)/environments/[environmentId]/surveys/lib/surveys.ts (1 hunks)
🧰 Additional context used
🔇 Additional comments (1)
apps/web/app/(app)/environments/[environmentId]/surveys/lib/surveys.ts (1)

1-34: LGTM: Imports and surveySelect object are well-structured

The imports cover all necessary modules for database operations, caching, and type definitions. The surveySelect object is comprehensive and includes relevant fields for survey data retrieval.

Comment on lines +36 to +87
export const getSurveys = reactCache(
(
environmentId: string,
limit?: number,
offset?: number,
filterCriteria?: TSurveyFilterCriteria
): Promise<TSurvey[]> =>
cache(
async () => {
validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]);

try {
if (filterCriteria?.sortBy === "relevance") {
// Call the sortByRelevance function
return await getSurveysSortedByRelevance(environmentId, limit, offset ?? 0, filterCriteria);
}

// Fetch surveys normally with pagination and include response count
const surveysPrisma = await prisma.survey.findMany({
where: {
environmentId,
...buildWhereClause(filterCriteria),
},
select: surveySelect,
orderBy: buildOrderByClause(filterCriteria?.sortBy),
take: limit,
skip: offset,
});

return surveysPrisma.map((survey) => {
return {
...survey,
responseCount: survey._count.responses,
};
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
throw new DatabaseError(error.message);
}
throw error;
}
},
[`surveyList-getSurveys-${environmentId}-${limit}-${offset}-${JSON.stringify(filterCriteria)}`],
{
tags: [
surveyCache.tag.byEnvironmentId(environmentId),
responseCache.tag.byEnvironmentId(environmentId),
],
}
)()
);
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

Approve implementation with a suggestion for cache key improvement

The getSurveys function is well-implemented, handling various scenarios including different sorting options. The use of caching and error handling is appropriate. However, there's a potential issue with the cache key.

The use of JSON.stringify(filterCriteria) in the cache key (line 79) may lead to inconsistent cache keys if the properties in filterCriteria are in different orders, even if they represent the same criteria. This can cause cache misses for logically equivalent filter criteria.

Consider using a serialization method that guarantees consistent property order, such as sorting the keys before stringifying:

const sortedFilterCriteria = filterCriteria ? 
  JSON.stringify(Object.keys(filterCriteria).sort().reduce((r, k) => (r[k] = filterCriteria[k], r), {})) : 
  '';

[`surveyList-getSurveys-${environmentId}-${limit}-${offset}-${sortedFilterCriteria}`]

This will ensure that the cache key remains consistent for equivalent filter criteria, regardless of the order of properties.

Comment on lines +89 to +173
export const getSurveysSortedByRelevance = reactCache(
(
environmentId: string,
limit?: number,
offset?: number,
filterCriteria?: TSurveyFilterCriteria
): Promise<TSurvey[]> =>
cache(
async () => {
validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]);

try {
let surveys: TSurvey[] = [];
const inProgressSurveyCount = await getInProgressSurveyCount(environmentId, filterCriteria);

// Fetch surveys that are in progress first
const inProgressSurveys =
offset && offset > inProgressSurveyCount
? []
: await prisma.survey.findMany({
where: {
environmentId,
status: "inProgress",
...buildWhereClause(filterCriteria),
},
select: surveySelect,
orderBy: buildOrderByClause("updatedAt"),
take: limit,
skip: offset,
});

surveys = inProgressSurveys.map((survey) => {
return {
...survey,
responseCount: survey._count.responses,
};
});

// Determine if additional surveys are needed
if (offset !== undefined && limit && inProgressSurveys.length < limit) {
const remainingLimit = limit - inProgressSurveys.length;
const newOffset = Math.max(0, offset - inProgressSurveyCount);
const additionalSurveys = await prisma.survey.findMany({
where: {
environmentId,
status: { not: "inProgress" },
...buildWhereClause(filterCriteria),
},
select: surveySelect,
orderBy: buildOrderByClause("updatedAt"),
take: remainingLimit,
skip: newOffset,
});

surveys = [
...surveys,
...additionalSurveys.map((survey) => {
return {
...survey,
responseCount: survey._count.responses,
};
}),
];
}

return surveys;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
throw new DatabaseError(error.message);
}
throw error;
}
},
[
`surveyList-getSurveysSortedByRelevance-${environmentId}-${limit}-${offset}-${JSON.stringify(filterCriteria)}`,
],
{
tags: [
surveyCache.tag.byEnvironmentId(environmentId),
responseCache.tag.byEnvironmentId(environmentId),
],
}
)()
);
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

Improve offset and limit handling in getSurveysSortedByRelevance

The function's logic for prioritizing in-progress surveys and handling pagination is generally sound. However, there are a few areas that could be improved:

  1. In the condition for fetching in-progress surveys (lines 106-108), the handling of offset could be more robust:
const inProgressSurveys = offset && offset >= inProgressSurveyCount
  ? []
  : await prisma.survey.findMany({
      // ... existing query
    });

This ensures that when offset is equal to inProgressSurveyCount, an empty array is returned, which is more intuitive.

  1. In the condition for fetching additional surveys (line 128), the check for limit could lead to unexpected behavior if limit is 0:
if (typeof offset === 'number' && typeof limit === 'number' && inProgressSurveys.length < limit) {
  // ... existing logic for fetching additional surveys
}

This change ensures that the condition is only true when both offset and limit are numbers, and prevents unexpected behavior when limit is 0.

  1. Consider adding a check to ensure that limit and offset are non-negative integers in the validateInputs call (line 98):
validateInputs(
  [environmentId, ZId],
  [limit, ZOptionalNumber.refine((n) => n === undefined || (Number.isInteger(n) && n >= 0), "Must be a non-negative integer")],
  [offset, ZOptionalNumber.refine((n) => n === undefined || (Number.isInteger(n) && n >= 0), "Must be a non-negative integer")]
);

These changes will improve the robustness of the function and prevent potential edge case issues.

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 0480af5 and 6b5e760.

📒 Files selected for processing (2)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCard.tsx (1 hunks)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyTypeIndicator.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCard.tsx
🧰 Additional context used
🔇 Additional comments (2)
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyTypeIndicator.tsx (2)

1-1: LGTM: Imports are correctly defined and used.

The import statement is concise and follows best practices. All imported icons are used in the component.


3-5: LGTM: Interface is well-defined and follows best practices.

The SurveyTypeIndicatorProps interface is clear, concise, and correctly typed. It provides a good structure for the component's props.

Comment on lines +7 to +10
const surveyTypeMapping = {
app: { icon: Code, label: "App" },
link: { icon: Link2Icon, label: "Link" },
};
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

LGTM: Well-structured mapping object. Consider enhancing type safety.

The surveyTypeMapping object is clear and provides a good association between survey types and their visual representations. To improve type safety and maintainability, consider defining a type for the mapping object:

type SurveyType = 'app' | 'link';
type IconType = typeof Code | typeof Link2Icon | typeof HelpCircle;

type SurveyTypeMap = {
  [key in SurveyType]: {
    icon: IconType;
    label: string;
  }
};

const surveyTypeMapping: SurveyTypeMap = {
  app: { icon: Code, label: "App" },
  link: { icon: Link2Icon, label: "Link" },
};

This change would provide better type checking and autocompletion when working with survey types.

Comment on lines +12 to +21
export const SurveyTypeIndicator = ({ type }: SurveyTypeIndicatorProps) => {
const { icon: Icon, label } = surveyTypeMapping[type] || { icon: HelpCircle, label: "Unknown" };

return (
<div className="flex items-center space-x-2 text-sm text-slate-600">
<Icon className="h-4 w-4" />
<span>{label}</span>
</div>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

LGTM: Well-implemented component. Consider enhancing type safety.

The SurveyTypeIndicator component is concise, follows React best practices, and handles unknown types gracefully. To improve type safety when accessing surveyTypeMapping[type], consider the following change:

export const SurveyTypeIndicator = ({ type }: SurveyTypeIndicatorProps) => {
  const { icon: Icon, label } = type in surveyTypeMapping
    ? surveyTypeMapping[type as keyof typeof surveyTypeMapping]
    : { icon: HelpCircle, label: "Unknown" };

  // ... rest of the component
};

This change ensures that TypeScript correctly narrows the type when accessing surveyTypeMapping[type], providing better type safety.

Copy link
Member

@mattinannt mattinannt left a comment

Choose a reason for hiding this comment

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

@Dhruwang works great; thank you :-)

@mattinannt mattinannt added this pull request to the merge queue Oct 10, 2024
Merged via the queue into main with commit abc4c7f Oct 10, 2024
@mattinannt mattinannt deleted the chore-central-data-fetching-for-surveys-table branch October 10, 2024 14:46
@oss-gg
Copy link

oss-gg bot commented Oct 10, 2024

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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants