-
Notifications
You must be signed in to change notification settings - Fork 116
Implement batch test case removal #4759
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement batch test case removal #4759
Conversation
5b2b4fd to
3db6d96
Compare
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughIntroduces batch delete modal for test cases with folder counter updates. Refactors test case selection state from flat array to richer Changes
Sequence DiagramsequenceDiagram
participant User
participant UI as TestCaseList / Modal
participant SelectionState as allTestCasesPage State
participant BatchDelete as useBatchDeleteTestCases
participant API
participant Redux
participant Notifications
User->>UI: Click Delete (or select rows)
UI->>SelectionState: handleSelectedRows(selectedRows)
SelectionState->>SelectionState: Update selectedRows: SelectedTestCaseRow[]
SelectionState->>SelectionState: Compute folderDeltasMap via countBy
User->>UI: Confirm delete in modal
UI->>BatchDelete: batchDelete(testCaseIds, folderDeltasMap)
BatchDelete->>BatchDelete: Show loading spinner
BatchDelete->>API: DELETE /project/{key}/tms/test-case/batch
API-->>BatchDelete: Success response
BatchDelete->>Redux: Dispatch updateFolderCounterAction (per folder)
BatchDelete->>Redux: Dispatch useRefetchCurrentTestCases()
Redux->>API: Fetch updated test cases
API-->>Redux: Updated list
BatchDelete->>SelectionState: onClearSelection()
SelectionState->>SelectionState: Clear selectedRows
BatchDelete->>Notifications: Show success notification
BatchDelete->>UI: Hide modal & spinner
UI->>User: Updated list displayed
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Areas requiring extra attention:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
3db6d96 to
31469b4
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## feature/test-library #4759 +/- ##
=====================================================
Coverage 72.59% 72.59%
=====================================================
Files 79 79
Lines 905 905
Branches 124 124
=====================================================
Hits 657 657
Misses 224 224
Partials 24 24 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (6)
app/src/controllers/testCase/selectors.ts (1)
72-73:testCasesSelectorreturn type and fallback are consistent; optional consistency refactorReturning
TestCase[]and defaulting to[]is a good, null‑safe pattern and matches the updated state typing.If you want to keep selectors consistent with others that go through
testCaseSelector, you could optionally refactor as:-export const testCasesSelector = (state: RootState): TestCase[] => - state.testCase?.testCases?.list || []; +export const testCasesSelector = (state: RootState): TestCase[] => + testCaseSelector(state).testCases?.list || [];Purely a style/consistency tweak; current code is fine.
app/src/pages/inside/testPlansPage/testPlanDetailsPage/testPlanFolders/allTestCasesPage/allTestCasesPage.tsx (1)
29-29: Consider the architectural implications of cross-feature imports.The import of
SelectedTestCaseRowfromtestCaseLibraryPageintotestPlansPagecreates a dependency between these features. IfSelectedTestCaseRowis truly shared across multiple features, consider moving it to a common types location (e.g.,pages/inside/common/typesortypes/testCase) to better reflect its shared nature and reduce coupling.app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/useBatchDeleteTestCases.ts (2)
35-43: Consider adding input validation for edge cases.The
batchDeletefunction doesn't validate thattestCaseIdsis non-empty before making the API call. While the UI likely prevents this, defensive validation would make the hook more robust and prevent unnecessary API calls.Apply this pattern:
const batchDelete = useCallback( async (testCaseIds: number[], folderDeltasMap: Record<number, number>) => { + if (testCaseIds.length === 0) { + return; + } + showSpinner();
61-64: Generic error notification lacks context.The error handler displays a generic "errorOccurredTryAgain" message without logging the error or providing specific context about the batch delete operation. This makes debugging difficult in production.
Consider logging the error and providing more context:
} catch (error: unknown) { + console.error('Batch delete test cases failed:', error); showErrorNotification({ - messageId: 'errorOccurredTryAgain', + messageId: 'testCasesBatchDeleteFailed', }); }app/src/pages/inside/common/testCaseList/testCaseList.tsx (2)
90-104: Selection logic correctly tracks folder IDs.The refactored
handleRowSelectproperly finds the test case to extractfolderIdand maintains the newSelectedTestCaseRowstructure. The toggle logic with.some()and.filter()is clear and correct.Optional performance consideration: For large test case lists, the
testCases.find()on Line 91 could become a bottleneck. Consider creating a Map ofid → testCaseif performance becomes an issue.
106-122: Select all logic is correct but could be optimized.The
handleSelectAllfunction properly toggles selection for the current page and maintains theSelectedTestCaseRowstructure. However, there are minor optimization opportunities:
- Lines 107-109: Creates
currentPageTestCaseIdsarray, then checks each ID with.includes()(O(n²) in worst case)- Lines 116-118: Filters and maps separately (two iterations)
Consider this optimization:
const handleSelectAll = () => { - const currentPageTestCaseIds = currentData.map(({ id }) => id); - const isAllCurrentPageSelected = currentPageTestCaseIds.every((testCaseId) => - selectedRowIds.includes(testCaseId), - ); + const selectedIdSet = new Set(selectedRowIds); + const isAllCurrentPageSelected = currentData.every(({ id }) => selectedIdSet.has(id)); const newSelectedRows = isAllCurrentPageSelected - ? selectedRows.filter((row) => !currentPageTestCaseIds.includes(row.id)) + ? selectedRows.filter((row) => !currentData.some(tc => tc.id === row.id)) : [ ...selectedRows, - ...currentData - .filter((testCase) => !selectedRowIds.includes(testCase.id)) - .map((testCase) => ({ id: testCase.id, folderId: testCase.testFolder.id })), + ...currentData + .filter((testCase) => !selectedIdSet.has(testCase.id)) + .map((testCase) => ({ id: testCase.id, folderId: testCase.testFolder.id })), ];
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
app/src/common/urls.js(1 hunks)app/src/controllers/testCase/index.ts(1 hunks)app/src/controllers/testCase/selectors.ts(2 hunks)app/src/pages/inside/common/testCaseList/testCaseList.tsx(6 hunks)app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/allTestCasesPage.tsx(7 hunks)app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/batchDeleteTestCasesModal.scss(1 hunks)app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/batchDeleteTestCasesModal.tsx(1 hunks)app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/index.ts(1 hunks)app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/messages.ts(1 hunks)app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/useBatchDeleteTestCases.ts(1 hunks)app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/useBatchDeleteTestCasesModal.tsx(1 hunks)app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDuplicateToFolderModal/useBatchDuplicateToFolder.ts(4 hunks)app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/changePriorityModal.tsx(1 hunks)app/src/pages/inside/testCaseLibraryPage/hooks/useRefetchCurrentTestCases.ts(1 hunks)app/src/pages/inside/testCaseLibraryPage/testCaseDetailsPage/editTestCaseModal/useUpdateTestCase.ts(3 hunks)app/src/pages/inside/testCaseLibraryPage/testCaseFolders/modals/duplicateFolderModal/useDuplicateFolder.ts(1 hunks)app/src/pages/inside/testPlansPage/testPlanDetailsPage/testPlanFolders/allTestCasesPage/allTestCasesPage.tsx(3 hunks)
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: yelyzavetakhokhlova
Repo: reportportal/service-ui PR: 4615
File: app/src/pages/inside/testCaseLibraryPage/createTestCaseModal/useCreateTestCase.ts:22-23
Timestamp: 2025-09-22T11:43:56.813Z
Learning: In the test case creation modal (app/src/pages/inside/testCaseLibraryPage/createTestCaseModal/), testFolderId is intentionally hardcoded to 85 as a temporary measure until folder selection functionality is implemented.
📚 Learning: 2025-09-22T11:43:56.813Z
Learnt from: yelyzavetakhokhlova
Repo: reportportal/service-ui PR: 4615
File: app/src/pages/inside/testCaseLibraryPage/createTestCaseModal/useCreateTestCase.ts:22-23
Timestamp: 2025-09-22T11:43:56.813Z
Learning: In the test case creation modal (app/src/pages/inside/testCaseLibraryPage/createTestCaseModal/), testFolderId is intentionally hardcoded to 85 as a temporary measure until folder selection functionality is implemented.
Applied to files:
app/src/controllers/testCase/index.tsapp/src/pages/inside/testCaseLibraryPage/testCaseFolders/modals/duplicateFolderModal/useDuplicateFolder.tsapp/src/pages/inside/testCaseLibraryPage/hooks/useRefetchCurrentTestCases.tsapp/src/pages/inside/common/testCaseList/testCaseList.tsxapp/src/pages/inside/testCaseLibraryPage/allTestCasesPage/changePriorityModal.tsxapp/src/pages/inside/testCaseLibraryPage/allTestCasesPage/allTestCasesPage.tsxapp/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDuplicateToFolderModal/useBatchDuplicateToFolder.tsapp/src/pages/inside/testCaseLibraryPage/testCaseDetailsPage/editTestCaseModal/useUpdateTestCase.tsapp/src/pages/inside/testPlansPage/testPlanDetailsPage/testPlanFolders/allTestCasesPage/allTestCasesPage.tsx
📚 Learning: 2025-07-24T11:26:08.671Z
Learnt from: allaprischepa
Repo: reportportal/service-ui PR: 4487
File: app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.jsx:105-105
Timestamp: 2025-07-24T11:26:08.671Z
Learning: In the ReportPortal service-ui project, the react-hooks/exhaustive-deps ESLint rule is strictly enforced, requiring ALL dependencies used within useEffect, useMemo, and useCallback hooks to be included in the dependency array, even stable references like formatMessage from useIntl(). This prevents ESLint errors and maintains code consistency.
Applied to files:
app/src/pages/inside/common/testCaseList/testCaseList.tsxapp/src/pages/inside/testCaseLibraryPage/allTestCasesPage/allTestCasesPage.tsx
📚 Learning: 2025-08-14T12:06:14.147Z
Learnt from: allaprischepa
Repo: reportportal/service-ui PR: 4543
File: app/src/pages/inside/projectSettingsPageContainer/content/analyzerContainer/autoAnalysis/autoAnalysis.jsx:17-17
Timestamp: 2025-08-14T12:06:14.147Z
Learning: In the reportportal/service-ui project, webpack uses ProvidePlugin to automatically provide React as a global variable, so explicit `import React from 'react'` is not needed in .jsx files for JSX to work. The project uses classic JSX runtime with automatic React injection via webpack.
Applied to files:
app/src/pages/inside/common/testCaseList/testCaseList.tsxapp/src/pages/inside/testCaseLibraryPage/allTestCasesPage/allTestCasesPage.tsx
📚 Learning: 2025-06-09T17:12:07.281Z
Learnt from: Guria
Repo: reportportal/service-ui PR: 4385
File: app/src/components/testCaseList/testCaseNameCell/testCaseNameCell.tsx:30-42
Timestamp: 2025-06-09T17:12:07.281Z
Learning: In React components, static objects like icon mappings that don't depend on props or state should be defined outside the component function to avoid unnecessary re-creation on every render, improving performance.
Applied to files:
app/src/pages/inside/common/testCaseList/testCaseList.tsxapp/src/pages/inside/testCaseLibraryPage/allTestCasesPage/allTestCasesPage.tsx
📚 Learning: 2025-06-18T14:36:19.317Z
Learnt from: yelyzavetakhokhlova
Repo: reportportal/service-ui PR: 4416
File: app/src/pages/inside/testCaseLibraryPage/createTestCaseModal/testCaseDetails/template/template.tsx:75-86
Timestamp: 2025-06-18T14:36:19.317Z
Learning: In ReportPortal TypeScript components, noop handlers are sometimes used to satisfy TypeScript requirements for required props, not because functionality is missing. This is a common pattern when the actual form handling is managed by wrapper components.
Applied to files:
app/src/pages/inside/common/testCaseList/testCaseList.tsxapp/src/pages/inside/testCaseLibraryPage/allTestCasesPage/allTestCasesPage.tsx
📚 Learning: 2025-09-04T09:25:36.100Z
Learnt from: allaprischepa
Repo: reportportal/service-ui PR: 4589
File: app/src/pages/inside/common/modals/renameOrganizationModal/renameOrganizationModal.tsx:90-95
Timestamp: 2025-09-04T09:25:36.100Z
Learning: In the ReportPortal service-ui project, it's a common approach to use the `dirty` prop from redux-form to control modal closing behavior - specifically to prohibit modal closing when changes were made in the form. This pattern should be maintained for consistency across the project.
Applied to files:
app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/changePriorityModal.tsx
📚 Learning: 2025-06-26T13:05:48.078Z
Learnt from: allaprischepa
Repo: reportportal/service-ui PR: 4434
File: app/src/controllers/organization/projects/sagas.js:141-172
Timestamp: 2025-06-26T13:05:48.078Z
Learning: In the ReportPortal service-ui codebase, the JSON Patch operations for project renaming use a custom path format where the `path` property is specified as 'name' (without leading forward slash), which differs from RFC 6902 standard but matches their API implementation requirements.
Applied to files:
app/src/common/urls.js
🧬 Code graph analysis (8)
app/src/pages/inside/testCaseLibraryPage/hooks/useRefetchCurrentTestCases.ts (4)
app/src/controllers/pages/typed-selectors.ts (1)
urlFolderIdSelector(55-63)app/src/controllers/testCase/selectors.ts (1)
testCasesPageSelector(75-76)app/src/pages/inside/testCaseLibraryPage/utils.ts (1)
getTestCaseRequestParams(40-57)app/src/controllers/testCase/index.ts (2)
getTestCaseByFolderIdAction(20-20)getAllTestCasesAction(19-19)
app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/useBatchDeleteTestCasesModal.tsx (2)
app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/index.ts (2)
useBatchDeleteTestCasesModal(17-17)BATCH_DELETE_TEST_CASES_MODAL_KEY(18-18)app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/batchDeleteTestCasesModal.tsx (2)
BatchDeleteTestCasesModalData(38-42)BATCH_DELETE_TEST_CASES_MODAL_KEY(36-36)
app/src/pages/inside/common/testCaseList/testCaseList.tsx (1)
app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/allTestCasesPage.tsx (1)
SelectedTestCaseRow(67-70)
app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/useBatchDeleteTestCases.ts (3)
app/src/pages/inside/testCaseLibraryPage/hooks/useRefetchCurrentTestCases.ts (1)
useRefetchCurrentTestCases(28-47)app/src/common/utils/fetch.ts (1)
fetch(61-92)app/src/controllers/testCase/index.ts (1)
updateFolderCounterAction(21-21)
app/src/controllers/testCase/selectors.ts (1)
app/src/pages/inside/testCaseLibraryPage/types.ts (1)
TestCase(76-94)
app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDuplicateToFolderModal/useBatchDuplicateToFolder.ts (1)
app/src/pages/inside/testCaseLibraryPage/hooks/useRefetchCurrentTestCases.ts (1)
useRefetchCurrentTestCases(28-47)
app/src/pages/inside/testCaseLibraryPage/testCaseDetailsPage/editTestCaseModal/useUpdateTestCase.ts (1)
app/src/pages/inside/testCaseLibraryPage/hooks/useRefetchCurrentTestCases.ts (1)
useRefetchCurrentTestCases(28-47)
app/src/pages/inside/testPlansPage/testPlanDetailsPage/testPlanFolders/allTestCasesPage/allTestCasesPage.tsx (1)
app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/allTestCasesPage.tsx (1)
SelectedTestCaseRow(67-70)
🪛 GitHub Check: SonarCloud Code Analysis
app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/batchDeleteTestCasesModal.tsx
[warning] 96-96: Move this component definition out of the parent component and pass data as props.
🔇 Additional comments (17)
app/src/controllers/testCase/index.ts (1)
21-21: Export verified and properly defined.The
updateFolderCounterActionis correctly defined inactionCreators.ts(line 186) and properly exported inindex.ts(line 21). The export statement is syntactically correct and aligns with the PR objective to implement batch test case removal with folder counter updates. No issues found.app/src/controllers/testCase/selectors.ts (1)
28-40: Stronger typing oftestCases.listtoTestCase[]looks goodAligning
testCases.listwith theTestCaseinterface improves type safety and eliminates the need for casts at call sites. This should make downstream selector/consumer code clearer without affecting runtime behavior.app/src/pages/inside/testCaseLibraryPage/testCaseFolders/modals/duplicateFolderModal/useDuplicateFolder.ts (1)
51-52: LGTM!The formatting change (added empty line) improves readability with no functional impact.
app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/batchDeleteTestCasesModal.scss (1)
17-23: LGTM!The SCSS styling for bold text emphasis is properly structured and aligns with the modal's UI requirements.
app/src/pages/inside/testCaseLibraryPage/hooks/useRefetchCurrentTestCases.ts (1)
28-47: LGTM! Effective refactoring that centralizes refetch logic.The hook properly consolidates test case refetch logic that was previously duplicated across multiple modal hooks. The conditional dispatching based on
urlFolderIdand the dependency array are correct.app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDuplicateToFolderModal/useBatchDuplicateToFolder.ts (1)
28-28: LGTM! Clean refactoring using the new centralized hook.The refactoring successfully replaces the conditional dispatch logic with the
useRefetchCurrentTestCaseshook, reducing code duplication and improving maintainability.Also applies to: 44-44, 71-73, 91-91
app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/useBatchDeleteTestCasesModal.tsx (1)
24-28: LGTM!The modal hook follows the standard pattern and is properly typed with
BatchDeleteTestCasesModalData.app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/index.ts (1)
17-18: LGTM!The barrel export file properly consolidates the batch delete modal exports.
app/src/common/urls.js (1)
379-379: All references to the oldbulkUpdateTestCasesURL generator have been successfully updated. ThetestCasesBatchURL generator is correctly used inuseUpdateTestCase.ts(line 78), and all call sites inchangePriorityModal.tsxare using the updated function properly. No orphaned references remain.app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/changePriorityModal.tsx (1)
47-53: Based on my verification of the codebase, I can now provide the rewritten review comment.The payload structure is correctly implemented and intentionally designed — no backend compatibility issues identified.
The
BulkUpdateTestCasesPayloadtype inuseUpdateTestCase.tsexplicitly definestestCaseIdsandpriorityas the only fields. The removal offolderIdfrom bulk operations appears to be intentional architecture: single test case updates (UpdateTestCasePayload) includetestFolderId, while bulk updates deliberately exclude it. The payload sent fromchangePriorityModal.tsxmatches the type definition exactly, and there are no type errors or compile-time issues. Since this is the current type definition in the codebase and the code compiles successfully, the backend integration for this simplified payload structure is already in place.app/src/pages/inside/testPlansPage/testPlanDetailsPage/testPlanFolders/allTestCasesPage/allTestCasesPage.tsx (1)
53-66: Selection state maintained despiteselectable={false}.The component maintains
selectedRowsstate and passes selection props toTestCaseList, but Line 68 setsselectable={false}. Verify this is intentional—if selection UI is hidden but state tracking is needed for other purposes, consider adding a comment explaining why.app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/allTestCasesPage.tsx (2)
67-70: LGTM! Well-structured interface for richer selection tracking.The
SelectedTestCaseRowinterface correctly captures bothidandfolderId, enabling accurate folder counter updates during batch operations. This is a solid foundation for the batch delete feature.
86-91: Good optimization withuseMemofor derived selection IDs.The derived
selectedRowIdsis correctly memoized to prevent unnecessary re-renders when passing to child components.app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/batchDeleteTestCasesModal.tsx (1)
46-60: Modal integration and loading state management look solid.The hook integration properly handles success callbacks, modal dismissal, and selection clearing. The loading overlay is correctly positioned to block interaction during the operation.
app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/messages.ts (1)
19-29: LGTM! Well-structured localization messages.The message definitions follow react-intl best practices with proper pluralization and semantic formatting. The bold emphasis appropriately highlights the count and irreversible nature of the action.
app/src/pages/inside/testCaseLibraryPage/testCaseDetailsPage/editTestCaseModal/useUpdateTestCase.ts (2)
25-25: Excellent refactoring to use centralized refetch logic.Replacing manual action dispatching with the
useRefetchCurrentTestCaseshook reduces code duplication and ensures consistent refetch behavior across the application. This aligns well with the DRY principle.Also applies to: 42-42, 85-85
27-36: Payload type updates align with API evolution.The changes from
folderId?: stringtotestFolderId: numberand removal offolderIdfrom bulk updates reflect a cleaner, more type-safe API contract. The requiredtestFolderIdensures the field is always present when needed.
app/src/pages/inside/testCaseLibraryPage/allTestCasesPage/allTestCasesPage.tsx
Show resolved
Hide resolved
...testCaseLibraryPage/allTestCasesPage/batchDeleteTestCasesModal/batchDeleteTestCasesModal.tsx
Show resolved
Hide resolved
31469b4 to
328d946
Compare
|



PR Checklist
developfor features/bugfixes, other if mentioned in the task)npm run lint) prior to submission? Enable the git hook on commit in your IDE to run it and format the code automatically.npm run build)?devnpm script)manage:translationsscript?Visuals
Screen.Recording.2025-11-26.at.20.40.39.mov
Summary by CodeRabbit
New Features
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.