Skip to content

Staging #915

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

Open
wants to merge 121 commits into
base: main
Choose a base branch
from
Open

Staging #915

wants to merge 121 commits into from

Conversation

Anchel123
Copy link
Contributor

@Anchel123 Anchel123 commented Apr 28, 2025

Summary by CodeRabbit

  • New Features

    • Enabled role-based query execution distinguishing read-only users.
    • Added graph and schema duplication via PATCH API routes.
    • Introduced paginated, searchable lists for labels and query history.
    • Added comprehensive attribute CRUD table with async API integration.
    • Implemented multi-selection and bulk deletion of graph and schema elements.
    • Added graph details component showing node, edge, and graph name summaries.
    • Introduced select graph component with management dialogs for delete, export, duplicate.
    • Added controls for animation toggle, zoom, and fit-to-view in graph UI.
    • Implemented label management dialogs with add and remove label components.
    • Added a generic pagination list component with debounce search and navigation.
    • Enhanced schema create element UI with improved label management and keyboard support.
    • Added a new graph selector with role-based management and pagination.
    • Introduced a new controls component for graph interaction (animation, zoom, center).
    • Added a new data panel for attribute editing with API calls and undo support.
    • Added a new graph data table component for attribute CRUD operations.
    • Implemented a new schema view with externalized state and simplified layout.
    • Added a tutorial panel component for onboarding automation with UI tests.
    • Enhanced login verification with localStorage synchronization for timeout and limit.
    • Added a new graph toolbar with search-driven controls and keyboard navigation.
    • Added a new table view tab with tests for data presence and row count.
    • Added a new graph details summary component.
    • Added a new remove label component with confirmation dialog.
    • Added a new add label component with dialog and loading state.
    • Added a new select graph dropdown with management dialogs.
    • Added a new page object model class for generic graph/schema UI elements.
    • Added new Playwright tests for tutorial, table view, and data panel UI.
    • Added utility functions for waiting on element visibility and label inference.
    • Added security headers configuration for all routes.
  • Improvements

    • Refactored layouts for header, data panels, graph and schema views to use context and externalized state.
    • Centralized state management via React contexts for graphs, schemas, and indicator.
    • Improved role-based access control enforcement in API and UI.
    • Standardized test IDs across components for improved test reliability.
    • Added loading indicators and disabled states for async actions and inputs.
    • Unified page object models with flexible, parameterized locator getters.
    • Enhanced zoom-to-fit with adjustable padding multiplier.
    • Simplified UI for schema and graph management, removing redundant controls.
    • Improved label components with strong typing, test IDs, and simplified scroll behavior.
    • Streamlined data panels by delegating attribute and label management to dedicated components.
    • Enhanced pagination list with debounce search, dynamic page ranges, and loading states.
    • Improved form field types using discriminated unions for safety and consistency.
    • Standardized dialogs with test IDs and default triggers for better accessibility.
    • Updated header layout to vertical stack with improved navigation and help menu.
    • Improved selector component with integrated query history tabs and editor maximize.
    • Refined graph duplication UI with input validation and updated API usage.
    • Improved test code with consistent method calls, updated imports, and disabled flaky tests.
    • Added security headers to all routes for enhanced protection.
    • Refined toast and tooltip components with added icons and styling.
    • Improved editor component with simplified dialog control and theme management.
    • Enhanced query settings UI with info tooltips and localStorage sync.
    • Enhanced toolbar with search-driven controls and keyboard navigation.
    • Improved pagination and filtering in table and schema views.
    • Refactored Playwright tests and page objects for reliability and maintainability.
  • Bug Fixes

    • Fixed UI inconsistencies and error handling in dialogs and data panels.
    • Corrected localStorage synchronization for query limits and timeouts.
    • Fixed flaky test selectors and interactions in multiple test suites.
    • Resolved deletion flow issues updating categories and labels correctly.
    • Addressed read-only user query execution with proper method usage.
    • Fixed visibility and interaction bugs in canvas and schema models.
    • Fixed export and duplication API calls to use correct HTTP methods and endpoints.
  • Documentation

    • Enhanced tooltip descriptions and user guidance with added info icons.
  • Tests

    • Added tests for tutorial onboarding, data panel attribute editing, table view tab behavior, and query settings.
    • Refactored page object models for graph and schema pages, tutorial panel, data panel, and table view.
    • Disabled flaky tests and updated method calls for consistency.
    • Added tests for graph selection, export, and deletion flows.
  • Chores

    • Upgraded package version to 2.0.0 and updated Playwright dependency.
    • Added HTTP security headers configuration to Next.js routes.

Anchel123 and others added 23 commits April 23, 2025 11:01
…and improve input handling for infinite values. Adjust button click behavior to prevent negative values.
… persistence for timeout and limit values. Initialize state from localStorage in LoginVerification and update localStorage on timeout and limit adjustments in QuerySettings.
…QuerySettings component. Add methods for decreasing limits and timeouts, and update tests to validate behavior for negative values and button interactions.
Fix #887 Implement role-based query handling for Read-Only users across graph and schema API routes
Fix #898 Enhance QuerySettings component with tooltips for Timeout and Limit
Copy link

vercel bot commented Apr 28, 2025

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

Name Status Preview Comments Updated (UTC)
falkordb-browser ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 27, 2025 10:28am

@Anchel123 Anchel123 requested a review from barakb April 28, 2025 13:26
Copy link
Contributor

coderabbitai bot commented Apr 28, 2025

Walkthrough

This update refactors the application's architecture, UI, API routes, and testing framework. Major changes include context-based state management for graphs and schemas, role-based query execution, UI component redesigns, enhanced test automation with page objects, and security headers. It also updates package versions and introduces new features like multi-selection and batch deletion.

Changes

File(s)/Path(s) Change Summary
API Routes (Graph & Schema) All relevant API endpoints now extract the user object from the session and conditionally use graph.roQuery for "Read-Only" users, otherwise graph.query. New PATCH handlers for duplication and renaming are added.
app/api/graph/model.ts Refactored Node, Category, and Graph types; removed displayName; introduced generic categories, new query types, and enhanced element/category removal logic.
app/components/graph/DuplicateGraph.tsx Renamed component, improved validation, updated API endpoint/method, and added test IDs.
app/components/graph/DeleteGraph.tsx Refactored props, integrated context for graph names, improved deletion logic, enhanced feedback, and added test IDs.
app/components/ForceGraph.tsx Removed onExpand prop, added setters for add modes, adjusted force simulation, and enhanced deletion logic.
app/components/DialogComponent.tsx Dialog descriptions now appear in tooltips for better accessibility.
app/components/EditorComponent.tsx Always renders inline editor, simplifies maximize dialog, updates prop signatures, and enhances testability.
app/components/ExportGraph.tsx Internalizes trigger button, adds test IDs, and standardizes export dialog.
app/components/FormComponent.tsx Refactored Field type to a discriminated union for better type safety and clarity.
app/components/Header.tsx Redesigned as a vertical, context-aware layout; improved navigation, help, and accessibility.
app/components/TableComponent.tsx Replaced options with a scoped label prop, added test IDs, improved edit UX, and refined cell type handling.
app/components/provider.ts Introduced multiple new React contexts for graph/schema state management.
app/components/ui/Button.tsx Added "Delete" variant with new styling and tooltip logic.
app/components/ui/combobox.tsx Unified behavior around type prop, improved role-based UI, added loading state, and enhanced testability.
app/graph/GraphDataPanel.tsx Refactored props, removed onExpand, added setCategories, and improved label/attribute management.
app/graph/GraphDetails.tsx New component displaying graph summary (nodes, edges, name) with tooltips.
app/graph/GraphView.tsx Refactored to use externalized state and props, simplified UI to tab-based layout, and improved controls.
app/graph/MetadataView.tsx Split into Profile, Metadata, and Explain components for modularity.
app/graph/Selector.tsx Simplified props/state, added query history pagination/tabs, improved query editing, and refactored UI.
app/graph/TableView.tsx Now consumes data from context, not props; updated for new TableComponent API.
app/graph/View.tsx Now uses context for graph state instead of props.
app/graph/controls.tsx New Controls component for animation, zoom, and centering.
app/graph/labels.tsx Made generic, added test IDs, simplified layout, and improved re-render logic.
app/graph/page.tsx Switched to context-based state, added multi-selection, batch deletion, cooldown handling, and improved run/delete logic.
app/graph/toolbar.tsx Refactored to support search/filter, multi-selection, and keyboard navigation; improved add/delete logic.
app/globals.css Updated color variables and class styles for tabs, page, and data panel.
app/layout.tsx Changed body to horizontal flex layout and reordered providers.
app/login/LoginForm.tsx Typed fields array, made input change handlers async.
app/login/page.tsx Updated root div class for layout consistency.
app/loginVerification.tsx Added effect to initialize timeout and limit from localStorage.
app/page.tsx Added full width class to root container.
app/providers.tsx Added context providers for graph/schema state and conditional header rendering.
app/schema/SchemaCreateElement.tsx Replaced onExpand with setIsAdd, improved label UI, and refactored close logic.
app/schema/SchemaDataPanel.tsx Refactored props, improved label/attribute logic, and updated UI for label management.
app/schema/SchemaView.tsx Fully externalized state/handlers, simplified layout, and improved controls.
app/schema/page.tsx Switched to context-based state, added multi-selection, batch deletion, cooldown handling, and improved run/delete logic.
app/settings/Configurations.tsx Explicitly typed config rows, refined cell types, and added label prop for TableComponent.
app/settings/QuerySettings.tsx Enhanced tooltip UI, improved input parsing, and synced state with localStorage.
app/settings/page.tsx Removed header, adjusted padding and tab styling.
app/settings/users/AddUser.tsx Updated select field config for role selection.
app/settings/users/Users.tsx Renamed user state variables, refined role selection UI, and updated dialog references.
components/ui/toaster.tsx Added data-testid="toast" for testability.
components/ui/tooltip.tsx Updated tooltip content structure, added info icon, and improved styling.
e2e/infra/utils.ts Added new query constants, utility functions for element visibility, and label inference.
e2e/logic/POM/dataPanelComponent.ts Refactored to use test IDs for locators, improved method naming, and enhanced reliability.
e2e/logic/POM/graphPage.ts Refactored to extend new Page class, simplified locators, improved canvas interactions, and unified methods.
e2e/logic/POM/page.ts New base Page class with parameterized locator getters for UI elements.
e2e/logic/POM/queryHistoryComponent.ts Entire class commented out, disabling all functionality.
e2e/logic/POM/schemaPage.ts Refactored to extend Page, removed private locators, and unified interaction methods.
e2e/logic/POM/settingsQueryPage.ts Refined limit/timeout input methods, added new tests for boundary conditions.
e2e/logic/POM/tableView.ts New TableView class for table tab interactions and row counting.
e2e/logic/POM/tutorialPanelComponent.ts New TutotialPanel class for automating tutorial UI interactions.
e2e/tests/auth.setup.ts Skipped authentication setup for tests.
e2e/tests/canvas.spec.ts Changed loop from for...of to async forEach for test generation.
e2e/tests/dataPanel.spec.ts Replaced all previous tests with new admin-focused UI tests for data panel interactions.
e2e/tests/graph.spec.ts Refactored for new query constants, method names, and improved test clarity; commented out some tests.
e2e/tests/login.spec.ts Minor syntax fixes for async waits and parameter usage.
e2e/tests/queryHistory.spec.ts Updated method names for graph selection and canvas search.
e2e/tests/schema.spec.ts Replaced nodeClick with elementClick for schema interactions.
e2e/tests/settingsQuery.spec.ts Updated to use new input methods, added new tests for boundary conditions.
e2e/tests/table.spec.ts New test suite for table view tab, validating enablement, selection, and row count.
e2e/tests/tutorial.spec.ts New test suite for tutorial UI, covering add, dismiss, and persistence behaviors.
lib/utils.ts Added screen size constants, refactored Cell type to discriminated union, and enhanced handleZoomToFit.
next.config.js Added security headers for all routes.
package.json Bumped version to 2.0.0 and upgraded Playwright dev dependency.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant Context
    participant API
    participant DB

    User->>UI: Interacts (select, add, delete, edit)
    UI->>Context: Reads/Writes state (graph, schema, selection)
    UI->>API: Sends request (CRUD, run query, duplicate, etc.)
    API->>Context: Extracts user/session info
    API->>DB: Executes query (roQuery if Read-Only, else query)
    DB-->>API: Returns result
    API-->>UI: Responds with data/status
    UI->>Context: Updates state (elements, categories, labels)
    UI-->>User: Updates view, shows toast/feedback
Loading
sequenceDiagram
    participant Test
    participant POM
    participant UI
    participant API

    Test->>POM: Calls high-level method (e.g., addGraph, deleteNode)
    POM->>UI: Locates element via test ID, performs action
    UI->>API: Sends request if needed
    API-->>UI: Responds
    UI-->>POM: Updates DOM/state
    POM-->>Test: Returns result/assertion
Loading

Possibly related PRs

Suggested labels

Review effort 2/5

Poem

🐇
A hop, a leap, a bounding feat—
Contexts bloom where states now meet.
Graphs and schemas, side by side,
With multi-select, you can decide!
UI shines with test IDs,
And batch deletes are now a breeze.
Version two—let’s celebrate, please!

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm warn config production Use --omit=dev instead.
npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-05-27T10_28_24_824Z-debug-0.log

✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🔭 Outside diff range comments (2)
app/api/schema/[schema]/[node]/route.ts (1)

52-67: ⚠️ Potential issue

Consider handling read-only access differently in the DELETE handler

Similar to the POST handler, the DELETE handler conditionally uses graph.roQuery for read-only users, which is contradictory to deletion operations.

Read-only users should be explicitly prevented from performing delete operations. Consider modifying the code:

-const result = user.role === "Read-Only"
-    ? await graph.roQuery(query, { params: { nodeId } })
-    : await graph.query(query, { params: { nodeId } })
+if (user.role === "Read-Only") {
+    return NextResponse.json(
+        { error: "Operation not permitted for read-only users" },
+        { status: 403 }
+    )
+}
+const result = await graph.query(query, { params: { nodeId } })
app/api/schema/[schema]/route.ts (1)

66-70: ⚠️ Potential issue

Missing role-based access control in DELETE handler

Unlike the GET and POST handlers, the DELETE handler doesn't implement the role-based permission check. This could allow "Read-Only" users to delete schemas, which contradicts the purpose of having a read-only role.

-    const { client } = session
+    const { client, user } = session

     const { schema } = await params
     const schemaName = `${schema}_schema`

     try {
+        if (user.role === "Read-Only") {
+            throw new Error("Operation not permitted for read-only users")
+        }
         const graph = client.selectGraph(schemaName)
🧹 Nitpick comments (2)
next.config.js (1)

8-40: Strong security headers implementation.

Adding these security headers is an excellent security practice. The configuration includes essential protections like Content-Security-Policy, HSTS, X-Frame-Options, and others.

Consider expanding the Content-Security-Policy beyond just frame-ancestors to also control script, style, and other resource loading for comprehensive protection.

Example of a more comprehensive CSP:

-            value: "frame-ancestors 'none';"
+            value: "default-src 'self'; script-src 'self'; style-src 'self'; frame-ancestors 'none'; img-src 'self' data:; connect-src 'self';"
app/settings/QuerySettings.tsx (1)

18-31: Enhanced UI with informative tooltips.

Adding tooltips with explanatory text and documentation links improves the user experience by providing context for the settings.

One minor issue in the tooltip text:

-                                <p>Shows a `Timed Out` error if the query takes longer than the timeout in seconds.</p>
+                                <p>Shows a "Timed Out" error if the query takes longer than the timeout in seconds.</p>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77ae7af and 7b3666d.

📒 Files selected for processing (22)
  • app/api/graph/[graph]/[node]/[key]/route.ts (4 hunks)
  • app/api/graph/[graph]/[node]/label/route.ts (4 hunks)
  • app/api/graph/[graph]/[node]/route.ts (3 hunks)
  • app/api/graph/[graph]/count/route.ts (1 hunks)
  • app/api/graph/[graph]/duplicate/route.tsx (1 hunks)
  • app/api/graph/[graph]/route.ts (1 hunks)
  • app/api/graph/[graph]/suggestions/route.ts (2 hunks)
  • app/api/schema/[schema]/[node]/[key]/route.ts (4 hunks)
  • app/api/schema/[schema]/[node]/label/route.ts (3 hunks)
  • app/api/schema/[schema]/[node]/route.ts (4 hunks)
  • app/api/schema/[schema]/count/route.ts (1 hunks)
  • app/api/schema/[schema]/duplicate/route.tsx (1 hunks)
  • app/api/schema/[schema]/route.ts (4 hunks)
  • app/components/graph/DeleteGraph.tsx (1 hunks)
  • app/components/ui/combobox.tsx (1 hunks)
  • app/graph/Duplicate.tsx (1 hunks)
  • app/settings/QuerySettings.tsx (6 hunks)
  • e2e/logic/POM/graphPage.ts (2 hunks)
  • e2e/logic/POM/settingsQueryPage.ts (3 hunks)
  • e2e/tests/queryHistory.spec.ts (1 hunks)
  • e2e/tests/settingsQuery.spec.ts (2 hunks)
  • next.config.js (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (17)
app/api/schema/[schema]/count/route.ts (1)
app/api/auth/[...nextauth]/options.ts (1)
  • session (149-167)
app/api/graph/[graph]/[node]/label/route.ts (1)
app/api/auth/[...nextauth]/options.ts (1)
  • session (149-167)
app/api/graph/[graph]/route.ts (1)
app/api/auth/[...nextauth]/options.ts (1)
  • session (149-167)
app/api/schema/[schema]/[node]/[key]/route.ts (1)
app/api/auth/[...nextauth]/options.ts (1)
  • session (149-167)
app/api/graph/[graph]/duplicate/route.tsx (2)
app/api/schema/[schema]/duplicate/route.tsx (1)
  • PATCH (5-28)
app/api/auth/[...nextauth]/options.ts (2)
  • session (149-167)
  • getClient (171-200)
app/api/graph/[graph]/[node]/route.ts (1)
app/api/auth/[...nextauth]/options.ts (1)
  • session (149-167)
app/api/graph/[graph]/[node]/[key]/route.ts (1)
app/api/auth/[...nextauth]/options.ts (1)
  • session (149-167)
app/api/graph/[graph]/suggestions/route.ts (1)
app/api/auth/[...nextauth]/options.ts (1)
  • session (149-167)
e2e/logic/POM/graphPage.ts (1)
e2e/infra/utils.ts (1)
  • interactWhenVisible (118-122)
app/graph/Duplicate.tsx (1)
lib/utils.ts (2)
  • securedFetch (42-66)
  • prepareArg (68-70)
app/api/schema/[schema]/duplicate/route.tsx (3)
app/api/graph/[graph]/duplicate/route.tsx (1)
  • PATCH (5-26)
app/api/schema/[schema]/route.ts (1)
  • PATCH (83-109)
app/api/auth/[...nextauth]/options.ts (2)
  • session (149-167)
  • getClient (171-200)
app/api/graph/[graph]/count/route.ts (1)
app/api/auth/[...nextauth]/options.ts (1)
  • session (149-167)
app/api/schema/[schema]/[node]/route.ts (2)
app/api/auth/[...nextauth]/options.ts (1)
  • session (149-167)
app/api/schema/[schema]/[node]/utils.ts (1)
  • formatAttributes (10-10)
app/components/ui/combobox.tsx (1)
lib/utils.ts (2)
  • securedFetch (42-66)
  • prepareArg (68-70)
app/settings/QuerySettings.tsx (1)
lib/utils.ts (1)
  • cn (38-40)
e2e/logic/POM/settingsQueryPage.ts (1)
e2e/infra/utils.ts (1)
  • interactWhenVisible (118-122)
app/api/schema/[schema]/route.ts (4)
app/api/auth/[...nextauth]/options.ts (2)
  • session (149-167)
  • getClient (171-200)
app/api/schema/[schema]/duplicate/route.tsx (1)
  • PATCH (5-28)
app/api/schema/[schema]/[node]/[key]/route.ts (1)
  • PATCH (5-37)
app/api/graph/[graph]/route.ts (1)
  • PATCH (71-95)
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: build
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: build
🔇 Additional comments (48)
e2e/logic/POM/graphPage.ts (2)

13-19: Well-structured locator implementation for navigation buttons.

The new button locators follow the established pattern in this class, using XPath selectors to find elements by their text content.


269-275: Good implementation of navigation methods.

These new methods properly use the interactWhenVisible utility to ensure elements are visible before interaction, consistent with the class's existing patterns.

app/settings/QuerySettings.tsx (6)

2-5: Good import organization.

Added imports for required UI components and utility functions.


42-55: Improved input handling for timeout.

The input now correctly handles the infinity symbol and empty values. The use of the cn utility for class composition is also a good practice.


60-60: Prevents negative timeout values.

The updated onClick handler prevents the timeout from going below zero, which is a good validation.


67-80: Consistent UI pattern for limit settings.

The tooltip implementation for the limit setting is consistent with the timeout section, providing a good user experience.


91-104: Improved input handling for limit.

Similar to the timeout input, this correctly handles the infinity symbol and empty values.


109-109: Prevents negative limit values.

The updated onClick handler prevents the limit from going below zero, providing proper validation.

e2e/tests/queryHistory.spec.ts (1)

3-3: Import reordering.

Moving the BATCH_CREATE_PERSONS import is a minor change that doesn't affect functionality. This reordering may improve readability or follow a style convention.

app/api/graph/[graph]/count/route.ts (1)

12-12: Added role-based query execution - good security practice!

The changes correctly implement role-based access control by extracting the user's role from the session and conditionally using either the read-only query method or the standard query method. This is a good security enhancement that ensures users with "Read-Only" permissions can only perform non-mutating operations.

Also applies to: 18-20

app/api/graph/[graph]/route.ts (1)

52-52: Added role-based query execution to POST handler - good consistency

This change properly extends the role-based query execution pattern to the POST handler, maintaining consistency with other route handlers. The implementation correctly checks the user's role and uses the appropriate query method.

However, since this is a POST handler that might typically be used for creating resources, consider whether read-only users should be attempting this operation at all. While the current implementation safely uses roQuery for read-only users, you might want to return an error for read-only users attempting to access POST endpoints.

Also applies to: 58-60

app/api/graph/[graph]/suggestions/route.ts (1)

12-12: Implemented role-based query execution - good security practice

The changes correctly implement role-based access control by extracting the user's role from the session and conditionally using either the read-only query method or the standard query method based on that role. This is a good security enhancement that ensures users with "Read-Only" permissions can only perform non-mutating operations.

Also applies to: 35-37

app/api/schema/[schema]/count/route.ts (1)

12-12: Added role-based query execution - good security practice

The implementation correctly uses role-based access control by extracting the user's role from the session and conditionally using either the read-only query method or the standard query method. This ensures users with "Read-Only" permissions can only perform non-mutating operations, which is a good security practice.

Also applies to: 19-21

app/api/schema/[schema]/[node]/[key]/route.ts (4)

12-12: Role-based access control implementation looks good.

The extraction of both client and user from the session is correctly implemented.


26-28: Good implementation of role-based query execution.

This conditional logic correctly restricts users with "Read-Only" role to using the read-only query method (roQuery), preventing them from making mutations to the database. This is a good security practice.


46-46: Consistent extraction of user role.

The extraction of both client and user from the session is consistent with the PATCH handler above.


58-60: Well-implemented role-based query execution.

This conditional query execution correctly enforces read-only restrictions for users with the "Read-Only" role, consistent with the implementation in the PATCH handler.

app/api/graph/[graph]/[node]/[key]/route.ts (4)

11-11: Properly extracting user with role information.

The destructuring now correctly extracts both the client and user from the session.


27-29: Good implementation of role-based query execution.

This conditional logic properly handles parameterized queries based on user role. For "Read-Only" users, it correctly uses roQuery to prevent mutations.


46-46: Consistent extraction of user role.

The extraction of both client and user from the session is consistent with the POST handler implementation.


61-63: Well-implemented parameterized query with role-based access control.

The conditional execution based on user role is correctly implemented, ensuring that "Read-Only" users can't perform mutation operations while maintaining the proper parameterization.

app/api/schema/[schema]/[node]/label/route.ts (4)

11-11: Properly extracting user with role information.

The destructuring correctly extracts both the client and user from the session.


21-23: Good implementation of role-based query execution.

The conditional logic correctly determines which query method to use based on the user's role, enhancing security by preventing "Read-Only" users from making mutations.


41-41: Consistent extraction of user role.

The extraction of both client and user from the session is consistently implemented across handlers.


52-54: Well-implemented role-based query execution.

The conditional query execution correctly enforces read-only restrictions for users with the "Read-Only" role.

app/api/graph/[graph]/[node]/label/route.ts (4)

12-12: Properly extracting user with role information.

The destructuring now correctly extracts both the client and user from the session.


22-24: Good implementation of role-based query execution with parameters.

This conditional logic properly handles parameterized queries based on user role, ensuring "Read-Only" users can't perform mutations while maintaining the proper query parameters.


43-43: Consistent extraction of user role.

The extraction of both client and user from the session is consistent with the DELETE handler implementation.


53-55: Well-implemented parameterized query with role-based access control.

The conditional execution based on user role is correctly implemented, ensuring that "Read-Only" users can't perform mutation operations.

app/components/ui/combobox.tsx (2)

60-60: Update correctly handles dynamic endpoint selection

The API URL now dynamically selects the appropriate endpoint based on the type prop, enhancing reusability of the component.


60-65:

❌ Incorrect review comment

Missing request body in PATCH request

The PATCH request is missing a request body, which might be required by the backend API. Verify that the endpoint doesn't expect a body parameter.


🏁 Script executed:

#!/bin/bash
# Check if the API handlers for graph/schema rename expect a body
echo "Checking API handlers for graph and schema rename operations..."
fd -t f "route.ts" app/api/graph app/api/schema | xargs grep -l "PATCH" | xargs cat

Length of output: 13971


No request body needed for PATCH rename endpoints

The Graph and Schema PATCH handlers in app/api/graph/[graph]/route.ts and app/api/schema/[schema]/route.ts both read sourceName from request.nextUrl.searchParams and do not call request.json(). No JSON body is expected or used—your securedFetch call is correct as-is.

Likely an incorrect or invalid review comment.

app/components/graph/DeleteGraph.tsx (1)

42-42: Improved selection handling after deletion

The updated condition properly checks if the selected value is still present in the updated list of options before potentially changing it.

app/api/graph/[graph]/[node]/route.ts (1)

12-12: GET handler correctly implements role-based query execution

The GET handler properly extracts the user's role from the session and conditionally uses the appropriate query method based on the role.

Also applies to: 24-26

e2e/tests/settingsQuery.spec.ts (4)

27-27: Method name update looks good

The change from addTimeout to fillTimeout better reflects the action of setting a specific timeout value.


43-43: Method name update looks good

The change from addLimit to fillLimit better reflects the action of setting a specific limit value.


51-51: Good addition of settings navigation

Adding clickOnSettings() after verifying limited query results provides a cleaner test flow and properly closes the interaction cycle.


54-97: Well-structured boundary tests

These new tests comprehensively cover important edge cases for limit and timeout settings:

  • Negative value handling via direct input
  • Negative value handling via decrease buttons
  • Value increment behavior via increase buttons

The tests follow a consistent pattern and provide good coverage of UI interactions.

app/api/graph/[graph]/duplicate/route.tsx (1)

1-26: Well-implemented duplication endpoint

The new PATCH handler for graph duplication follows good practices:

  • Proper authentication via getClient()
  • Clear parameter extraction and validation
  • Good error handling with appropriate status codes
  • Consistent pattern with similar endpoints
app/graph/Duplicate.tsx (1)

28-30: API endpoint update looks good

The change to use PATCH endpoints for duplication is well-implemented:

  • Properly handles both graph and schema types
  • Uses URL encoding through prepareArg
  • Passes source name as a query parameter
  • Aligns with the new backend endpoints
app/api/schema/[schema]/duplicate/route.tsx (2)

1-28: Well-implemented schema duplication endpoint

The PATCH handler for schema duplication is consistently implemented:

  • Proper authentication via getClient()
  • Clear parameter extraction and validation
  • Appropriate schema name handling with "_schema" suffix
  • Good error handling with appropriate status codes

19-21: Consistent schema naming convention

Good implementation of the "_schema" suffix for both source and target schema names, maintaining consistency with the application's naming conventions.

app/api/schema/[schema]/route.ts (2)

11-11: Well-implemented role-based query execution

The GET handler now correctly extracts the user role from the session and conditionally executes a read-only query for users with the "Read-Only" role. This ensures proper access control at the query level.

Also applies to: 22-24


40-40: Consistent implementation of role-based access control

The POST handler follows the same pattern as the GET handler, ensuring read-only users can only execute non-mutating queries. This maintains a consistent security model throughout the API.

Also applies to: 46-48

e2e/logic/POM/settingsQueryPage.ts (5)

23-29: Well-structured locators for decrement buttons

These locators follow the same pattern as the existing increment button locators, making the code consistent and maintainable.


39-45: Good implementation of click methods for decrement buttons

These methods properly use the interactWhenVisible utility to ensure elements are visible before interaction, following the established pattern in the class.


55-61: Clear and concise fill methods

The fillLimit and fillTimeout methods provide a clean API for setting numeric values in the inputs, simplifying the test code.


63-77: Well-designed wrapper methods for interactions

These wrapper methods create a more semantic API for tests, making test code more readable by providing clear intent through method names rather than implementation details.


79-87: Useful getter methods for assertions

The getLimit and getTimeout methods will be valuable for assertions in tests, allowing verification of input values after interactions.


setOpen(false)
setOpenMenage(false)
handleSetRows(options.filter(opt => !opts.includes(opt)))
handleSetRows(options.filter(opt => newNames.includes(opt)))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential logic issue with filtered rows

The update changes the filtering logic to use options.filter(opt => newNames.includes(opt)) instead of filtering based on deleted items. This logic might be incorrect since options contains the original list of options before deletion.

Consider correcting the logic to use newNames directly instead of filtering the original options:

-handleSetRows(options.filter(opt => newNames.includes(opt)))
+handleSetRows(newNames)

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +16 to +19
try {
if (!sourceName) throw new Error("Missing parameter sourceName")

const result = await client.selectGraph(sourceName).copy(graphId)
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider adding validation for target graph name

The current implementation validates the source graph name but doesn't check if the target graph already exists. This could potentially lead to overwriting an existing graph.


🏁 Script executed:

#!/bin/bash
# Check if there's any validation in the client.selectGraph().copy() method
# that prevents overwriting existing graphs

# Find any graph copy implementation in the codebase
rg -A 5 -B 5 "\.copy\(" --type ts

Length of output: 1785


Add validation for target graph name to prevent overwriting

Ensure you validate the graphId parameter and check that a graph with this name doesn’t already exist before calling .copy().

• app/api/graph/[graph]/duplicate/route.tsx (around lines 16–19):
– Throw if graphId is missing
– Query the client to see if a graph with graphId already exists and error out if so

Example diff:

     try {
-        if (!sourceName) throw new Error("Missing parameter sourceName")
-
-        const result = await client.selectGraph(sourceName).copy(graphId)
+        if (!sourceName) throw new Error("Missing parameter sourceName")
+
+        const graphId = request.nextUrl.searchParams.get("graphId")
+        if (!graphId) throw new Error("Missing parameter graphId")
+
+        // Prevent overwriting an existing graph
+        const exists = await client
+          .selectGraph(graphId)
+          .get()
+          .then(() => true)
+          .catch(err => err.message.includes("NotFound") ? false : Promise.reject(err))
+        if (exists) throw new Error(`Graph "${graphId}" already exists`)
+
+        const result = await client.selectGraph(sourceName).copy(graphId)
📝 Committable suggestion

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

Suggested change
try {
if (!sourceName) throw new Error("Missing parameter sourceName")
const result = await client.selectGraph(sourceName).copy(graphId)
try {
if (!sourceName) throw new Error("Missing parameter sourceName")
const graphId = request.nextUrl.searchParams.get("graphId")
if (!graphId) throw new Error("Missing parameter graphId")
// Prevent overwriting an existing graph
const exists = await client
.selectGraph(graphId)
.get()
.then(() => true)
.catch(err => err.message.includes("NotFound") ? false : Promise.reject(err))
if (exists) throw new Error(`Graph "${graphId}" already exists`)
const result = await client.selectGraph(sourceName).copy(graphId)

Comment on lines +83 to 109
export async function PATCH(request: NextRequest, { params }: { params: Promise<{ schema: string }> }) {

const session = await getClient()
if (session instanceof NextResponse) {
return session
}

const { client } = session

const { schema } = await params
const schemaName = `${schema}_schema`
const source = request.nextUrl.searchParams.get("sourceName")

try {
if (!source) throw new Error("Missing parameter sourceName")

const sourceName = `${source}_schema`
const data = await (await client.connection).renameNX(sourceName, schemaName);

if (!data) throw new Error(`${schema} already exists`)

return NextResponse.json({ data })
} catch (err: unknown) {
console.error(err)
return NextResponse.json({ message: (err as Error).message }, { status: 400 })
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

PATCH handler needs role-based access control

The newly added PATCH handler for renaming schemas doesn't implement the role-based permission check that exists in the GET and POST handlers. "Read-Only" users should not be able to rename schemas.

-    const { client } = session
+    const { client, user } = session

     const { schema } = await params
     const schemaName = `${schema}_schema`
     const source = request.nextUrl.searchParams.get("sourceName")

     try {
+        if (user.role === "Read-Only") {
+            throw new Error("Operation not permitted for read-only users")
+        }
         if (!source) throw new Error("Missing parameter sourceName")

Additionally, the error message on line 102 should use schemaName instead of schema for consistency with how the code refers to the schema with its _schema suffix:

-        if (!data) throw new Error(`${schema} already exists`)
+        if (!data) throw new Error(`${schemaName} already exists`)
📝 Committable suggestion

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

Suggested change
export async function PATCH(request: NextRequest, { params }: { params: Promise<{ schema: string }> }) {
const session = await getClient()
if (session instanceof NextResponse) {
return session
}
const { client } = session
const { schema } = await params
const schemaName = `${schema}_schema`
const source = request.nextUrl.searchParams.get("sourceName")
try {
if (!source) throw new Error("Missing parameter sourceName")
const sourceName = `${source}_schema`
const data = await (await client.connection).renameNX(sourceName, schemaName);
if (!data) throw new Error(`${schema} already exists`)
return NextResponse.json({ data })
} catch (err: unknown) {
console.error(err)
return NextResponse.json({ message: (err as Error).message }, { status: 400 })
}
}
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ schema: string }> }
) {
const session = await getClient()
if (session instanceof NextResponse) {
return session
}
const { client, user } = session
const { schema } = await params
const schemaName = `${schema}_schema`
const source = request.nextUrl.searchParams.get("sourceName")
try {
if (user.role === "Read-Only") {
throw new Error("Operation not permitted for read-only users")
}
if (!source) throw new Error("Missing parameter sourceName")
const sourceName = `${source}_schema`
const data = await (await client.connection).renameNX(sourceName, schemaName)
if (!data) throw new Error(`${schemaName} already exists`)
return NextResponse.json({ data })
} catch (err: unknown) {
console.error(err)
return NextResponse.json(
{ message: (err as Error).message },
{ status: 400 }
)
}
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
app/graph/GraphDataPanel.tsx (2)

84-86: State updates should use immutable patterns

The code still directly spreads the graph.Categories array after modification, which doesn't follow React's immutable update pattern and was previously flagged in code reviews.

-            setCategories([...graph.Categories])
-            setLabel([...node.category])
+            setCategories(prevCategories => {
+                const updatedCategories = [...prevCategories]
+                const newCategoryIndex = graph.Categories.findIndex(c => c.name === newLabel)
+                if (newCategoryIndex !== -1) {
+                    updatedCategories.push({...graph.Categories[newCategoryIndex]})
+                }
+                return updatedCategories
+            })
+            setLabel(prevLabels => [...prevLabels, newLabel])

104-107: State updates should use immutable patterns when removing labels

Similar to the add label case, the remove label functionality should update state immutably rather than spreading arrays that were modified externally.

-            setCategories([...graph.Categories])
-            setLabel([...node.category])
+            setCategories(prevCategories => 
+                prevCategories.filter(c => c.name !== removeLabel)
+            )
+            setLabel(prevLabels => prevLabels.filter(l => l !== removeLabel))
app/graph/toolbar.tsx (2)

50-55: Fix potential null/undefined handling in search filter

The current implementation could crash if any value in el.data is null or undefined since toString() would be called on them.

While I see this was already identified in a previous review and marked as addressed, the fix doesn't appear in this version of the code. For safety, the filter should check for null/undefined values before calling toString().

const elements = graph.getElements().filter(el =>
-   Object.values(el.data).some(value => value.toString().toLowerCase().startsWith(searchElement.toLowerCase()))
+   Object.values(el.data).some(value => 
+     value !== null && value !== undefined && 
+     String(value).toLowerCase().startsWith(searchElement.toLowerCase())
+   )
    || el.id.toString().toLowerCase().includes(searchElement.toLowerCase())
    || el.label && (el as Link).label.toLowerCase().includes(searchElement.toLowerCase())
    || el.category && (el as Node).category.some(c => c.toLowerCase().includes(searchElement.toLowerCase()))
)

60-66: Reset suggestionIndex when suggestions change

If the previous index is larger than the new suggestions list's length, suggestions[suggestionIndex] could become undefined, breaking Enter and arrow-navigation functionality.

useEffect(() => {
-   const timeout = setTimeout(handleOnChange, 500)
+   const timeout = setTimeout(() => {
+       handleOnChange()
+       setSuggestionIndex(0)          // <- reset index when suggestions change
+   }, 500)

    return () => {
        clearTimeout(timeout)
    }
}, [graph, handleOnChange, searchElement])
🧹 Nitpick comments (4)
app/graph/GraphDataPanel.tsx (3)

115-159: Consider extracting view components to reduce complexity

The component has two entirely different rendering paths based on showAsDialog. This creates a large, complex component that's harder to test and maintain.

Consider extracting each view into its own component to improve readability and maintainability:

function GraphDataPanelDialog({ object, setObject, label, selectedLabel, setSelectedLabel, handleAddLabel, handleRemoveLabel, onDeleteElement }) {
  // Dialog implementation
}

function GraphDataPanelInline({ object, setObject, label, labelsListRef, labelsHover, setLabelsHover, handleAddLabel, handleRemoveLabel, onDeleteElement }) {
  // Inline implementation
}

// Main component just handles logic and conditionally renders the appropriate view

Also applies to: 160-236


44-48: Consider debouncing the dialog decision

The effect that determines whether to show labels as a dialog runs on every clientHeight change, which could cause flickering if the height is near the threshold.

Consider adding debounce logic or a small buffer around the threshold to prevent rapid toggling:

useEffect(() => {
  if (labelsListRef.current) {
    // Add a small buffer to prevent flickering
    const shouldShowDialog = labelsListRef.current.clientHeight > 85;
    if (shouldShowDialog !== showAsDialog) {
      setShowAsDialog(shouldShowDialog);
    }
  }
}, [labelsListRef.current?.clientHeight, showAsDialog]);

58-91: Extract API operations to a custom hook

The label management API operations could be extracted to a custom hook to improve reusability and separation of concerns.

Consider creating a custom hook like useGraphLabels that encapsulates the API logic:

function useGraphLabels(graph, object, toast, setIndicator, setCategories, setLabel) {
  const addLabel = async (newLabel) => {
    // Implementation of handleAddLabel
  };
  
  const removeLabel = async (removeLabel) => {
    // Implementation of handleRemoveLabel
  };
  
  return { addLabel, removeLabel };
}

// In component:
const { addLabel, removeLabel } = useGraphLabels(
  graph, 
  object, 
  toast, 
  setIndicator, 
  setCategories, 
  setLabel
);

Also applies to: 93-113

app/graph/toolbar.tsx (1)

68-73: Consider optimizing element zoom logic

The current implementation of handleSearchElement looks good, but the filter function passed to handleZoomToFit could be more efficient.

Consider memoizing the filter function or optimizing the condition for complex graphs:

const handleSearchElement = (element: Node | Link) => {
-   handleZoomToFit(chartRef, (node: Node) => element.category ? element.id === node.id : node.id === element.source.id || node.id === element.target.id)
+   // For nodes, zoom to the node itself; for links, zoom to both connected nodes
+   const zoomFilter = element.category 
+     ? (node: Node) => element.id === node.id
+     : (node: Node) => node.id === element.source.id || node.id === element.target.id
+   
+   handleZoomToFit(chartRef, zoomFilter)
    setSelectedElement(element)
    setSearchElement("")
    setSuggestions([])
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d8248b and 4cc43b9.

📒 Files selected for processing (7)
  • app/api/graph/model.ts (14 hunks)
  • app/graph/DeleteElement.tsx (2 hunks)
  • app/graph/GraphDataPanel.tsx (2 hunks)
  • app/graph/GraphDataTable.tsx (1 hunks)
  • app/graph/addLabel.tsx (1 hunks)
  • app/graph/toolbar.tsx (1 hunks)
  • app/schema/SchemaCreateElement.tsx (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/graph/addLabel.tsx
  • app/graph/DeleteElement.tsx
  • app/graph/GraphDataTable.tsx
  • app/api/graph/model.ts
  • app/schema/SchemaCreateElement.tsx
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Run settings tests
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run tests part 2 (except settings)
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run settings tests
  • GitHub Check: Run tests part 2 (except settings)
🔇 Additional comments (11)
app/graph/GraphDataPanel.tsx (6)

38-41: Good implementation of component state management

The component properly separates concerns with distinct state variables for UI behavior (showAsDialog, labelsHover) and data (selectedLabel, label). This aligns well with the PR's focus on improved state management.


29-30: Effective use of context for global state

Using GraphContext and IndicatorContext instead of prop drilling improves code maintainability and aligns with the architectural refactoring described in the PR objectives.


76-91: Good pattern for API error handling and feedback

The implementation properly handles API responses, provides user feedback via toast notifications, and updates the UI state accordingly. The function also correctly returns booleans to indicate success/failure to callers.


148-154: Effective use of component composition with GraphDataTable

Extracting attribute management into a dedicated GraphDataTable component is a good refactoring that improves separation of concerns and maintainability, aligning with the PR's architectural improvements.

Also applies to: 228-234


189-204: Well-implemented role-based UI permissions

The code correctly implements role-based UI permissions by conditionally rendering the remove label button based on the user's role. This is a good security practice that aligns with the PR's objectives.


176-178: Good use of data-testid attributes

Adding data-testid attributes throughout the component improves testability, which aligns with the PR objectives of enhanced testability via data-testid attributes.

app/graph/toolbar.tsx (5)

75-80: LGTM: Good keyboard navigation implementation

The scrollToSuggestion function provides proper keyboard navigation by automatically scrolling to the highlighted suggestion, which greatly improves accessibility.


83-200: Great search interface implementation

The implementation of the search input and suggestion list provides excellent user experience with:

  • Debounced search filtering
  • Keyboard navigation support
  • Accessible ARIA roles
  • Visual highlighting of selected items
  • Tooltips for additional information

206-215: LGTM: Good conditional rendering of delete button

The deletion UI is only shown when multiple elements are selected, which is a sensible design choice that prevents accidental deletions and keeps the UI clean when not needed.


217-260: Good implementation of Add Element dropdown

The dropdown menu for adding nodes and edges is well-implemented with:

  • Proper reset of selected element when changing modes
  • Clear test IDs for automated testing
  • Appropriate button labels and icons

268-272: Good use of defaultProps

Providing default values for optional props is a good practice that improves component reusability and prevents potential runtime errors.

Comment on lines +93 to +118
onKeyDown={(e) => {
if (e.key === 'Escape') {
e.preventDefault()
setSearchElement("")
}

if (e.key === 'Enter' && suggestions[suggestionIndex]) {
e.preventDefault()
handleSearchElement(suggestions[suggestionIndex])
setSearchElement("")
}

if (e.key === 'ArrowDown') {
e.preventDefault()
const index = suggestionIndex === suggestions.length - 1 ? 0 : suggestionIndex + 1
setSuggestionIndex(index)
scrollToSuggestion(index)
}

if (e.key === 'ArrowUp') {
e.preventDefault()
const index = suggestionIndex === 0 ? suggestions.length - 1 : suggestionIndex - 1
setSuggestionIndex(index)
scrollToSuggestion(index)
}
}}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Eliminate duplicated keyboard navigation logic

The same keyboard event handling code is duplicated for both the input element and the suggestion list. This creates maintenance issues and increases the risk of inconsistencies.

Extract the shared keyboard handling logic into a reusable function:

+ const handleKeyboardNavigation = (e: React.KeyboardEvent) => {
+     if (e.key === 'Escape') {
+         e.preventDefault()
+         setSearchElement("")
+     }
+
+     if (e.key === 'Enter' && suggestions[suggestionIndex]) {
+         e.preventDefault()
+         handleSearchElement(suggestions[suggestionIndex])
+         setSearchElement("")
+     }
+
+     if (e.key === 'ArrowDown') {
+         e.preventDefault()
+         const index = suggestionIndex === suggestions.length - 1 ? 0 : suggestionIndex + 1
+         setSuggestionIndex(index)
+         scrollToSuggestion(index)
+     }
+
+     if (e.key === 'ArrowUp') {
+         e.preventDefault()
+         const index = suggestionIndex === 0 ? suggestions.length - 1 : suggestionIndex - 1
+         setSuggestionIndex(index)
+         scrollToSuggestion(index)
+     }
+ }

// Then replace both event handlers with the shared function:
// In the Input component:
- onKeyDown={(e) => {
-     if (e.key === 'Escape') {
-         e.preventDefault()
-         setSearchElement("")
-     }
-
-     if (e.key === 'Enter' && suggestions[suggestionIndex]) {
-         e.preventDefault()
-         handleSearchElement(suggestions[suggestionIndex])
-         setSearchElement("")
-     }
-
-     if (e.key === 'ArrowDown') {
-         e.preventDefault()
-         const index = suggestionIndex === suggestions.length - 1 ? 0 : suggestionIndex + 1
-         setSuggestionIndex(index)
-         scrollToSuggestion(index)
-     }
-
-     if (e.key === 'ArrowUp') {
-         e.preventDefault()
-         const index = suggestionIndex === 0 ? suggestions.length - 1 : suggestionIndex - 1
-         setSuggestionIndex(index)
-         scrollToSuggestion(index)
-     }
- }}
+ onKeyDown={handleKeyboardNavigation}

// And in the suggestions list:
- onKeyDown={(e) => {
-     // Same duplicate code
- }}
+ onKeyDown={handleKeyboardNavigation}

Also applies to: 137-162

🤖 Prompt for AI Agents
In app/graph/toolbar.tsx around lines 93 to 118 and also 137 to 162, the
keyboard event handling logic for navigation and selection is duplicated for
both the input element and the suggestion list. To fix this, extract the shared
onKeyDown handler code into a single reusable function that takes the keyboard
event and handles Escape, Enter, ArrowDown, and ArrowUp keys consistently. Then
replace the duplicated inline handlers with calls to this new function to
centralize and simplify the logic.

Anchel123 and others added 2 commits May 22, 2025 11:10
…ceholder. Update usages in Configurations and Users components to pass entityName prop. Adjust DialogContent height in SelectGraph for better layout.
Fix #972 Enhance TableComponent to support dynamic entity names for search placeholder.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
app/graph/selectGraph.tsx (1)

46-52: PATCH request lacks a body but still declares Content-Type

Declaring Content-Type: application/json without a body violates the spec and may trigger CORS pre-flight failures in some browsers.
Either supply a JSON body (e.g. { newName: option }) or remove the header for this query-parameter-only call.

const handleSetOption = async (option: string, optionName: string) => {
    const result = await securedFetch(`api/${type === "Graph" ? "graph" : "schema"}/${prepareArg(option)}/?sourceName=${prepareArg(optionName)}`, {
        method: "PATCH",
-       headers: {
-           "Content-Type": "application/json"
-       },
    }, toast, setIndicator)
🧹 Nitpick comments (3)
app/graph/selectGraph.tsx (3)

1-2: Consider removing ESLint disable directive

The eslint-disable comment at the top of the file suppresses warnings about using variables before they're defined. Instead of disabling this rule, consider refactoring the code to maintain a logical flow where functions are defined before use.


67-69: Break down complex row mapping logic for better readability

The current implementation packs too much logic into a single line, making it difficult to read and maintain. Consider breaking this down into smaller, more focused functions.

const handleSetRows = (opts: string[]) => {
-   setRows(opts.map(opt => session?.user?.role === "Admin" ? ({ checked: false, name: opt, cells: [{ value: opt, onChange: (value: string) => handleSetOption(value, opt), type: "text" }] }) : ({ checked: false, name: opt, cells: [{ value: opt, type: "readonly" }] })))
+   setRows(opts.map(opt => {
+       const isAdmin = session?.user?.role === "Admin";
+       return {
+           checked: false,
+           name: opt,
+           cells: [
+               isAdmin 
+                   ? { value: opt, onChange: (value: string) => handleSetOption(value, opt), type: "text" } 
+                   : { value: opt, type: "readonly" }
+           ]
+       };
+   }));
}

99-105: Consider using a single chevron component with dynamic rotation

The current implementation uses two separate components (ChevronUp and ChevronDown) based on the open state. A more maintainable approach would be to use a single component with dynamic styling.

-{
-    open ?
-        <ChevronUp className="min-w-4 min-h-4" />
-        :
-        <ChevronDown className="min-w-4 min-h-4" />
-}
+<ChevronDown className={`min-w-4 min-h-4 transform transition-transform ${open ? 'rotate-180' : ''}`} />
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4cc43b9 and 67be6d6.

📒 Files selected for processing (4)
  • app/components/TableComponent.tsx (10 hunks)
  • app/graph/selectGraph.tsx (1 hunks)
  • app/settings/Configurations.tsx (2 hunks)
  • app/settings/users/Users.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/settings/users/Users.tsx
  • app/components/TableComponent.tsx
  • app/settings/Configurations.tsx
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run tests part 2 (except settings)
  • GitHub Check: Run tests part 2 (except settings)
  • GitHub Check: Run tests part 1 (except settings)
🔇 Additional comments (2)
app/graph/selectGraph.tsx (2)

116-116: Empty callback function may indicate missing implementation

The afterSearchCallback is set to an empty function. This might indicate an incomplete implementation or a missed requirement. Consider implementing proper functionality or documenting why this is intentionally empty.

-afterSearchCallback={() => { }}
+afterSearchCallback={(searchResult) => {
+   // Add appropriate implementation or
+   // TODO: Implement search result handling
+}}

147-172: Duplicate button may be enabled in inconsistent states

The DuplicateGraph component's disabled prop only checks if exactly one row is checked, but doesn't verify if the table is empty. This could lead to inconsistent UI states where the button appears enabled but doesn't have valid data to work with.

Consider enhancing the disabled condition to handle more edge cases:

<DuplicateGraph
    selectedValue={rows.filter(opt => opt.checked).map(opt => opt.cells[0].value as string)[0]}
    type={type}
    open={openDuplicate}
    onOpenChange={setOpenDuplicate}
    onDuplicate={(duplicateName) => {
        setSelectedValue(duplicateName)
        setOptions!([...options, duplicateName])
    }}
-   disabled={rows.filter(opt => opt.checked).length !== 1}
+   disabled={rows.length === 0 || rows.filter(opt => opt.checked).length !== 1}
/>

</DropdownMenu>
<DialogContent disableClose className="flex flex-col border-none rounded-lg max-w-none h-[90dvh]">
<DialogHeader className="flex-row justify-between items-center border-b border-secondary pb-4">
<DialogTitle className="text-2xl font-medium">Manage Graphs</DialogTitle>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Hardcoded dialog title should use the type prop

The dialog title is hardcoded as "Manage Graphs" when it should dynamically use the type prop to show either "Manage Graphs" or "Manage Schemas".

-<DialogTitle className="text-2xl font-medium">Manage Graphs</DialogTitle>
+<DialogTitle className="text-2xl font-medium">Manage {type}s</DialogTitle>
📝 Committable suggestion

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

Suggested change
<DialogTitle className="text-2xl font-medium">Manage Graphs</DialogTitle>
<DialogTitle className="text-2xl font-medium">Manage {type}s</DialogTitle>
🤖 Prompt for AI Agents
In app/graph/selectGraph.tsx at line 131, the DialogTitle is hardcoded as
"Manage Graphs" but should dynamically display the title based on the `type`
prop. Update the DialogTitle to conditionally render "Manage Graphs" or "Manage
Schemas" depending on the value of the `type` prop passed to the component.

Comment on lines +71 to +73
useEffect(() => {
handleSetRows(options)
}, [options])
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing dependencies in useEffect dependency array

The useEffect dependency array is missing handleSetRows which depends on session, handleSetOption, and potentially other variables. This could lead to stale closures and unexpected behavior.

useEffect(() => {
    handleSetRows(options)
-}, [options])
+}, [options, session, handleSetOption])
📝 Committable suggestion

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

Suggested change
useEffect(() => {
handleSetRows(options)
}, [options])
useEffect(() => {
handleSetRows(options)
}, [options, session, handleSetOption])
🤖 Prompt for AI Agents
In app/graph/selectGraph.tsx around lines 71 to 73, the useEffect hook's
dependency array is missing the handleSetRows function, which relies on session,
handleSetOption, and possibly other variables. To fix this, include
handleSetRows in the dependency array to ensure the effect runs correctly when
handleSetRows or its dependencies change, preventing stale closures and
unexpected behavior.

Anchel123 and others added 5 commits May 22, 2025 13:19
…current path. Add Escape key listener in Settings component to navigate back when pressed.
…. Update SelectGraph, Selector, and Toolbar components to use 'h-full' for improved layout and responsiveness.
…-second-click

Fix #953 Enhance Header component to toggle settings page navigation based on current path.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (5)
app/components/Header.tsx (3)

10-11: Importing the whole package.json bloats the client bundle & leaks internals

import pkg from '@/package.json' in a client component ships the entire package.json (including dependency list) to every visitor.
Consider creating a small file that only exports the version string.


37-44: Using Link for an external URL disables Next.js optimizations

next/link is designed for internal navigation; for external resources it adds unnecessary overhead.


31-31: ⚠️ Potential issue

showCreate can erroneously expose create-UI to unauthenticated users

When session is still loading (null/undefined), session?.user?.role !== "Read-Only" evaluates to true, and the create button becomes visible before authentication is complete.

-const showCreate = (pathname.includes("/graph") || pathname.includes("/schema")) && session?.user?.role !== "Read-Only"
+const showCreate = 
+  (pathname.includes("/graph") || pathname.includes("/schema")) && 
+  Boolean(session?.user?.role) && 
+  session.user.role !== "Read-Only"
app/api/graph/[graph]/count/route.ts (1)

24-24: Improve error handling with specific error details

The current error message is too generic, making debugging difficult.

- if (!result.ok) throw new Error("Something went wrong")
+ if (!result.ok) {
+   const errorText = await result.text().catch(() => "Could not read error response");
+   throw new Error(`Failed to fetch graph data: ${result.status} ${result.statusText}. ${errorText}`);
+ }
app/api/schema/[schema]/count/route.ts (1)

25-25: Improve error handling with more specific messaging

The current error message is too generic and doesn't provide useful debugging information.

-        if (!result.ok) throw new Error("Something went wrong")
+        if (!result.ok) {
+            const errorText = await result.text().catch(() => "Could not read error response");
+            throw new Error(`Failed to fetch schema data: ${result.status} ${result.statusText}. ${errorText}`);
+        }
🧹 Nitpick comments (1)
app/components/Header.tsx (1)

138-138: Remove unnecessary template literal and braces in version display

The version is displayed with superfluous braces that will show as "{2.0.0}" instead of just "2.0.0".

-<p>Version: {`{${pkg.version}}`}</p>
+<p>Version: {pkg.version}</p>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67be6d6 and 697ac4f.

📒 Files selected for processing (5)
  • app/api/graph/[graph]/count/route.ts (1 hunks)
  • app/api/schema/[schema]/count/route.ts (1 hunks)
  • app/components/EditorComponent.tsx (9 hunks)
  • app/components/Header.tsx (1 hunks)
  • app/settings/page.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/components/EditorComponent.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/settings/page.tsx (1)
lib/utils.ts (1)
  • cn (52-54)
🪛 GitHub Check: CodeQL
app/api/graph/[graph]/count/route.ts

[failure] 17-22: Server-side request forgery
The URL of this request depends on a user-provided value.

app/api/schema/[schema]/count/route.ts

[failure] 18-23: Server-side request forgery
The URL of this request depends on a user-provided value.

⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Run settings tests
  • GitHub Check: Run tests part 2 (except settings)
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run tests part 2 (except settings)
  • GitHub Check: Run settings tests
🔇 Additional comments (3)
app/components/Header.tsx (2)

26-32: 🛠️ Refactor suggestion

Props are now required but lack proper TypeScript validation

The component props have been changed from optional to required, but the interface definition doesn't reflect this properly.

interface Props {
-    onSetGraphName: (newGraphName: string) => void
-    graphNames: string[]
+    onSetGraphName: (newGraphName: string) => void;
+    graphNames: string[];
}

Likely an incorrect or invalid review comment.


86-86:

❓ Verification inconclusive

Verify if preventDefault() is necessary on the dropdown trigger

Using preventDefault() on the dropdown trigger might interfere with the normal behavior of the dropdown menu.


🏁 Script executed:

#!/bin/bash
# Check if the DropdownMenuTrigger has onClick handlers in other places
rg -A 2 "DropdownMenuTrigger" --type tsx --type jsx

Length of output: 79


Let’s search for all <DropdownMenuTrigger> usages and inspect the surrounding code in Header.tsx for context:


🏁 Script executed:

#!/bin/bash
# Search for all DropdownMenuTrigger occurrences in .tsx/.jsx files
rg -n "<DropdownMenuTrigger" -g "*.tsx" -g "*.jsx" -A 2 || true

# Show lines 60–120 of Header.tsx to get full context around the preventDefault call
if [ -f app/components/Header.tsx ]; then
  echo "== app/components/Header.tsx (lines 60–120) =="
  sed -n '60,120p' app/components/Header.tsx
else
  echo "app/components/Header.tsx not found"
fi

Length of output: 4720


Remove unnecessary preventDefault on DropdownMenuTrigger

The onClick={(e) => e.preventDefault()} on your Help menu trigger is not used in any other DropdownMenuTrigger (see selectGraph.tsx and toolbar.tsx) and may actually block Radix’s built-in toggle behavior. Unless you have a specific edge-case you’re preventing here, please remove this handler and verify that the Help dropdown still opens and closes as expected:

• Location: app/components/Header.tsx line 86
• Compare with usages in:
app/graph/selectGraph.tsx (no preventDefault)
app/graph/toolbar.tsx (no preventDefault)

app/settings/page.tsx (1)

53-53: Improved tab styling with active states

The tab styling has been updated to use foreground background for active tabs and gray text for inactive tabs, which improves visual distinction.

Also applies to: 55-55, 61-61, 67-67

Comment on lines 18 to 32
useEffect(() => {
window.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
router.back()
}
})

return () => {
window.removeEventListener("keydown", (e) => {
if (e.key === "Escape") {
router.back()
}
})
}
}, [router])
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix memory leak in event listener cleanup

The cleanup function creates a new function reference instead of removing the original listener, causing a memory leak.

useEffect(() => {
+    const handleEscapeKey = (e: KeyboardEvent) => {
+        if (e.key === "Escape") {
+            router.back()
+        }
+    }

-    window.addEventListener("keydown", (e) => {
-        if (e.key === "Escape") {
-            router.back()
-        }
-    })
+    window.addEventListener("keydown", handleEscapeKey)

    return () => {
-        window.removeEventListener("keydown", (e) => {
-            if (e.key === "Escape") {
-                router.back()
-            }
-        })
+        window.removeEventListener("keydown", handleEscapeKey)
    }
}, [router])
📝 Committable suggestion

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

Suggested change
useEffect(() => {
window.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
router.back()
}
})
return () => {
window.removeEventListener("keydown", (e) => {
if (e.key === "Escape") {
router.back()
}
})
}
}, [router])
useEffect(() => {
const handleEscapeKey = (e: KeyboardEvent) => {
if (e.key === "Escape") {
router.back()
}
}
window.addEventListener("keydown", handleEscapeKey)
return () => {
window.removeEventListener("keydown", handleEscapeKey)
}
}, [router])
🤖 Prompt for AI Agents
In app/settings/page.tsx around lines 18 to 32, the useEffect hook adds a
keydown event listener with an inline function but attempts to remove it with a
new inline function, which does not match the original reference and causes a
memory leak. To fix this, define the event handler function separately outside
the addEventListener and removeEventListener calls, then use that same function
reference in both to properly add and remove the listener.


const json = await result.json()

const data = typeof json.result === "number" ? json.result : { data: [json.result.data[0] || { edges: 0, nodes: 0 }] }
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix data transformation logic

The current data transformation is incorrect and inconsistent. The ternary operator creates different data structures based on the type of json.result, which can lead to unexpected behavior.

- const data = typeof json.result === "number" ? json.result : { data: [json.result.data[0] || { edges: 0, nodes: 0 }] }
+ let data;
+ if (typeof json.result === "number") {
+     data = { data: [{ edges: 0, nodes: json.result }] };
+ } else if (json.result?.data?.[0]) {
+     data = { data: [json.result.data[0]] };
+ } else {
+     data = { data: [{ edges: 0, nodes: 0 }] };
+ }

This ensures a consistent data structure regardless of the input format, and includes proper validation of nested properties.

📝 Committable suggestion

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

Suggested change
const data = typeof json.result === "number" ? json.result : { data: [json.result.data[0] || { edges: 0, nodes: 0 }] }
let data;
if (typeof json.result === "number") {
data = { data: [{ edges: 0, nodes: json.result }] };
} else if (json.result?.data?.[0]) {
data = { data: [json.result.data[0]] };
} else {
data = { data: [{ edges: 0, nodes: 0 }] };
}
🤖 Prompt for AI Agents
In app/api/graph/[graph]/count/route.ts at line 28, the data transformation uses
a ternary operator that results in inconsistent data structures depending on
whether json.result is a number or an object. To fix this, refactor the logic to
always produce a consistent data structure by validating nested properties
properly and ensuring the output format is uniform regardless of input type.

Comment on lines +17 to +22
const result = await fetch(`${request.nextUrl.origin}/api/graph/${graph}/?query=${encodeURIComponent(query)}`, {
method: "GET",
headers: {
cookie: request.headers.get('cookie') || '',
}
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Mitigate potential SSRF vulnerability by validating the graph parameter

The URL of the fetch request is constructed using a user-provided value (graph parameter). While this is an internal request within the same application, it's still important to validate this parameter to prevent path traversal or other injection attacks.

    const { graph } = await params
+   
+   // Validate graph parameter to prevent path traversal or injection
+   if (!graph || /[^a-zA-Z0-9_-]/.test(graph)) {
+       return NextResponse.json({ error: "Invalid graph name" }, { status: 400 })
+   }

    try {
📝 Committable suggestion

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

Suggested change
const result = await fetch(`${request.nextUrl.origin}/api/graph/${graph}/?query=${encodeURIComponent(query)}`, {
method: "GET",
headers: {
cookie: request.headers.get('cookie') || '',
}
})
const { graph } = await params
// Validate graph parameter to prevent path traversal or injection
if (!graph || /[^a-zA-Z0-9_-]/.test(graph)) {
return NextResponse.json({ error: "Invalid graph name" }, { status: 400 })
}
try {
const result = await fetch(
`${request.nextUrl.origin}/api/graph/${graph}/?query=${encodeURIComponent(query)}`,
{
method: "GET",
headers: {
cookie: request.headers.get('cookie') || '',
},
}
)
// …
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 17-22: Server-side request forgery
The URL of this request depends on a user-provided value.

🤖 Prompt for AI Agents
In app/api/graph/[graph]/count/route.ts around lines 17 to 22, the fetch URL
uses the user-provided graph parameter directly, which can lead to SSRF or path
traversal vulnerabilities. To fix this, validate the graph parameter against a
whitelist of allowed values or ensure it matches a strict pattern (e.g.,
alphanumeric and limited length) before using it in the URL. Reject or sanitize
any invalid input to prevent injection attacks.


const result = data.length === 0 ? { nodes: 0, edges: 0 } : data[0]
const data = typeof json.result === "number" ? json.result : { data: [json.result.data[0] || { nodes: 0, edges: 0 }] }
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix data transformation logic

The current data transformation is incorrect and inconsistent. The ternary operator creates different data structures based on the type of json.result, which can lead to unexpected behavior.

- const data = typeof json.result === "number" ? json.result : { data: [json.result.data[0] || { nodes: 0, edges: 0 }] }
+ let data;
+ if (typeof json.result === "number") {
+     data = { data: [{ nodes: json.result, edges: 0 }] };
+ } else if (json.result?.data?.[0]) {
+     data = { data: [json.result.data[0]] };
+ } else {
+     data = { data: [{ nodes: 0, edges: 0 }] };
+ }

This ensures a consistent data structure regardless of the input format, and includes proper validation of nested properties.

📝 Committable suggestion

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

Suggested change
const data = typeof json.result === "number" ? json.result : { data: [json.result.data[0] || { nodes: 0, edges: 0 }] }
let data;
if (typeof json.result === "number") {
data = { data: [{ nodes: json.result, edges: 0 }] };
} else if (json.result?.data?.[0]) {
data = { data: [json.result.data[0]] };
} else {
data = { data: [{ nodes: 0, edges: 0 }] };
}
🤖 Prompt for AI Agents
In app/api/schema/[schema]/count/route.ts at line 29, the data transformation
uses a ternary operator that produces inconsistent data structures depending on
whether json.result is a number or an object. To fix this, refactor the logic to
always produce a consistent data structure by validating nested properties
properly and ensuring the output format is uniform regardless of input type.

Comment on lines +18 to +23
const result = await fetch(`${request.nextUrl.origin}/api/graph/${schemaName}/?query=${encodeURIComponent(query)}`, {
method: "GET",
headers: {
cookie: request.headers.get('cookie') || '',
}
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Mitigate potential SSRF vulnerability by validating the schema parameter

The URL of the fetch request is constructed using a user-provided value (schema parameter). While this is an internal request within the same application, it's still important to validate this parameter to prevent path traversal or other injection attacks.

    const { schema } = await params
+   
+   // Validate schema parameter to prevent path traversal or injection
+   if (!schema || /[^a-zA-Z0-9_-]/.test(schema)) {
+       return NextResponse.json({ error: "Invalid schema name" }, { status: 400 })
+   }
    const schemaName = `${schema}_schema`

    try {
📝 Committable suggestion

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

Suggested change
const result = await fetch(`${request.nextUrl.origin}/api/graph/${schemaName}/?query=${encodeURIComponent(query)}`, {
method: "GET",
headers: {
cookie: request.headers.get('cookie') || '',
}
})
const { schema } = await params
// Validate schema parameter to prevent path traversal or injection
if (!schema || /[^a-zA-Z0-9_-]/.test(schema)) {
return NextResponse.json({ error: "Invalid schema name" }, { status: 400 })
}
const schemaName = `${schema}_schema`
try {
const result = await fetch(
`${request.nextUrl.origin}/api/graph/${schemaName}/?query=${encodeURIComponent(query)}`,
{
method: "GET",
headers: {
cookie: request.headers.get('cookie') || '',
},
}
)
// …
}
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 18-23: Server-side request forgery
The URL of this request depends on a user-provided value.

🤖 Prompt for AI Agents
In app/api/schema/[schema]/count/route.ts around lines 18 to 23, the fetch URL
is constructed using the user-provided schema parameter without validation,
which risks SSRF or injection attacks. To fix this, validate the schemaName
parameter against a whitelist of allowed schema names or use strict pattern
matching to ensure it contains only safe characters before including it in the
URL. Reject or sanitize any invalid input to prevent malicious URL manipulation.

Anchel123 and others added 12 commits May 22, 2025 15:21
…lity and maintainability. Use a named function for event handling and ensure proper cleanup on component unmount.
… padding for button variants and adjust layout for AddLabel and RemoveLabel components for improved UI consistency.
…component height for improved UI consistency.
…ing for Primary and Secondary button variants, and modify button alignment for improved UI consistency.
…same-height

Fix #962 Make the selector elements the same height
…he-query-input

Fix #951 Refactor EditorComponent layout to group buttons
Fix #953 Refactor Escape key listener in Settings component to improve readability and maintainability.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🔭 Outside diff range comments (1)
app/graph/Selector.tsx (1)

90-101: ⚠️ Potential issue

Remove redundant useEffect hooks to prevent duplicate API calls.

There are three useEffect hooks that call getOptions(), which will cause redundant API calls:

  • Lines 91-92: Calls on mount and dependency changes
  • Lines 94-96: Calls when indicator becomes "online"
  • Lines 98-101: Redundant call with empty dependencies

The third useEffect (lines 98-101) is unnecessary since the first one already handles the initial mount.

Apply this diff to remove the redundant hook:

-    useEffect(() => {
-        getOptions()
-    }, [])
♻️ Duplicate comments (10)
app/graph/selectGraph.tsx (3)

46-65: PATCH request lacks body but still declares Content-Type.


71-73: Missing dependencies in useEffect dependency array.


131-131: Hardcoded dialog title should use the type prop.

app/graph/GraphDataTable.tsx (2)

58-77: Previous review comment still applies - missing Content-Type header and incorrect HTTP method

The issues identified in the previous review remain unaddressed. The API call is still missing the Content-Type: application/json header and uses POST instead of PATCH for updating existing properties.


125-128: ⚠️ Potential issue

Missing Content-Type header in DELETE request

Similar to the setProperty function, this DELETE request sends a JSON body but doesn't specify the Content-Type header, which could cause server errors.

 const success = (await securedFetch(`api/graph/${prepareArg(graph.Id)}/${id}/${key}`, {
     method: "DELETE",
+    headers: { "Content-Type": "application/json" },
     body: JSON.stringify({ type }),
 }, toast, setIndicator)).ok
app/graph/GraphDataPanel.tsx (2)

84-86: 🛠️ Refactor suggestion

Update categories immutably after adding a label

After adding a category to the graph, the state should be updated immutably.

 graph.addCategory(newLabel, node)
-setCategories([...graph.Categories])
-setLabel([...node.category])
+// Ensure immutable updates
+const updatedCategories = graph.Categories.map(cat => ({...cat}));
+setCategories(updatedCategories);
+setLabel([...node.category]);

Alternatively, if graph.Categories contains the newly added category:

+setCategories(prevCategories => {
+  const newCategory = graph.Categories.find(c => c.name === newLabel);
+  return newCategory ? [...prevCategories, {...newCategory}] : prevCategories;
+});

104-106: 🛠️ Refactor suggestion

Update categories immutably after removing a label

Similar to the add label issue, removing a label should update state immutably.

 graph.removeCategory(removeLabel, node)
-setCategories([...graph.Categories])
-setLabel([...node.category])
+// Ensure immutable updates
+setCategories(prevCategories => 
+  prevCategories.filter(c => c.name !== removeLabel).map(cat => ({...cat}))
+);
+setLabel(prevLabels => prevLabels.filter(l => l !== removeLabel));
app/graph/toolbar.tsx (3)

50-52: ⚠️ Potential issue

Potential null/undefined error in search filter

The search filter on line 51 could throw a TypeError when value is null or undefined. Consider using a safer approach:

-Object.values(el.data).some(value => value.toString().toLowerCase().startsWith(searchElement.toLowerCase()))
+Object.values(el.data).some(value => 
+    value != null && String(value).toLowerCase().startsWith(searchElement.toLowerCase())
+)

60-66: 🛠️ Refactor suggestion

Reset suggestionIndex when the suggestions list changes

When the suggestions array changes, the current suggestionIndex might exceed the new array length, causing suggestions[suggestionIndex] to be undefined.

 useEffect(() => {
-    const timeout = setTimeout(handleOnChange, 500)
+    const timeout = setTimeout(() => {
+        handleOnChange()
+        setSuggestionIndex(0)
+    }, 500)

93-118: 🛠️ Refactor suggestion

Eliminate duplicated keyboard navigation logic

The keyboard event handling is duplicated between the input element and suggestion list, creating maintenance issues.

Extract the shared logic:

+ const handleKeyboardNavigation = (e: React.KeyboardEvent) => {
+     if (e.key === 'Escape') {
+         e.preventDefault()
+         setSearchElement("")
+     }
+
+     if (e.key === 'Enter' && suggestions[suggestionIndex]) {
+         e.preventDefault()
+         handleSearchElement(suggestions[suggestionIndex])
+         setSearchElement("")
+     }
+
+     if (e.key === 'ArrowDown') {
+         e.preventDefault()
+         const index = suggestionIndex === suggestions.length - 1 ? 0 : suggestionIndex + 1
+         setSuggestionIndex(index)
+         scrollToSuggestion(index)
+     }
+
+     if (e.key === 'ArrowUp') {
+         e.preventDefault()
+         const index = suggestionIndex === 0 ? suggestions.length - 1 : suggestionIndex - 1
+         setSuggestionIndex(index)
+         scrollToSuggestion(index)
+     }
+ }

Then replace both handlers with:

- onKeyDown={(e) => { /* ... duplicate code ... */ }}
+ onKeyDown={handleKeyboardNavigation}

Also applies to: 137-162

🧹 Nitpick comments (13)
app/graph/selectGraph.tsx (3)

1-1: Consider addressing the ESLint rule instead of disabling it.

The ESLint disable comment suggests there might be a code organization issue. Consider restructuring the code to avoid the "no-use-before-define" rule violation rather than disabling it.

-/* eslint-disable @typescript-eslint/no-use-before-define */

Move function definitions above their usage or consider extracting helper functions to separate modules.


110-119: Hardcoded label prop should be dynamic based on type.

The label prop in PaginationList is hardcoded as "graph" but should reflect the actual type being managed.

-label="graph"
+label={type.toLowerCase()}

38-38: Typo in state variable name.

The state variable openMenage appears to be a typo. It should be openManage.

-const [openMenage, setOpenMenage] = useState(false)
+const [openManage, setOpenManage] = useState(false)

And update all references throughout the component:

-<Dialog open={openMenage} onOpenChange={setOpenMenage}>
+<Dialog open={openManage} onOpenChange={setOpenManage}>

-setOpenMenage={setOpenMenage}
+setOpenManage={setOpenManage}
app/graph/GraphDataTable.tsx (2)

25-379: Consider breaking down this large component into smaller, focused components

This component handles multiple responsibilities: table rendering, inline editing, add/delete operations, and API calls. Consider extracting smaller components for better maintainability and testability.

Potential extractions:

  • AttributeRow component for individual table rows
  • AddAttributeRow component for the add new attribute functionality
  • AttributeActions component for the edit/delete buttons
  • Custom hooks for API operations (useAttributeOperations)

This would improve code organization, make unit testing easier, and enhance reusability.


39-47: Optimize useEffect dependencies to prevent unnecessary re-renders

The effect has setAttributes in the dependency array, but setAttributes is a state setter that doesn't change between renders. This creates an unnecessary dependency.

 useEffect(() => {
     if (lastObjId.current !== object.id) {
         setEditable("")
         setNewVal("")
         setNewKey("")
         setIsAddValue(false)
     }
     setAttributes(Object.keys(object.data))
-}, [lastObjId, object, setAttributes, type])
+}, [lastObjId, object, type])
app/graph/addLabel.tsx (1)

15-21: Consider adding a meaningful title to the default trigger button.

The default trigger button has an empty title attribute, which reduces accessibility for screen readers and tooltips.

    trigger = <Button
        variant="Primary"
        label="Add Label"
-        title=""
+        title="Open add label dialog"
    >
        <Plus />
    </Button>,
app/graph/Selector.tsx (1)

42-324: Consider breaking down this component for better maintainability.

The Selector component is handling multiple responsibilities and has grown quite large (300+ lines). Consider extracting some logic into separate components:

  1. Query history dialog (lines 190-296) could be a separate component
  2. Editor configuration and setup (lines 118-143, 257-283) could be extracted
  3. The conditional rendering logic for Graph vs Schema mode could be simplified

This would improve readability, testability, and maintainability.

app/graph/RemoveLabel.tsx (3)

16-18: Fix inconsistent label terminology.

There's inconsistency between the default trigger button label ("Delete Label") and the confirm button label ("Remove Label"). This could confuse users.

         <Button
             variant="Delete"
-            label="Delete Label"
+            label="Remove Label"
             title=""
             disabled={!selectedLabel}
         >

Also applies to: 54-54


19-19: Provide meaningful title attribute.

The empty title="" attribute provides no accessibility benefit. Either remove it or provide a descriptive title.

             variant="Delete"
             label="Remove Label"
-            title=""
+            title="Remove the selected label"
             disabled={!selectedLabel}

8-12: Consider making selectedLabel optional with validation.

The current interface requires selectedLabel but the default trigger button handles the empty case by disabling itself. Consider making it optional and adding proper validation.

 interface Props {
     trigger?: React.ReactNode
     onRemoveLabel: (label: string) => Promise<boolean>
-    selectedLabel: string
+    selectedLabel?: string
 }

Then add validation in the handler:

 const handleRemoveLabel = async () => {
+    if (!selectedLabel) {
+        console.warn('No label selected for removal')
+        return
+    }
     setIsLoading(true)
app/components/EditorComponent.tsx (2)

291-293: Potential performance issue with line counting.

The useEffect recalculates line numbers on every query change by splitting the string. For large queries, this could be inefficient.

Consider optimizing line counting:

useEffect(() => {
-    setLineNumber(historyQuery.query.split("\n").length)
+    setLineNumber(historyQuery.query ? historyQuery.query.split("\n").length : 1)
}, [historyQuery.query])

Alternatively, consider using Monaco Editor's built-in line counting methods when available.


720-734: Potential code duplication in onChange handlers.

The onChange logic for both editors (inline and dialog) is very similar but reversed in the conditional logic. This could be extracted to a common function.

Consider extracting the onChange logic:

+const handleQueryChange = (val: string | undefined, isDialogEditor: boolean) => {
+    if (historyQuery.counter) {
+        setHistoryQuery(prev => ({
+            ...prev,
+            query: val || ""
+        }))
+    } else {
+        setHistoryQuery(prev => ({
+            ...prev,
+            query: val || "",
+            currentQuery: val || ""
+        }))
+    }
+}

// Then use: onChange={(val) => handleQueryChange(val, false)} for inline
// And: onChange={(val) => handleQueryChange(val, true)} for dialog
app/graph/toolbar.tsx (1)

165-167: Use stable keys instead of array indices

Using array indices as keys can cause React rendering issues when the suggestions list changes. Consider using a stable identifier.

-suggestions.map((suggestion, index) =>
-    // eslint-disable-next-line react/no-array-index-key
-    <li key={index}>
+suggestions.map((suggestion, index) =>
+    <li key={`${suggestion.id}-${suggestion.category ? 'node' : 'link'}`}>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 697ac4f and 9624c25.

📒 Files selected for processing (10)
  • app/components/EditorComponent.tsx (9 hunks)
  • app/components/ui/Button.tsx (4 hunks)
  • app/graph/GraphDataPanel.tsx (2 hunks)
  • app/graph/GraphDataTable.tsx (1 hunks)
  • app/graph/RemoveLabel.tsx (1 hunks)
  • app/graph/Selector.tsx (4 hunks)
  • app/graph/addLabel.tsx (1 hunks)
  • app/graph/selectGraph.tsx (1 hunks)
  • app/graph/toolbar.tsx (1 hunks)
  • app/settings/page.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/settings/page.tsx
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Run tests part 2 (except settings)
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run tests part 2 (except settings)
🔇 Additional comments (24)
app/graph/selectGraph.tsx (1)

165-168: 🛠️ Refactor suggestion

Missing error handling in onDuplicate callback.

The callback function should handle potential errors when updating state, especially if setOptions or setSelectedValue could fail.

onDuplicate={(duplicateName) => {
-    setSelectedValue(duplicateName)
-    setOptions!([...options, duplicateName])
+    try {
+        setSelectedValue(duplicateName)
+        setOptions!([...options, duplicateName])
+    } catch (error) {
+        console.error('Failed to update after duplication:', error)
+        toast({
+            title: "Error",
+            description: "Failed to update selection after duplication",
+            variant: "destructive"
+        })
+    }
}}

Likely an incorrect or invalid review comment.

app/graph/addLabel.tsx (6)

1-7: Import organization looks good.

The imports are well-organized and follow a logical pattern with external libraries first, followed by internal components.


9-12: Props interface is well-designed.

The interface properly defines the expected props with appropriate TypeScript types. The optional trigger with required callback pattern provides good flexibility.


25-29: State management structure is appropriate.

The component uses appropriate state variables for dialog control, input value, and loading state management.


31-44: Form submission handler has good error handling structure.

The async handler properly prevents default form submission and uses try-finally for cleanup. The success-based conditional logic for resetting state is well-implemented.


57-63: Input component implementation is well-structured.

The input has proper test ID, controlled value, and change handler. Good use of data-testid for testing purposes.


65-77: Button configuration is comprehensive.

Both buttons have appropriate variants, labels, titles, and types. The loading state integration with the submit button is well-implemented.

app/graph/Selector.tsx (2)

73-88: Well-implemented API fetching with offline handling.

The getOptions callback properly handles offline state and auto-selection logic. Good use of useCallback for optimization.


118-143: Excellent keyboard shortcut implementation for query submission.

The editor configuration provides intuitive keyboard shortcuts:

  • Ctrl/Cmd+Enter for submit
  • Shift+Enter for new line
  • Enter for submit (when suggestions aren't visible)

This enhances the user experience significantly.

app/components/ui/Button.tsx (6)

8-8: LGTM! Variant type extension follows established pattern.

The addition of "Delete" to the Variant type is consistent with the existing string literal union pattern and enables delete-specific styling.


19-19: LGTM! Good design for tooltip customization.

Adding the optional tooltipVariant property enhances component flexibility by allowing independent tooltip styling while maintaining backward compatibility.


35-48: LGTM! Delete variant styling follows established pattern.

The new "Delete" case in the switch statement follows the same pattern as other variants, appropriately using border-red-500 for visual distinction of destructive actions.


54-54: LGTM! Well-designed default parameter assignment.

The tooltipVariant = variant default assignment ensures backward compatibility while enabling tooltip customization when needed.


71-76: LGTM! Good UX improvement with text truncation.

Wrapping the label in a <p> element with truncate class prevents text overflow and improves visual presentation while maintaining semantic structure.


81-81: LGTM! Consistent Delete variant tooltip styling.

The conditional red styling for Delete variant tooltips maintains visual consistency with the button's red border theme and provides appropriate visual cues for destructive actions.

app/graph/RemoveLabel.tsx (1)

26-29: LGTM! Good state management structure.

The component properly uses React hooks and context for state management. The separation of dialog state and loading state is clean and appropriate.

app/graph/GraphDataPanel.tsx (1)

115-241: Well-structured conditional rendering with proper access control!

The implementation cleanly handles both dialog and inline display modes, with appropriate role-based access control for label management operations.

app/components/EditorComponent.tsx (5)

19-42: Good practice: Extracting theme configuration to a reusable function.

The setTheme function is well-structured and provides a clean way to configure Monaco Editor themes. The color scheme and token definitions look appropriate for a dark theme.


79-83: Configuration update looks appropriate.

The addition of overflowWidgetsDomNode: undefined and hidden scrollbars aligns with creating a cleaner editor interface.


232-233: Good practice: Separate refs for different editor instances.

Having separate refs for the inline editor (editorRef) and dialog editor (dialogEditorRef) is a good approach for managing two independent Monaco Editor instances.


45-49:

❌ Incorrect review comment

Verify the breaking change in runQuery signature.

The runQuery prop signature has changed from (query: string, timeout?: number) => Promise<boolean> to (query: string) => Promise<void>. This is a breaking change that needs verification across the codebase.


🏁 Script executed:

#!/bin/bash
# Description: Find all usages of runQuery to ensure they match the new signature
# Expected: All calls should only pass query string, no timeout parameter or return value handling

# Search for runQuery function calls
rg -A 3 -B 1 "runQuery\(" --type tsx --type ts

# Search for runQuery prop definitions
rg -A 2 -B 1 "runQuery:" --type tsx --type ts

Length of output: 150


🏁 Script executed:

#!/bin/bash
# Description: Re-verify all usages and definitions of runQuery in TS/TSX files

# 1. Find all invocations of runQuery
rg -n --glob '*.ts' --glob '*.tsx' "runQuery\s*\(" -C2

# 2. Identify any calls passing more than one argument (i.e., potential timeout usage)
rg -n --glob '*.ts' --glob '*.tsx' -e "runQuery\s*\([^,]+,[^)]*" -C2

# 3. Find all prop/type definitions of runQuery
rg -n --glob '*.ts' --glob '*.tsx' "runQuery\s*:" -C2

Length of output: 8157


Ignore runQuery signature break alert

The runQuery change applies only to the EditorComponent prop (now (query: string) => Promise<void>) and does not affect the apicalls.runQuery used in your e2e tests (which remain runQuery(graphName: string, query: string, role?: string): Promise<RunQueryResponse>). No calls to the Editor’s prop pass a timeout or expect a boolean return, and your tests continue to call the unchanged API method. No further action is needed.

Likely an incorrect or invalid review comment.


662-739:

✅ Verification successful

Verify dialog accessibility and behavior.

The dialog structure looks well-implemented with proper accessibility features (VisuallyHidden components for screen readers). However, there are a few concerns:

  1. The dialog has disableClose prop but includes a close button - verify this behavior is intentional
  2. Both editors share the same handleEditorDidMount function but have different refs

🏁 Script executed:

#!/bin/bash
# Description: Check for DialogContent disableClose prop usage and CloseDialog component implementation
# Expected: Verify if disableClose prop conflicts with CloseDialog behavior

# Search for CloseDialog component definition
rg -A 10 -B 2 "CloseDialog" --type tsx --type ts

# Search for disableClose prop usage in DialogContent
rg -A 5 -B 2 "disableClose" --type tsx --type ts

Length of output: 153


I’ll re-run searches without the tsx/ts type filters and pull in the component definitions and import context to confirm behaviors:


🏁 Script executed:

#!/bin/bash
# Show imports at top of EditorComponent for context
sed -n '1,50p' app/components/EditorComponent.tsx

# Find CloseDialog declaration/usages
rg -g '*.tsx' -g '*.ts' -n "CloseDialog" -A 10 -B 2

# Find DialogContent declaration to see what disableClose does
rg -g '*.tsx' -g '*.ts' -n "DialogContent" -A 10 -B 2

# Find all disableClose usages
rg -g '*.tsx' -g '*.ts' -n "disableClose" -A 5 -B 2

# Locate handleEditorDidMount implementation
rg -g '*.tsx' -g '*.ts' -n "handleEditorDidMount" -A 10 -B 5

# Locate editor refs
rg -g '*.tsx' -g '*.ts' -n "dialogEditorRef" -A 3 -B 3
rg -g '*.tsx' -g '*.ts' -n "editorRef" -A 3 -B 3

Length of output: 68839


No changes needed: custom close control and shared mount logic are intentional

  • The disableClose prop on DialogContent simply removes the primitive’s default close button; you’ve explicitly added a <CloseDialog> in the header to render your custom close control.
  • The single handleEditorDidMount function installs the same editor commands and behavior in both the main and maximized editors, while editorRef and dialogEditorRef each track their own instance for focus and API calls.

Everything aligns with the design system and works as intended—no updates required.

app/graph/toolbar.tsx (2)

11-21: Well-structured Props interface

The refactored Props interface provides better type safety and clearer component API with appropriate optional properties.


82-265: Well-implemented search and control interface

The refactored component provides excellent functionality:

  • Debounced search with keyboard navigation
  • Clean dropdown menu for add operations
  • Proper conditional rendering and test attributes
  • Good accessibility with ARIA attributes

type={type}
/>
<DuplicateGraph
selectedValue={rows.filter(opt => opt.checked).map(opt => opt.cells[0].value as string)[0]}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential runtime error with array access.

Accessing the first element with [0] could cause a runtime error if the filtered array is empty. Consider adding a safeguard.

-selectedValue={rows.filter(opt => opt.checked).map(opt => opt.cells[0].value as string)[0]}
+selectedValue={rows.filter(opt => opt.checked).map(opt => opt.cells[0].value as string)[0] || ""}
📝 Committable suggestion

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

Suggested change
selectedValue={rows.filter(opt => opt.checked).map(opt => opt.cells[0].value as string)[0]}
selectedValue={rows.filter(opt => opt.checked)
.map(opt => opt.cells[0].value as string)[0] || ""}
🤖 Prompt for AI Agents
In app/graph/selectGraph.tsx at line 161, the code filters the rows for checked
options and then accesses the first element's value directly, which could cause
a runtime error if no options are checked and the filtered array is empty. Add a
safeguard by checking if the filtered array has any elements before accessing
the first one, or provide a default value to prevent potential errors.

Comment on lines +67 to +69
const handleSetRows = (opts: string[]) => {
setRows(opts.map(opt => session?.user?.role === "Admin" ? ({ checked: false, name: opt, cells: [{ value: opt, onChange: (value: string) => handleSetOption(value, opt), type: "text" }] }) : ({ checked: false, name: opt, cells: [{ value: opt, type: "readonly" }] })))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing the handleSetRows function to improve performance.

The handleSetRows function creates new objects on every call and depends on session and handleSetOption. This could cause unnecessary re-renders and should be memoized.

+import { useCallback } from "react";

-const handleSetRows = (opts: string[]) => {
-    setRows(opts.map(opt => session?.user?.role === "Admin" ? ({ checked: false, name: opt, cells: [{ value: opt, onChange: (value: string) => handleSetOption(value, opt), type: "text" }] }) : ({ checked: false, name: opt, cells: [{ value: opt, type: "readonly" }] })))
-}
+const handleSetRows = useCallback((opts: string[]) => {
+    setRows(opts.map(opt => session?.user?.role === "Admin" ? ({ checked: false, name: opt, cells: [{ value: opt, onChange: (value: string) => handleSetOption(value, opt), type: "text" }] }) : ({ checked: false, name: opt, cells: [{ value: opt, type: "readonly" }] })))
+}, [session?.user?.role, handleSetOption])
📝 Committable suggestion

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

Suggested change
const handleSetRows = (opts: string[]) => {
setRows(opts.map(opt => session?.user?.role === "Admin" ? ({ checked: false, name: opt, cells: [{ value: opt, onChange: (value: string) => handleSetOption(value, opt), type: "text" }] }) : ({ checked: false, name: opt, cells: [{ value: opt, type: "readonly" }] })))
}
// At the top of app/graph/selectGraph.tsx
import { useCallback } from "react";
// Replace the existing handleSetRows definition
const handleSetRows = useCallback((opts: string[]) => {
setRows(
opts.map(opt =>
session?.user?.role === "Admin"
? {
checked: false,
name: opt,
cells: [
{
value: opt,
onChange: (value: string) => handleSetOption(value, opt),
type: "text",
},
],
}
: {
checked: false,
name: opt,
cells: [{ value: opt, type: "readonly" }],
}
)
);
}, [session?.user?.role, handleSetOption]);
🤖 Prompt for AI Agents
In app/graph/selectGraph.tsx at lines 67 to 69, the handleSetRows function is
recreated on every render, which may cause unnecessary re-renders. Memoize
handleSetRows using useCallback, including session and handleSetOption as
dependencies, to optimize performance and prevent redundant object creation.

Comment on lines +324 to +326
<Input
data-testid="DataPanelAddAttributeKey"
ref={ref => !newKey ? ref?.focus() : undefined}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potential accessibility issue with conditional focus

The conditional focus logic using ref={ref => !newKey ? ref?.focus() : undefined} may not work reliably and could cause accessibility issues for screen readers.

-<Input
-    data-testid="DataPanelAddAttributeKey"
-    ref={ref => !newKey ? ref?.focus() : undefined}
-    className="w-full"
-    value={newKey}
-    onChange={(e) => setNewKey(e.target.value)}
-    onKeyDown={handleAddKeyDown}
-/>
+<Input
+    data-testid="DataPanelAddAttributeKey"
+    autoFocus
+    className="w-full"
+    value={newKey}
+    onChange={(e) => setNewKey(e.target.value)}
+    onKeyDown={handleAddKeyDown}
+/>

Alternatively, use a useEffect hook to focus the input when isAddValue becomes true.

📝 Committable suggestion

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

Suggested change
<Input
data-testid="DataPanelAddAttributeKey"
ref={ref => !newKey ? ref?.focus() : undefined}
<Input
data-testid="DataPanelAddAttributeKey"
autoFocus
className="w-full"
value={newKey}
onChange={(e) => setNewKey(e.target.value)}
onKeyDown={handleAddKeyDown}
/>
🤖 Prompt for AI Agents
In app/graph/GraphDataTable.tsx around lines 324 to 326, the focus is
conditionally set directly within the ref callback, which may not reliably focus
the input and could cause accessibility issues. To fix this, remove the inline
focus logic from the ref and instead use a useEffect hook that triggers when the
relevant state (such as isAddValue) changes to true, then call focus() on the
input element. This approach ensures consistent and accessible focus management.

Comment on lines +202 to +270
session?.user?.role !== "Read-Only" && (
editable === key ?
<>
<Button
data-testid="DataPanelSetAttributeConfirm"
indicator={indicator}
variant="button"
onClick={(e) => {
e.stopPropagation()
setProperty(key, newVal, true)
}}
isLoading={isSetLoading}
>
<Check size={20} />
</Button>
{
!isSetLoading &&
<Button
data-testid="DataPanelSetAttributeCancel"
variant="button"
onClick={(e) => {
e.stopPropagation()
handleSetEditable("", "")
}}
>
<X size={20} />
</Button>
}
</>
: hover === key &&
<>
<Button
data-testid="DataPanelSetAttribute"
variant="button"
onClick={() => handleSetEditable(key, object.data[key])}
disabled={isAddValue}
>
<Pencil size={20} />
</Button>
<DialogComponent
trigger={
<Button
data-testid="DataPanelDeleteAttribute"
variant="button"
title="Delete Attribute"
>
<Trash2 size={20} />
</Button>
}
title="Delete Attribute"
description="Are you sure you want to delete this attribute?"
>
<div className="flex justify-end gap-4">
<Button
data-testid="DataPanelDeleteAttributeConfirm"
variant="Delete"
label="Delete"
onClick={() => removeProperty(key)}
isLoading={isRemoveLoading}
/>
<CloseDialog
data-testid="DataPanelDeleteAttributeCancel"
label="Cancel"
variant="Cancel"
/>
</div>
</DialogComponent>
</>
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add proper ARIA labels for better accessibility

The edit and delete buttons lack proper ARIA labels, making them less accessible to screen reader users.

 <Button
     data-testid="DataPanelSetAttribute"
     variant="button"
+    aria-label={`Edit attribute ${key}`}
     onClick={() => handleSetEditable(key, object.data[key])}
     disabled={isAddValue}
 >
     <Pencil size={20} />
 </Button>
 <Button
     data-testid="DataPanelDeleteAttribute"
     variant="button"
+    aria-label={`Delete attribute ${key}`}
     title="Delete Attribute"
 >
     <Trash2 size={20} />
 </Button>
📝 Committable suggestion

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

Suggested change
session?.user?.role !== "Read-Only" && (
editable === key ?
<>
<Button
data-testid="DataPanelSetAttributeConfirm"
indicator={indicator}
variant="button"
onClick={(e) => {
e.stopPropagation()
setProperty(key, newVal, true)
}}
isLoading={isSetLoading}
>
<Check size={20} />
</Button>
{
!isSetLoading &&
<Button
data-testid="DataPanelSetAttributeCancel"
variant="button"
onClick={(e) => {
e.stopPropagation()
handleSetEditable("", "")
}}
>
<X size={20} />
</Button>
}
</>
: hover === key &&
<>
<Button
data-testid="DataPanelSetAttribute"
variant="button"
onClick={() => handleSetEditable(key, object.data[key])}
disabled={isAddValue}
>
<Pencil size={20} />
</Button>
<DialogComponent
trigger={
<Button
data-testid="DataPanelDeleteAttribute"
variant="button"
title="Delete Attribute"
>
<Trash2 size={20} />
</Button>
}
title="Delete Attribute"
description="Are you sure you want to delete this attribute?"
>
<div className="flex justify-end gap-4">
<Button
data-testid="DataPanelDeleteAttributeConfirm"
variant="Delete"
label="Delete"
onClick={() => removeProperty(key)}
isLoading={isRemoveLoading}
/>
<CloseDialog
data-testid="DataPanelDeleteAttributeCancel"
label="Cancel"
variant="Cancel"
/>
</div>
</DialogComponent>
</>
)
// …inside the hover===key branch of your JSX…
<Button
data-testid="DataPanelSetAttribute"
variant="button"
aria-label={`Edit attribute ${key}`}
onClick={() => handleSetEditable(key, object.data[key])}
disabled={isAddValue}
>
<Pencil size={20} />
</Button>
<DialogComponent
trigger={
<Button
data-testid="DataPanelDeleteAttribute"
variant="button"
title="Delete Attribute"
aria-label={`Delete attribute ${key}`}
>
<Trash2 size={20} />
</Button>
}
title="Delete Attribute"
description="Are you sure you want to delete this attribute?"
>
<div className="flex justify-end gap-4">
<Button
data-testid="DataPanelDeleteAttributeConfirm"
variant="Delete"
label="Delete"
onClick={() => removeProperty(key)}
isLoading={isRemoveLoading}
/>
<CloseDialog
data-testid="DataPanelDeleteAttributeCancel"
label="Cancel"
variant="Cancel"
/>
</div>
</DialogComponent>
// …rest of your JSX…
🤖 Prompt for AI Agents
In app/graph/GraphDataTable.tsx around lines 202 to 270, the edit and delete
buttons do not have ARIA labels, which affects accessibility for screen reader
users. Add descriptive ARIA labels to each button, such as aria-label="Edit
attribute" and aria-label="Delete attribute", to clearly convey their purpose to
assistive technologies.

Comment on lines +84 to +86
AddLabel.defaultProps = {
trigger: undefined
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove deprecated defaultProps usage.

The defaultProps pattern is deprecated in modern React. Since you're already using default parameters in the function signature (line 15), this declaration is redundant and should be removed.

-AddLabel.defaultProps = {
-    trigger: undefined
-}
📝 Committable suggestion

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

Suggested change
AddLabel.defaultProps = {
trigger: undefined
}
🤖 Prompt for AI Agents
In app/graph/addLabel.tsx around lines 84 to 86, the use of
AddLabel.defaultProps to set a default value for trigger is deprecated in modern
React. Remove the defaultProps assignment and rely solely on default parameters
in the function signature to define default values for props.

Comment on lines +70 to +72
RemoveLabel.defaultProps = {
trigger: undefined
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace deprecated defaultProps with default parameters.

The defaultProps pattern is deprecated in modern React with TypeScript. Use default parameters in the function signature instead.

-export default function RemoveLabel({
-    selectedLabel,
-    trigger = <Button
-        variant="Delete"
-        label="Delete Label"
-        title=""
-        disabled={!selectedLabel}
-    >
-        <Trash2 />
-    </Button>,
-    onRemoveLabel,
-}: Props) {
+export default function RemoveLabel({
+    selectedLabel,
+    trigger,
+    onRemoveLabel,
+}: Props) {
+    const defaultTrigger = (
+        <Button
+            variant="Delete"
+            label="Delete Label"
+            title=""
+            disabled={!selectedLabel}
+        >
+            <Trash2 />
+        </Button>
+    );

-RemoveLabel.defaultProps = {
-    trigger: undefined
-}

Then use trigger || defaultTrigger in the DialogComponent:

-            trigger={trigger}
+            trigger={trigger || defaultTrigger}
🤖 Prompt for AI Agents
In app/graph/RemoveLabel.tsx lines 70 to 72, the use of defaultProps is
deprecated in modern React with TypeScript. Remove the defaultProps assignment
and instead set a default value for the trigger parameter directly in the
function signature. Then, replace any usage of trigger inside the component with
trigger || defaultTrigger to ensure default behavior.

Comment on lines 44 to +48
toast({
title: "Error",
description: "Please fill in the value field",
variant: "destructive"
})
return false
}
try {
if (actionType === "set") setIsSetLoading(true)
const result = await securedFetch(`api/graph/${prepareArg(graph.Id)}/${id}/${key}`, {
method: "POST",
body: JSON.stringify({
value: val,
type
})
}, toast, setIndicator)

if (result.ok) {

graph.setProperty(key, val, id)

const value = obj.data[key]
setObj({ ...obj, data: { ...obj.data, [key]: val } })

handleSetEditable("", "")
toast({
title: "Success",
description: `Attribute ${actionType}`,
variant: "default",
action: isUndo ? <ToastButton onClick={() => setProperty(key, value, false)} /> : undefined
})
}

return result.ok
} finally {
if (actionType === "set") setIsSetLoading(false)
}
}

const handleAddValue = async (key: string, value: string) => {
if (!key || key === "" || !value || value === "") {
toast({
title: "Error",
description: "Please fill in both fields",
variant: "destructive"
})
return
}
try {
setIsAddLoading(true)
const success = await setProperty(key, value, false, "added")
if (!success) return
setIsAddValue(false)
setNewKey("")
setNewVal("")
} finally {
setIsAddLoading(false)
}
}

const removeProperty = async (key: string) => {
try {
setIsRemoveLoading(true)
const { id } = obj
const success = (await securedFetch(`api/graph/${prepareArg(graph.Id)}/${id}/${key}`, {
method: "DELETE",
body: JSON.stringify({ type }),
}, toast, setIndicator)).ok

if (success) {
const value = obj.data[key]

graph.removeProperty(key, id)
delete obj.data[key]
setObj({ ...obj })

toast({
title: "Success",
description: "Attribute removed",
action: <ToastButton onClick={() => handleAddValue(key, value)} />,
variant: "default"
})
}

return success
} finally {
setIsRemoveLoading(false)
}
}

const handleAddKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Escape") {
setIsAddValue(false)
setNewKey("")
setNewVal("")
return
if (labelsListRef.current) {
setShowAsDialog(labelsListRef.current.clientHeight > 80)
}
}, [labelsListRef.current?.clientHeight])
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix useEffect dependency to avoid potential issues.

The dependency labelsListRef.current?.clientHeight won't trigger effect re-runs because React doesn't track changes to ref properties. This could lead to stale state.

Consider using a ResizeObserver or removing the dependency:

 useEffect(() => {
     if (labelsListRef.current) {
         setShowAsDialog(labelsListRef.current.clientHeight > 80)
     }
-}, [labelsListRef.current?.clientHeight])
+}, []) // Run once on mount, or use ResizeObserver for dynamic updates

Alternatively, implement a ResizeObserver to properly track size changes:

useEffect(() => {
    const element = labelsListRef.current;
    if (!element) return;
    
    const observer = new ResizeObserver(() => {
        setShowAsDialog(element.clientHeight > 80);
    });
    
    observer.observe(element);
    return () => observer.disconnect();
}, []);
🤖 Prompt for AI Agents
In app/graph/GraphDataPanel.tsx around lines 44 to 48, the useEffect hook
depends on labelsListRef.current?.clientHeight, which won't trigger re-renders
because React does not track changes to ref properties. This may cause the
showAsDialog state to become stale if the element's height changes dynamically.
To fix this, replace the current useEffect with a ResizeObserver that observes
the element's size changes and updates showAsDialog accordingly, and set the
dependency array to empty to run only once on mount.

Comment on lines +400 to +404
useEffect(() => {
if (monacoEditor) {
addSuggestions(monacoEditor)
}
}, [monacoEditor, graphIdRef.current])
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potential unnecessary re-execution of suggestions.

The useEffect for addSuggestions will re-run whenever graphIdRef.current changes, but graphIdRef.current is a primitive value that may not trigger the effect as expected.

Use graph.Id directly in the dependency array:

useEffect(() => {
    if (monacoEditor) {
        addSuggestions(monacoEditor)
    }
-}, [monacoEditor, graphIdRef.current])
+}, [monacoEditor, graph.Id])
📝 Committable suggestion

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

Suggested change
useEffect(() => {
if (monacoEditor) {
addSuggestions(monacoEditor)
}
}, [monacoEditor, graphIdRef.current])
useEffect(() => {
if (monacoEditor) {
addSuggestions(monacoEditor)
}
}, [monacoEditor, graph.Id])
🤖 Prompt for AI Agents
In app/components/EditorComponent.tsx around lines 400 to 404, the useEffect
hook depends on graphIdRef.current, which is a ref and may not trigger
re-execution as intended. To fix this, replace graphIdRef.current with graph.id
(or the appropriate primitive value) directly in the dependency array to ensure
the effect runs when the graph's ID changes. This prevents unnecessary
re-renders and ensures addSuggestions is called appropriately.

Comment on lines +53 to +54
|| el.label && (el as Link).label.toLowerCase().includes(searchElement.toLowerCase())
|| el.category && (el as Node).category.some(c => c.toLowerCase().includes(searchElement.toLowerCase()))
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use optional chaining for safer property access

The code accesses optional properties without proper null checks, which could cause runtime errors.

-|| el.label && (el as Link).label.toLowerCase().includes(searchElement.toLowerCase())
-|| el.category && (el as Node).category.some(c => c.toLowerCase().includes(searchElement.toLowerCase()))
+|| (el as Link).label?.toLowerCase().includes(searchElement.toLowerCase())
+|| (el as Node).category?.some(c => c.toLowerCase().includes(searchElement.toLowerCase()))

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/graph/toolbar.tsx on lines 53 to 54, the code accesses properties like
el.label and el.category without checking if they are defined, which could lead
to runtime errors. Use optional chaining to safely access these properties, such
as el?.label and el?.category, before calling methods like toLowerCase() or
some(). This ensures the code only attempts to access properties if they exist.

Anchel123 and others added 2 commits May 27, 2025 12:59
…ency. Adjust button labels, editor height calculations, and layout styles. Update Selector and SelectGraph components for better alignment and height.
Fix #986 Refactor EditorComponent and Graph components for improved UI consistency.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
app/graph/Selector.tsx (1)

90-101: ⚠️ Potential issue

Remove redundant useEffect hook.

The useEffect at lines 98-101 with an empty dependency array is redundant. The component already calls getOptions() through the first useEffect (lines 90-92) which runs on mount and whenever getOptions changes.

Remove the redundant useEffect:

    useEffect(() => {
        if (indicator === "online") getOptions()
    }, [indicator, getOptions])

-    useEffect(() => {
-        getOptions()
-    }, [])
♻️ Duplicate comments (2)
app/graph/Selector.tsx (1)

144-153: ⚠️ Potential issue

Add null checks to prevent runtime errors.

The function uses non-null assertion operators on optional props without checking if they exist. This could cause runtime errors if runQuery or historyQuery are undefined.

Apply this diff to add proper null checks:

 const handleSubmit = async () => {
+    if (!runQuery || !historyQuery) return
     try {
         setIsLoading(true)
-        await runQuery!(historyQuery!.query.trim())
+        await runQuery(historyQuery.query.trim())
         setQueriesOpen(false)
     } finally {
         setIsLoading(false)
     }
 }
app/components/EditorComponent.tsx (1)

405-409: ⚠️ Potential issue

Fix incorrect dependency in useEffect.

The useEffect uses graphIdRef.current in the dependency array, but ref values don't trigger re-renders when changed. Use graph.Id directly instead.

Apply this diff:

useEffect(() => {
    if (monacoEditor) {
        addSuggestions(monacoEditor)
    }
-}, [monacoEditor, graphIdRef.current])
+}, [monacoEditor, graph.Id])
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9624c25 and 0a01118.

📒 Files selected for processing (4)
  • app/components/EditorComponent.tsx (9 hunks)
  • app/graph/GraphDataTable.tsx (1 hunks)
  • app/graph/Selector.tsx (4 hunks)
  • app/graph/selectGraph.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/graph/GraphDataTable.tsx
  • app/graph/selectGraph.tsx
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Run settings tests
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run tests part 2 (except settings)
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run tests part 2 (except settings)
  • GitHub Check: Run settings tests
🔇 Additional comments (1)
app/components/EditorComponent.tsx (1)

245-248: Well-structured height calculation with useMemo.

The editor height calculation is now properly memoized and cleanly structured using useMemo. This is a good improvement over inline calculations.

Comment on lines +213 to +237
<PaginationList
label="Query"
className="w-[40%] bg-background rounded-lg overflow-hidden"
isSelected={(item) => historyQuery.queries.findIndex(q => q.text === item.text) + 1 === historyQuery.counter}
afterSearchCallback={(newFilteredList) => {
if (newFilteredList.every(q => q.text !== historyQuery.query)) {
setHistoryQuery(prev => ({
...prev,
counter: 0
}))
}
focusEditorAtEnd()
}}
dataTestId="queryHistory"
list={historyQuery.queries.reverse()}
step={STEP}
onClick={(counter) => {
setHistoryQuery(prev => ({
...prev,
counter: historyQuery.queries.findIndex(q => q.text === counter) + 1
}))

setTab("query")
}}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix array mutation and optimize duplicate array operations.

Two issues in the PaginationList usage:

  1. reverse() mutates the original array
  2. findIndex is called twice for the same purpose

Apply this diff to fix both issues:

+const reversedQueries = [...historyQuery.queries].reverse()
+const getCurrentIndex = (query: { text: string }) => historyQuery.queries.findIndex(q => q.text === query.text) + 1

 <PaginationList
     label="Query"
     className="w-[40%] bg-background rounded-lg overflow-hidden"
-    isSelected={(item) => historyQuery.queries.findIndex(q => q.text === item.text) + 1 === historyQuery.counter}
+    isSelected={(item) => getCurrentIndex(item) === historyQuery.counter}
     afterSearchCallback={(newFilteredList) => {
         if (newFilteredList.every(q => q.text !== historyQuery.query)) {
             setHistoryQuery(prev => ({
                 ...prev,
                 counter: 0
             }))
         }
         focusEditorAtEnd()
     }}
     dataTestId="queryHistory"
-    list={historyQuery.queries.reverse()}
+    list={reversedQueries}
     step={STEP}
     onClick={(counter) => {
         setHistoryQuery(prev => ({
             ...prev,
-            counter: historyQuery.queries.findIndex(q => q.text === counter) + 1
+            counter: getCurrentIndex(counter)
         }))

         setTab("query")
     }}
 />
📝 Committable suggestion

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

Suggested change
<PaginationList
label="Query"
className="w-[40%] bg-background rounded-lg overflow-hidden"
isSelected={(item) => historyQuery.queries.findIndex(q => q.text === item.text) + 1 === historyQuery.counter}
afterSearchCallback={(newFilteredList) => {
if (newFilteredList.every(q => q.text !== historyQuery.query)) {
setHistoryQuery(prev => ({
...prev,
counter: 0
}))
}
focusEditorAtEnd()
}}
dataTestId="queryHistory"
list={historyQuery.queries.reverse()}
step={STEP}
onClick={(counter) => {
setHistoryQuery(prev => ({
...prev,
counter: historyQuery.queries.findIndex(q => q.text === counter) + 1
}))
setTab("query")
}}
/>
// Create a reversed copy to avoid mutating historyQuery.queries
const reversedQueries = [...historyQuery.queries].reverse()
// Cache the index lookup to avoid duplicate findIndex calls
const getCurrentIndex = (query: { text: string }) =>
historyQuery.queries.findIndex(q => q.text === query.text) + 1
<PaginationList
label="Query"
className="w-[40%] bg-background rounded-lg overflow-hidden"
isSelected={(item) => getCurrentIndex(item) === historyQuery.counter}
afterSearchCallback={(newFilteredList) => {
if (newFilteredList.every(q => q.text !== historyQuery.query)) {
setHistoryQuery(prev => ({
...prev,
counter: 0
}))
}
focusEditorAtEnd()
}}
dataTestId="queryHistory"
list={reversedQueries}
step={STEP}
onClick={(counter) => {
setHistoryQuery(prev => ({
...prev,
counter: getCurrentIndex(counter)
}))
setTab("query")
}}
/>
🤖 Prompt for AI Agents
In app/graph/Selector.tsx between lines 213 and 237, the code uses reverse()
directly on historyQuery.queries, which mutates the original array, and calls
findIndex twice redundantly. To fix this, create a reversed copy of
historyQuery.queries without mutating the original by using slice().reverse(),
and store the result of findIndex in a variable to reuse it instead of calling
it twice. Update the isSelected and onClick handlers accordingly to use the
stored index.

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

Successfully merging this pull request may close these issues.

3 participants