feat: custom data fetching for surveys table#3318
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
1 Skipped Deployment
|
WalkthroughThe 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
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
|
Thank you for following the naming conventions for pull request titles! 🙏 |
There was a problem hiding this comment.
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
ZSurveyListItemschema is comprehensive and well-defined. Here are some observations and suggestions:
The 'type' field uses an enum with a comment about future changes. Consider creating a separate issue to track this refactoring.
The 'singleUse' object includes an 'isEncrypted' field. Ensure that this is handled securely in the application logic.
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
deleteSurveyimport 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
getSurveysActiontogetSurveyListItemsActionand the use ofgetSurveyListItemsare consistent with the import changes. The addition of schema validation forfilterCriteriain 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
deleteSurveyActionis 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 fetchCurrently, the component does not handle errors that may occur during the initial fetch of surveys. If the
getSurveyListItemsActionfails, the user might not receive feedback, andisFetchingmay remaintrue. 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 fetchSimilar to the initial fetch, the
fetchNextPagefunction 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 dependenciesIn the
useEffecthook for initializing survey filters, the checktypeof 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
useEffecthook: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 inhandleDeleteSurveyThe 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
finallyblock can ensuresetLoading(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 induplicateSurveyAndRefreshThe 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
finallyblock to ensuresetLoading(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
📒 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
zodlibrary is imported for schema definition, andZSurveyStatusis imported from a custom types file, which is good for maintaining consistency across the project.
26-26: LGTM: Type export is correctly defined.The
TSurveyListItemtype is correctly inferred from theZSurveyListItemschema usingz.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:
- Comprehensive schema definition covering various aspects of a survey list item.
- Proper use of Zod for schema validation and TypeScript type inference.
- Flexibility in handling nullable fields like 'creator' and 'singleUse'.
Areas for potential improvement:
- Consider adding more specific constraints to some fields for tighter validation.
- 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
SurveyLoadingcomponent 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
SurveyLoadingcomponent 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:
- Importing
TSurveyListItemtype ensures consistency with the new data structure for surveys.- Importing
Modalfrom@formbricks/uipromotes 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.tsxenhance type safety and modularity through:
- Updated imports for better consistency with the project structure.
- A more specific type (
TSurveyListItem) for the survey prop.These improvements are commendable. However, to ensure these changes don't introduce issues:
- Verify that all uses of the
surveyprop within this component and its children (especiallySurveyCopyOptions) are compatible with the newTSurveyListItemtype.- Confirm that
SurveyCopyOptionscomponent is updated to work withTSurveyListItem.- Ensure no critical data is lost in the transition from
TSurveytoTSurveyListItem.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
SurveyCopyOptionscomponent is compatible with the newTSurveyListItemtype.Please verify:
- That the
SurveyCopyOptionscomponent's props are updated to acceptTSurveyListIteminstead ofTSurvey.- That all uses of the
surveyprop withinSurveyCopyOptionsare compatible withTSurveyListItem.Run the following script to check the
SurveyCopyOptionscomponent:#!/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
SurveyCopyOptionsis fully compatible with the newTSurveyListItemtype.
9-9: Verify internal usage of the updated survey prop type.The change from
TSurveytoTSurveyListItemfor thesurveyprop 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:
- That all uses of the
surveyprop within this component and its children are compatible with theTSurveyListItemtype.- That no required fields from the previous
TSurveytype are missing inTSurveyListItem.Run the following script to check for potential issues:
If the script returns results, please review them to ensure compatibility with the new
TSurveyListItemtype.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
TSurveyListItemimport 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
surveyprop type has been updated fromTSurveytoTSurveyListItem, 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.tsxprimarily 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:
- Compile the entire project to check for any type-related errors.
- Run the application and navigate to the surveys page to verify that the
SurveyCopyOptionscomponent renders correctly.- 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.tsxandTSurveyListItemare 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 componentsThe changes to the import statements are a positive improvement:
- Centralizing UI component imports from
@formbricks/ui/componentsenhances maintainability and consistency across the application.- This approach makes it easier to manage and update UI components globally.
- 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 importsI've reviewed the rest of the
SurveyFilterDropdowncomponent, and I can confirm:
- The component's logic and structure remain unchanged.
- The imported UI components are used correctly throughout the component.
- 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 changeThe 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 componentThe 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:
- Ensure that the new SurveysList component maintains the same interface as the previous one to avoid breaking changes in this file.
- 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.
- 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
SurveysListcomponent has been successfully relocated to a more specific path withinpage.tsx. No other imports ofSurveysListwere 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
getSurveyActiontogetSurveyListItemActionand the use ofgetSurveyListItemare 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
getSurveyActionfound.🏁 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 tsxLength 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:
- The form renders correctly with the new imports.
- The checkbox functionality for selecting environments works as expected.
- The form submission process, including the
onSubmitfunction, operates correctly with the newTSurveyListItemtype.- The
copySurveyToOtherEnvironmentActionis called with the correct parameters.- 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:
- The shift to using "@formbricks/ui/components" suggests an improved component library structure.
- The change from
TSurveytoTSurveyListItemindicates a refinement in the data structure being used.- The core functionality of the component remains intact, which is good for maintaining consistency.
Recommendations:
- Verify that all instances of
TSurveythat should now beTSurveyListItemhave been updated throughout the codebase.- Conduct thorough testing of the CopySurveyForm component to ensure it functions correctly with the new imports and types.
- Consider updating or adding unit tests to cover the component's functionality, especially focusing on areas affected by the type changes.
- 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
TSurveytoTSurveyListItemfor thesurveyprop 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
TSurveythat should now beTSurveyListItemhave been updated accordingly.✅ Verification successful
Approve type change with .
The change from
TSurveytoTSurveyListItemfor thesurveyprop inCopySurveyForm.tsxhas 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 fieldsThe
.toString()calls onsurvey.createdAtandsurvey.updatedAtmay be unnecessary if these fields are already strings or acceptable formats forconvertDateStringandtimeSincefunctions. Removing.toString()could simplify the code.Also applies to: 126-126
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyList.tsx (3)
125-127: EnsurehandleDuplicateSurveyworks withTSurveyListItemThe
handleDuplicateSurveyfunction now accepts a parameter of typeTSurveyListItem. Confirm that the duplication logic is appropriate for this type and that all necessary properties are correctly handled when updating thesurveysstate.
149-163: Verify thatSurveyCardaccepts updated survey typeEnsure that the
SurveyCardcomponent is updated to acceptsurveyof typeTSurveyListItem. This includes verifying that all required properties used withinSurveyCardare present inTSurveyListItem.
44-44: Verify compatibility ofTSurveyListItemwith existing usageThe state
surveyshas been updated to useTSurveyListItem[]instead ofTSurvey[]. Ensure that all usages ofsurveyswithin this component and any child components, such asSurveyCard, are compatible withTSurveyListItem. 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
surveysand check for type compatibility:apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilters.tsx (1)
8-14: Good use of shared UI components for modularityImporting
Button,DropdownMenu, andSearchBarfrom@formbricks/ui/componentsimproves 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 functionsThe imports have been correctly updated to include
copySurveyToOtherEnvironmentAction,deleteSurveyAction, andgetSurveyListItemAction, replacinggetSurveyAction. This ensures the component uses the latest action functions.
8-8: Updated type import toTSurveyListItemThe 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 forsurveyupdated toTSurveyListItemThe
surveyprop inSurveyDropDownMenuPropsinterface has been updated fromTSurveytoTSurveyListItem. Verify that all usages ofsurveywithin the component are compatible withTSurveyListItemand that no required properties are missing.
35-35: UpdatedduplicateSurveyfunction signatureThe
duplicateSurveyfunction now accepts aTSurveyListIteminstead ofTSurvey. Ensure that this change is reflected whereverduplicateSurveyis implemented and that it functions correctly with the new type.
56-60: SimplifiedhandleDeleteSurveyfunction parameterThe
handleDeleteSurveyfunction now accepts asurveyIdstring directly instead of asurveyobject. 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 usegetSurveyListItemActionThe duplication process now uses
getSurveyListItemAction, ensuring that the duplicated survey data aligns with the expectedTSurveyListItemtype. This update maintains consistency in how survey data is handled post-duplication.
213-213: CorrectonDeletehandler inDeleteDialogThe
onDeleteprop now correctly passessurvey.idtohandleDeleteSurvey, matching the updated function signature that expects asurveyId.
| @@ -1,5 +1,5 @@ | |||
| import { TSortOption, TSurveyFilters } from "@formbricks/types/surveys/types"; | |||
| import { DropdownMenuItem } from "../../DropdownMenu"; | |||
| import { DropdownMenuItem } from "@formbricks/ui/components/DropdownMenu"; | |||
There was a problem hiding this comment.
💡 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
| 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]); |
There was a problem hiding this comment.
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.
| 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, |
There was a problem hiding this comment.
🛠️ 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.
| 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)
There was a problem hiding this comment.
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!
| <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> |
There was a problem hiding this comment.
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.
| <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> |
| <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> |
There was a problem hiding this comment.
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]); |
There was a problem hiding this comment.
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.
| 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 }; |
There was a problem hiding this comment.
🛠️ 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.
| return { ...surveyPrisma, responseCount: surveyPrisma?._count.responses }; | |
| return { ...surveyPrisma, responseCount: surveyPrisma._count.responses }; |
| ): Promise<TSurveyListItem[]> => | ||
| cache( | ||
| async () => { | ||
| validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]); |
There was a problem hiding this comment.
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.
| validateInputs([environmentId, ZId], [limit, ZOptionalNumber], [offset, ZOptionalNumber]); | |
| validateInputs( | |
| [environmentId, ZId], | |
| [limit, ZOptionalNumber.min(0).int()], | |
| [offset, ZOptionalNumber.min(0).int()] | |
| ); |
| if (offset !== undefined && limit && inProgressSurveys.length < limit) { | ||
| const remainingLimit = limit - inProgressSurveys.length; | ||
| const newOffset = Math.max(0, offset - inProgressSurveyCount); |
There was a problem hiding this comment.
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.
| 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); |
| offset && offset > inProgressSurveyCount | ||
| ? [] | ||
| : await prisma.survey.findMany({ |
There was a problem hiding this comment.
🛠️ 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.
| offset && offset > inProgressSurveyCount | |
| ? [] | |
| : await prisma.survey.findMany({ | |
| (offset ?? 0) >= inProgressSurveyCount | |
| ? [] | |
| : await prisma.survey.findMany({ |
mattinannt
left a comment
There was a problem hiding this comment.
@Dhruwang thanks for the PR 🚀 Already looks pretty good but going through the code I saw a few things that needs change :-)
apps/web/app/(app)/environments/[environmentId]/surveys/components/CopySurveyForm.tsx
Outdated
Show resolved
Hide resolved
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCard.tsx
Show resolved
Hide resolved
| if (survey.singleUse?.enabled) { | ||
| const generateSingleUseIdResponse = await generateSingleUseIdAction({ | ||
| surveyId: survey.id, | ||
| isEncrypted: survey.singleUse?.isEncrypted ? true : false, |
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyList.tsx
Outdated
Show resolved
Hide resolved
|
|
||
| const [singleUseId, setSingleUseId] = useState<string | undefined>(); | ||
|
|
||
| useEffect(() => { |
There was a problem hiding this comment.
can we also generate this singleUseId in the dropdown itself and make this whole component a server component?
There was a problem hiding this comment.
SurveyCard is called by SurveyList which is a client component so, this would automatically make this a client component 😊
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyCopyOptions.tsx
Outdated
Show resolved
Hide resolved
| await deleteSurveyAction({ surveyId: survey.id }); | ||
| deleteSurvey(survey.id); | ||
| await deleteSurveyAction({ surveyId }); | ||
| deleteSurvey(surveyId); |
There was a problem hiding this comment.
What is this doing? Again could maybe a router.refresh be a better choice here instead of prop drilling?
apps/web/app/(app)/environments/[environmentId]/surveys/lib/surveys.ts
Outdated
Show resolved
Hide resolved
…ore-central-data-fetching-for-surveys-table
…://github.com/formbricks/formbricks into chore-central-data-fetching-for-surveys-table
There was a problem hiding this comment.
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
ZSurveyschema is comprehensive and well-defined. It covers all essential properties of a survey object.Consider creating a separate
ZSurveyTypeenum for the 'type' field to make future updates easier. This would allow you to replace the inline enum withZSurveyTypeand 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 variableThe
isSurveyCreationDeletionDisabledvariable is redundant as it's simply assigning the value ofisViewer. You can useisViewerdirectly where needed, improving code clarity.Apply this diff to remove the redundant variable:
- const isSurveyCreationDeletionDisabled = isViewer;Make sure to replace any usage of
isSurveyCreationDeletionDisabledwithisViewerin 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 improvedThe changes to the
SurveyFilterscomponent 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
SurveyFilterscomponent?apps/web/app/(app)/environments/[environmentId]/surveys/lib/surveys.ts (1)
173-206: Approve getSurvey implementation with a minor suggestionThe
getSurveyfunction 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
surveyPrismais 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
handleDeleteSurveyimprove the function's simplicity and modularity by working directly with thesurveyIdand usingdeleteSurveyAction.Consider improving the error handling by using the
getFormattedErrorMessagefunction 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
📒 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
TSurveytype 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
ZSurveyCopyFormValidationschema 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
TSurveyCopyFormDatatype alias is properly defined using Zod's infer utility, consistent with the approach used forTSurvey.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/uipackage for theModalcomponent 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-structuredThe "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 improvementsThe
SurveyCardcomponent 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:
- Proper use of "use client" directive for client-side rendering.
- Well-defined props interface.
- Effective use of useMemo for computed values.
- Good error handling in the singleUseId fetching logic.
Areas for improvement:
- Moving the
SurveyTypeIndicatorto a separate file for better organization.- Restructuring the layout to place the
SurveyDropDownMenuoutside theLinkcomponent for improved accessibility.- Simplifying some expressions and removing redundant variables.
- 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 issueAdd a default case to handle unexpected survey statuses
The
surveyStatusLabelcomputation doesn't handle unexpected or new statuses, which could lead toundefinedlabels. 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 testingThe new data fetching logic using
getSurveysActionand 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:
- Fetch surveys with no filters applied
- Fetch surveys with a single filter (e.g., by name)
- Fetch surveys with multiple filters
- 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 TSurveyFiltersThe
handleDuplicateSurveyfunction signature has been updated to useTSurveyFiltersinstead ofTSurvey. 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
TSurveyFilterstype. If necessary, update the duplication logic to work with the new type.
Line range hint
1-190: Summary of SurveyList.tsx changesOverall, 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:
- Verify type consistency between
TSurveyandTSurveyFiltersthroughout the component.- Run a thorough type-check to ensure no type-related errors were introduced.
- Consider adding test cases for pagination with various filter combinations.
- Fix the grid column mismatch in the survey card layout.
- Review and update the survey duplication logic to ensure it works correctly with the new
TSurveyFilterstype.After addressing these points, the component should be well-structured and fully functional with its new features.
139-163:⚠️ Potential issueFix 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 verificationThe addition of filter-related state and effects enhances the component's functionality. The change in the
surveysstate type fromTSurvey[]toTSurveyFilters[]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 TSurveyFiltersThe import of both
TSurveyandTSurveyFilterssuggests 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
TSurveyandTSurveyFiltersinSurveyList.tsxare 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/ -A2Length 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/ -A2Length 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 -A2Length of output: 1390
apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyFilters.tsx (4)
1-16: LGTM: Import statements have been improvedThe changes to the import statements look good. The shift to using shared UI components from
@formbricks/ui/componentsenhances 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
SortOptionandinitialFiltersto the shared UI library has been partially addressed. Consider ifSortOptioncould also be moved to the shared library in the future for further consistency.
70-73: LGTM: typeOptions moved inside componentMoving the
typeOptionsarray 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 functionalityThe 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
SurveyFilterPropsinterface 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 simplifiedThe removal of
orientationandsetOrientationprops from theSurveyFilterPropsinterface 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
SurveyFiltershave been updated to remove theorientationandsetOrientationprops. Run the following script to check for any remaining usage:✅ Verification successful
Please run the following script to verify any remaining usage of
orientationprops inSurveyFilters:
Please run the following script to verify any remaining usage of
orientationprops inSurveyFilters:
Verified:
orientationprops successfully removed fromSurveyFiltersThe
orientationandsetOrientationprops have been fully removed from theSurveyFiltersinterface, 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-definedThe imports cover all necessary modules and types for the file's functionality. The
surveySelectobject 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
DeleteDialogimproves 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.iddirectly tohandleDeleteSurveyis consistent with the earlier changes and improves code clarity.
| 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> | ||
| ); |
There was a problem hiding this comment.
🛠️ 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.
- Create a new file, e.g.,
SurveyTypeIndicator.tsx. - Move the
SurveyTypeIndicatorcomponent to this new file. - Export the component from the new file.
- Import it in the
SurveyCard.tsxfile.
This separation will improve the overall structure of your code and make it easier to maintain and potentially reuse the SurveyTypeIndicator component.
There was a problem hiding this comment.
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!
| const generateSingleUseIdResponse = await generateSingleUseIdAction({ | ||
| surveyId: survey.id, | ||
| isEncrypted: survey.singleUse?.isEncrypted ? true : false, | ||
| }); |
There was a problem hiding this comment.
🛠️ 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.
| 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)
| 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]); |
There was a problem hiding this comment.
🛠️ 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.
| 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)
| 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> |
There was a problem hiding this comment.
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:
- Move the
SurveyDropDownMenuoutside theLinkcomponent. - 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.
| 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> | |
| ) |
| 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), | ||
| ], | ||
| } | ||
| )() | ||
| ); |
There was a problem hiding this comment.
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.
| 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), | ||
| ], | ||
| } | ||
| )() | ||
| ); |
There was a problem hiding this comment.
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:
- In the condition for fetching in-progress surveys (lines 106-108), the handling of
offsetcould 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.
- In the condition for fetching additional surveys (line 128), the check for
limitcould lead to unexpected behavior iflimitis 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.
- Consider adding a check to ensure that
limitandoffsetare non-negative integers in thevalidateInputscall (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.
…ore-central-data-fetching-for-surveys-table
…://github.com/formbricks/formbricks into chore-central-data-fetching-for-surveys-table
There was a problem hiding this comment.
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 operatorThe
getSurveyfunction is well-implemented with proper error handling and caching. However, there's an unnecessary optional chaining operator on line 201.Since
surveyPrismais confirmed to be non-null in the preceding check (if (!surveyPrisma) { return null; }), the optional chaining operator?insurveyPrisma?._count.responsesis 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
📒 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-structuredThe imports cover all necessary modules for database operations, caching, and type definitions. The
surveySelectobject is comprehensive and includes relevant fields for survey data retrieval.
| 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), | ||
| ], | ||
| } | ||
| )() | ||
| ); |
There was a problem hiding this comment.
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.
| 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), | ||
| ], | ||
| } | ||
| )() | ||
| ); |
There was a problem hiding this comment.
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:
- In the condition for fetching in-progress surveys (lines 106-108), the handling of
offsetcould 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.
- In the condition for fetching additional surveys (line 128), the check for
limitcould lead to unexpected behavior iflimitis 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.
- Consider adding a check to ensure that
limitandoffsetare non-negative integers in thevalidateInputscall (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.
There was a problem hiding this comment.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 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
SurveyTypeIndicatorPropsinterface is clear, concise, and correctly typed. It provides a good structure for the component's props.
| const surveyTypeMapping = { | ||
| app: { icon: Code, label: "App" }, | ||
| link: { icon: Link2Icon, label: "Link" }, | ||
| }; |
There was a problem hiding this comment.
🛠️ 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.
| 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> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
🛠️ 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.
mattinannt
left a comment
There was a problem hiding this comment.
@Dhruwang works great; thank you :-)
|
Awarding Dhruwang: 50 points 🕹️ Well done! Check out your new contribution on oss.gg/Dhruwang |
What does this PR do?
Fixes https://github.com/formbricks/internal/issues/383 #2844
How should this be tested?
Test surveys table
Checklist
Required
pnpm buildconsole.logsgit pull origin mainAppreciated
Summary by CodeRabbit
Release Notes
New Features
SurveyCardcomponent for displaying survey information.SurveyTypeIndicatorcomponent for visual representation of survey types.Improvements
Bug Fixes
Chores