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 106 commits into
base: main
Choose a base branch
from
Open

Staging #915

wants to merge 106 commits into from

Conversation

Anchel123
Copy link
Contributor

@Anchel123 Anchel123 commented Apr 28, 2025

Summary by CodeRabbit

  • New Features

    • Added multi-selection and bulk deletion of elements in graph and schema views.
    • Introduced duplication functionality for graphs and schemas with PATCH API routes.
    • Added table view tab with row count validation and improved test coverage for table interactions.
    • Enhanced query editor with maximize mode, tabs for profile/metadata/explain, and improved query history navigation.
    • Added new controls for graph visualization: animation toggle, zoom, and fit-to-view.
    • Added tooltips and info icons for improved UI clarity across dialogs and settings.
    • Introduced paginated and searchable label and query history lists for better navigation.
    • Added label management dialogs with add and remove label components.
    • Added comprehensive attribute editing table with full CRUD operations.
    • Added a new pagination list component for navigating large lists with search and selection.
    • Added a new graph details component displaying nodes, edges, and graph name summaries.
    • Added a new select graph component with management dialogs and role-based editing.
    • Introduced a new controls component for graph animation and zoom controls.
  • Improvements

    • Refactored UI layouts for header, data panels, selectors, and graph views for cleaner, more accessible design.
    • Unified context-based state management for graphs and schemas, replacing local state with React contexts.
    • Improved role-based access control, enforcing read-only restrictions in API routes and UI query executions.
    • Enhanced testability with consistent data-testid attributes on interactive elements.
    • Added loading indicators and improved feedback for async operations.
    • Simplified and standardized page object models for end-to-end tests, improving locator strategies and interaction methods.
    • Improved zoom-to-fit behavior with adjustable padding multiplier.
    • Enhanced graph and schema management with new select and duplicate components.
    • Updated toolbar with searchable element selection and keyboard navigation.
    • Improved toast and tooltip components with better accessibility and styling.
    • Simplified editor component UI and dialog control flow; added explicit theme setting.
    • Refined table component to use typed cells, loading states, and scoped test IDs.
    • Improved query settings UI with info icons, tooltips, and localStorage synchronization.
    • Streamlined graph and schema views with externalized state and simplified tabbed layouts.
    • Improved label components with generic typing, fixed layout, and test IDs.
    • Enhanced data panel and graph data panel with delegated attribute editing and label management.
    • Improved pagination list with debounced search and dynamic page controls.
    • Refined form field types for better type safety and consistent event handling.
    • Standardized delete and export dialogs with consistent triggers and test IDs.
    • Improved graph page object model with unified locator and interaction methods.
    • Updated header layout to vertical stack with clearer navigation and menu structure.
    • Enhanced selector component with query history tabs and paginated lists.
    • Improved graph duplication UI with validation and PATCH API integration.
    • Added better handling of query history and editor integration in selector.
    • Enhanced schema create element and data panel with improved label management UI and state.
    • Refined controls component for graph interaction with animation and zoom buttons.
    • Refined API to conditionally execute queries based on user roles for read-only enforcement.
    • Updated internal API calls to use HTTP requests for count queries, improving modularity.
    • Improved localStorage synchronization for query timeout and limit settings.
    • Enhanced login verification with initialization of timeout and limit from localStorage.
    • Improved test code consistency by updating method names and interaction patterns.
  • Bug Fixes

    • Fixed UI inconsistencies and improved error handling in dialogs and forms.
    • Improved synchronization of localStorage settings for query limits and timeouts.
    • Corrected test interactions and fixed flaky test selectors.
    • Fixed graph and schema deletion flows to correctly update categories, labels, and selection states.
    • Resolved issues with read-only user query execution by conditional API query methods.
    • Addressed visibility and interaction issues in canvas and schema page object models.
  • Documentation

    • Updated tooltips and descriptions for better user guidance.
  • Tests

    • Added and refactored end-to-end tests for tutorial, data panel, table view, and query settings.
    • Improved page object models for more reliable and maintainable test automation.
    • Disabled flaky or deprecated tests and updated test method calls for consistency.
    • Added new tests for table view tab behavior and tutorial UI interactions.
  • Chores

    • Upgraded dependencies and bumped package version to 2.0.0.
    • Added enhanced security headers to all 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 22, 2025 0:14am

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

coderabbitai bot commented Apr 28, 2025

Walkthrough

This update introduces extensive architectural and UI refactoring across the application. Major changes include a new context-driven state management system for both graph and schema entities, enabling multi-selection and batch operations. The user interface is overhauled with improved accessibility, testability (via data-testid attributes), and modularity, including new controls, dialogs, and table views. Backend API routes now enforce role-based query execution. End-to-end test automation is modernized with a new page object model, enhanced selectors, and additional test suites for new features. Security headers are added at the framework level, and the package version is incremented to 2.0.0.

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 and 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 increment/decrement, and improved getter logic.
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

Suggested reviewers

  • AviAvni

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-22T12_15_49_755Z-debug-0.log

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.

✨ 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: 3

♻️ Duplicate comments (14)
app/schema/page.tsx (3)

75-82: Still mutating React state array in place (duplicate of earlier review)

stateSelectedElements.push(selectedElement) mutates the original array because Object.values returns the same reference for arrays.
This breaks immutability and can cause missed renders.

-const stateSelectedElements = Object.values(selectedElements)
+let stateSelectedElements = [...selectedElements]   // clone
 ...
-stateSelectedElements.push(selectedElement)
+stateSelectedElements = [...stateSelectedElements, selectedElement]

83-100: schema.removeLinks still fed with stale selectedElements (duplicate)

After computing stateSelectedElements, the call underneath still uses the old selectedElements, so links for the additionally-pushed element are never removed. Use the updated list.

-schema.removeLinks(setLabels, selectedElements.map((element) => element.id))
+schema.removeLinks(setLabels, stateSelectedElements.map((element) => element.id))

83-100: No error handling inside Promise.all loop (duplicate)

Failures in any DELETE request silently fail and stop subsequent clean-up logic from running. Wrap the fetch in try/catch or check result.ok and surface errors to the user.

app/graph/toolbar.tsx (2)

50-55: Null/undefined values in el.data can crash the search filter

Object.values(el.data).some(value => value.toString().toLowerCase()...) will throw a TypeError when a property is null or undefined, aborting the whole search.

-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())
+)

Using String(value) is both safe and concise.


60-66: Reset suggestionIndex when the suggestions list changes

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

 useEffect(() => {
-    const timeout = setTimeout(handleOnChange, 500)
+    const timeout = setTimeout(() => {
+        handleOnChange()
+        setSuggestionIndex(0)          // <- keep index in range
+    }, 500)
     
     return () => {
         clearTimeout(timeout)
     }
 }, [graph, handleOnChange, searchElement])
app/graph/page.tsx (2)

115-126: handleCooldown treats undefined as "running"

When called with no argument (e.g., after runQuery), ticks is undefined, yet the else-branch flags the canvas as running. Pass an explicit 0 or guard for undefined to reflect the real engine state.

-        if (ticks === 0) {
+        if (ticks === undefined || ticks === 0) {
            canvas.setAttribute('data-engine-status', 'stop')
        } else {
            canvas.setAttribute('data-engine-status', 'running')
        }

154-158: ⚠️ Potential issue

State array mutated in place – breaks React invariants

selectedElements.push(selectedElement) alters the current state array without using the setter. This may not trigger rerenders and causes unpredictable behavior.

-            selectedElements.push(selectedElement)
+            const toDelete = [...selectedElements, selectedElement]
+            setSelectedElements(toDelete)

Remember to use the new toDelete reference throughout the rest of the function.

app/graph/GraphDataPanel.tsx (2)

83-87: 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])
+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: 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])
+setCategories(prevCategories => 
+  prevCategories.filter(c => c.name !== removeLabel)
+)
+setLabel(prevLabels => prevLabels.filter(l => l !== removeLabel))
app/graph/GraphView.tsx (3)

190-202: Tight coupling between data layer and React state

The ForceGraph component receives setLabels directly, which tightly couples the graph data layer to React state management. This makes it harder to maintain separation of concerns and could lead to unexpected side effects.

Consider introducing a higher-level API that abstracts the state updates:

<ForceGraph
    graph={graph}
    chartRef={chartRef}
    data={data}
    setData={setData}
    selectedElement={selectedElement}
    setSelectedElement={setSelectedElement}
    selectedElements={selectedElements}
    setSelectedElements={setSelectedElements}
    cooldownTicks={cooldownTicks}
    handleCooldown={handleCooldown}
-   setLabels={setLabels}
+   onLabelsChange={(newLabels) => setLabels(newLabels)}
/>

Then, modify the ForceGraph component to call this callback rather than directly updating state.


97-113: 🛠️ Refactor suggestion

State mutation breaks React immutability principles

The onCategoryClick function directly mutates the category object and its elements. This can lead to unpredictable behavior in React since it violates React's immutability principles for state management.

Consider using an immutable approach:

const onCategoryClick = (category: Category<Node>) => {
-   category.show = !category.show
+   const updatedCategory = { ...category, show: !category.show }

-   category.elements.forEach((node) => {
+   const updatedElements = category.elements.map((node) => {
        if (node.category[0] !== category.name) return node
        if (category.show) {
-           node.visible = true
+           return { ...node, visible: true }
        } else {
-           node.visible = false
+           return { ...node, visible: false }
        }
+       return node
    })
+   updatedCategory.elements = updatedElements

    graph.visibleLinks(category.show)
-   graph.CategoriesMap.set(category.name, category)
+   graph.CategoriesMap.set(category.name, updatedCategory)
    setData({ ...graph.Elements })
}

115-128: 🛠️ Refactor suggestion

Direct mutations in onLabelClick function

Similar to onCategoryClick, this function directly mutates label objects, violating React's immutability principles.

const onLabelClick = (label: Category<Link>) => {
-   label.show = !label.show
+   const updatedLabel = { ...label, show: !label.show }

-   label.elements.filter((link) => link.source.visible && link.target.visible).forEach((link) => {
+   const updatedElements = label.elements.map((link) => {
+       if (!link.source.visible || !link.target.visible) return link
        if (label.show) {
-           link.visible = true
+           return { ...link, visible: true }
        } else {
-           link.visible = false
+           return { ...link, visible: false }
        }
+       return link
    })
+   updatedLabel.elements = updatedElements

-   graph.LabelsMap.set(label.name, label)
+   graph.LabelsMap.set(label.name, updatedLabel)
    setData({ ...graph.Elements })
}
app/schema/SchemaView.tsx (2)

92-103: 🛠️ Refactor suggestion

Ensure immutable state updates for categories

The onCategoryClick function directly mutates the category object and updates nodes in place. This violates React's immutability principles and can lead to unexpected behaviors.

const onCategoryClick = (category: Category<Node>) => {
-   category.show = !category.show
+   const updatedCategory = { ...category, show: !category.show }

-   schema.Elements.nodes.forEach((node) => {
+   const updatedNodes = schema.Elements.nodes.map((node) => {
        if (node.category[0] !== category.name) return node
-       node.visible = category.show
+       return { ...node, visible: updatedCategory.show }
    })
+   schema.Elements.nodes = updatedNodes

-   schema.visibleLinks(category.show)
+   schema.visibleLinks(updatedCategory.show)
-   schema.CategoriesMap.set(category.name, category)
+   schema.CategoriesMap.set(category.name, updatedCategory)
    setData({ ...schema.Elements })
}

105-115: 🛠️ Refactor suggestion

Ensure immutable state updates for labels

Similar to the category issue, the onLabelClick function directly mutates label objects. Apply immutable state update patterns for consistency with React's state management principles.

const onLabelClick = (label: Category<Link>) => {
-   label.show = !label.show
+   const updatedLabel = { ...label, show: !label.show }

-   schema.Elements.links.forEach((link) => {
+   const updatedLinks = schema.Elements.links.map((link) => {
        if (link.label !== label.name) return link
-       link.visible = label.show
+       return { ...link, visible: updatedLabel.show }
    })
+   schema.Elements.links = updatedLinks

-   schema.LabelsMap.set(label.name, label)
+   schema.LabelsMap.set(label.name, updatedLabel)
    setData({ ...schema.Elements })
}
🧹 Nitpick comments (26)
app/graph/RemoveLabel.tsx (2)

19-19: Add title attribute for improved accessibility.

The empty title attribute on the button should be populated with a meaningful description.

-        title=""
+        title="Remove selected label"

43-66: Add data-testid attributes for testing.

The component has good structure but lacks test IDs for automated testing, which would be consistent with other components in the codebase.

        <DialogComponent
+           data-testid="removeLabelDialog"
            title="Remove Label"
            trigger={trigger}
            open={open}
            onOpenChange={setOpen}
            description="Are you sure you want to remove this label?"
        >
            <div className="flex gap-4 justify-end">
                <Button
+                   data-testid="confirmRemoveLabel"
                    variant="Delete"
                    label="Remove Label"
                    title=""
                    onClick={handleRemoveLabel}
                    isLoading={isLoading}
                    indicator={indicator}
                />
app/graph/addLabel.tsx (3)

18-18: Add title attribute for improved accessibility.

The empty title attribute on the button should be populated with a meaningful description.

-        title=""
+        title="Add a new label"

53-63: Consider auto-focusing with a safer approach.

The current implementation uses a ref callback to focus the input, which works but doesn't account for potential rendering scenarios where focusing might fail or be undesirable (like mobile devices).

                <Input
                    data-testid="addLabelInput"
                    variant="primary"
-                    ref={ref => ref?.focus()}
+                    autoFocus
                    value={label}
                    onChange={(e) => setLabel(e.target.value)}
                />

64-78: Add data-testid attributes for testing.

The component has a test ID for the input field but lacks them for buttons, which would be helpful for consistent automated testing.

                <div className="flex gap-4 justify-end">
                    <Button
+                       data-testid="confirmAddLabel"
                        indicator={indicator}
                        variant="Primary"
                        label="Add label"
                        title="Add a new label"
                        type="submit"
                        isLoading={isLoading}
                    />
                    <CloseDialog
+                       data-testid="cancelAddLabel"
                        variant="Cancel"
                        label="Cancel"
                        type="button"
                    />
                </div>
app/components/ui/combobox.tsx (1)

4-7: Clean up unused Dialog import.

The component imports Dialog but only uses it for openMenage state, which doesn't appear to be fully utilized in the component.

-import { Dialog } from "@/components/ui/dialog"
 import { cn } from "@/lib/utils"
 import { useContext, useEffect, useState } from "react"
 import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
app/components/PaginationList.tsx (2)

59-59: Make search input test ID dynamic.

The search input uses a fixed test ID "queryHistorySearch" regardless of component purpose. Consider making this dynamic based on the component's context.

-                    data-testid="queryHistorySearch"
+                    data-testid={`${dataTestId}Search`}

106-128: Add comments for complex pagination logic.

The pagination calculation logic is complex and would benefit from explanatory comments to make it more maintainable.

                {
+                    // Calculate which page numbers to display
+                    // Shows a window of pages centered around current page
+                    // with appropriate bounds handling
                    Array(pageCount).fill(0).map((_, index) => index)
                        .slice(
                            Math.max(0, Math.min(
                                stepCounter - 1,
                                pageCount - 3
                            )),
                            Math.min(
                                Math.max(3, stepCounter + 2),
                                pageCount
                            )
                        )
                        .map((index) => (
app/graph/GraphDataTable.tsx (3)

95-97: isSetLoading never resets for “add” actions

Because the flag is toggled only when actionType === "set", it stays false during an “add” request, so the confirm button never shows a spinner.
Move the loading toggle out of the conditional or introduce a separate isAddLoading flag inside this helper.


17-23: type: boolean is cryptic – use a discriminated union or enum

true ⇢ Node, false ⇢ Link is not self-documenting and invites misuse. At minimum:

type ElementKind = "node" | "link";
...
type: ElementKind;

This also gives better IntelliSense & compile-time safety.


151-162: Keyboard handlers: prevent default to avoid accidental form submits

When the user presses Enter you call handleAddValue / setProperty, but the event may still bubble and submit an ancestor <form>.
Add e.preventDefault() after detecting the key.

Also applies to: 170-173

app/graph/selectGraph.tsx (2)

131-134: Dialog title is hard-coded to “Manage Graphs”

When type === "Schema" the modal still reads “Manage Graphs”, confusing users.

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

38-40: Typo: openMenageopenManage

Minor naming typo that spreads through the file; fix for readability & consistency.

app/graph/toolbar.tsx (1)

93-127: Reduce duplication by extracting keyboard navigation logic

The same keyboard event handling code is duplicated between the input and suggestion list elements. Extract this into a reusable function to improve maintainability.

+const handleKeyNavigation = (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 use this function in both onKeyDown handlers
-onKeyDown={(e) => { /* existing code */ }}
+onKeyDown={handleKeyNavigation}

Also applies to: 137-162

app/graph/page.tsx (1)

197-214: Avoid duplicate cleanup operations

The function sets setSelectedElements([]) and setSelectedElement(undefined) twice - once at the beginning of the cleanup section and again at the end. This duplication is unnecessary.

 graph.removeElements(selectedElements)

 fetchCount()
-setSelectedElements([])
-setSelectedElement(undefined)

 graph.removeLinks(setLabels, selectedElements.map((element) => element.id))

 setData({ ...graph.Elements })
 toast({
     title: "Success",
     description: `${selectedElements.length > 1 ? "Elements" : "Element"} deleted`,
 })
 handleCooldown()
 setSelectedElement(undefined)
 setSelectedElements([])
app/components/EditorComponent.tsx (3)

19-42: Consider moving setTheme to a separate utility file

The setTheme function is exported but defined within this component file. Since it's a pure utility function that configures Monaco themes, consider moving it to a dedicated utilities file for better code organization.

// New file: lib/editorUtils.ts
import { Monaco } from "@monaco-editor/react"

export const setTheme = (monacoI: Monaco, themeName: string, backgroundColor: string) => {
    // ... existing implementation
}

// Then import in EditorComponent.tsx
import { setTheme } from "@/lib/editorUtils"

611-623: Simplify onChange handler with destructuring and conditional

The current implementation uses two separate conditionals to update the historyQuery state. This can be simplified using destructuring and a conditional expression.

onChange={(val) => {
-    if (!historyQuery.counter) {
-        setHistoryQuery(prev => ({
-            ...prev,
-            currentQuery: val || "",
-            query: val || "",
-        }))
-    } else {
-        setHistoryQuery(prev => ({
-            ...prev,
-            query: val || "",
-        }))
-    }
+    setHistoryQuery(prev => {
+        const newQuery = val || "";
+        return {
+            ...prev,
+            query: newQuery,
+            ...(prev.counter ? {} : { currentQuery: newQuery })
+        };
+    });
}}

682-696: Duplicate state update logic in editor onChange handlers

The same conditional logic for updating history state appears in both editors. Extract this to a shared function to avoid duplication and ensure consistent behavior.

+const handleEditorChange = (val: string | undefined) => {
+    setHistoryQuery(prev => {
+        const newQuery = val || "";
+        return {
+            ...prev,
+            query: newQuery,
+            ...(!prev.counter ? { currentQuery: newQuery } : {})
+        };
+    });
+};

// Then use this in both onChange handlers
-onChange={(val) => { /* existing code */ }}
+onChange={handleEditorChange}
app/graph/GraphView.tsx (1)

86-90: Check current tab value before triggering cooldown

The useEffect for tab changes will run handleCooldown() every time tabsValue changes to "Graph", even if it's already set to "Graph". This could lead to unnecessary cooldown operations.

useEffect(() => {
-   if (tabsValue === "Graph") {
+   if (tabsValue === "Graph" && prevTabValue.current !== "Graph") {
        handleCooldown()
    }
+   prevTabValue.current = tabsValue
}, [tabsValue])

You'd need to add a ref to track the previous tab value:

const prevTabValue = useRef<string>("");
app/schema/SchemaView.tsx (2)

78-81: Optimize dependency arrays in useEffect hooks

The dependency array includes schema.Categories.length and schema.Labels.length, which could cause unnecessary re-renders if the arrays' contents change but their lengths remain the same.

Consider using a more stable dependency or implementing a deep comparison for categories and labels:

useEffect(() => {
    setCategories([...schema.Categories])
    setLabels([...schema.Labels])
-}, [schema.Id, schema.Categories.length, schema.Labels.length])
+}, [schema.Id])

Or use a memo to track if the actual contents have changed.


117-147: Improved error handling for element creation

The onCreateElement function doesn't handle potential errors if schema.extendNode or schema.extendEdge return undefined.

const onCreateElement = async (attributes: [string, string[]][], elementLabel?: string[]) => {
    // ...
    if (result.ok) {
        const json = await result.json()

        if (isAddEntity) {
-           const { category } = schema.extendNode(json.result.data[0].n, false, true)!
+           const extendResult = schema.extendNode(json.result.data[0].n, false, true)
+           if (extendResult) {
+               const { category } = extendResult
                setCategories(prev => [...prev, ...category.map(c => schema.CategoriesMap.get(c)!)])
                setIsAddEntity(false)
+           }
        } else {
-           const { label } = schema.extendEdge(json.result.data[0].e, false, true)!
+           const extendResult = schema.extendEdge(json.result.data[0].e, false, true)
+           if (extendResult) {
+               const { label } = extendResult
                setLabels(prev => [...prev, schema.LabelsMap.get(label)!])
                setIsAddRelation(false)
+           }
        }
        // ...
    }
}
app/graph/Selector.tsx (5)

56-56: Potential fragility in type determination

The type is determined based on the presence of certain props, which could be error-prone if the component is used in different contexts.

Consider making the type explicit:

- const type = runQuery && historyQuery && setHistoryQuery ? "Graph" : "Schema"
+ type Props = {
+   // ...existing props
+   type: "Graph" | "Schema"
+ }

// Then remove the type definition from the component body

This makes the component's behavior more predictable and explicit.


144-152: Guard against null references in handleSubmit

The function uses non-null assertions (!) which could lead to runtime errors if the props are undefined.

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)
    }
}

102-116: Add error handling in focusEditorAtEnd

The function directly manipulates the DOM without any error handling, which could cause issues if the editor structure changes.

const focusEditorAtEnd = () => {
    if (editorRef.current) {
-       editorRef.current.focus();
+       try {
+           editorRef.current.focus();
        
-           const model = editorRef.current.getModel();
+           const model = editorRef.current.getModel();
            if (model) {
                const lastLine = model.getLineCount();
                const lastColumn = model.getLineMaxColumn(lastLine);
                
                editorRef.current.setPosition({ lineNumber: lastLine, column: lastColumn });
                editorRef.current.revealPositionInCenter({ lineNumber: lastLine, column: lastColumn });
            }
+       } catch (error) {
+           console.error("Error focusing editor:", error);
+       }
    }
};

226-237: Optimize query history search callback

The search callback in PaginationList has a condition that could be simplified for better readability.

afterSearchCallback={(newFilteredList) => {
-   if (newFilteredList.every(q => q.text !== historyQuery.query)) {
+   const currentQueryFound = newFilteredList.some(q => q.text === historyQuery.query);
+   if (!currentQueryFound) {
        setHistoryQuery(prev => ({
            ...prev,
            counter: 0
        }))
    }
    focusEditorAtEnd()
}}

167-321: Extract complex conditional rendering into separate components

The component has complex conditional rendering logic that makes it hard to read and maintain. Consider breaking it down into smaller, more focused components.

For example, extract the query editor and history dialog into separate components:

// In a new file QueryEditor.tsx
export function QueryEditor({ 
  graph, 
  maximize, 
  setMaximize, 
  runQuery, 
  historyQuery, 
  setHistoryQuery 
}) {
  // ... editor code
}

// In a new file QueryHistory.tsx
export function QueryHistory({
  open,
  onOpenChange,
  historyQuery,
  setHistoryQuery,
  currentQuery,
  fetchCount,
  handleSubmit,
  isLoading
}) {
  // ... dialog code
}

// Then in Selector.tsx
return (
  <div className="z-20 absolute top-5 inset-x-24 h-[56px] flex flex-row gap-4 items-center">
    <SelectGraph
      options={options}
      // ... other props
    />
    {
      runQuery && historyQuery && setHistoryQuery ? (
        <>
          <QueryEditor
            graph={graph}
            maximize={maximize}
            setMaximize={setMaximize}
            runQuery={runQuery}
            historyQuery={historyQuery}
            setHistoryQuery={setHistoryQuery}
          />
          <QueryHistory
            open={queriesOpen}
            onOpenChange={setQueriesOpen}
            historyQuery={historyQuery}
            setHistoryQuery={setHistoryQuery}
            currentQuery={currentQuery}
            fetchCount={fetchCount}
            handleSubmit={handleSubmit}
            isLoading={isLoading}
          />
        </>
      ) : (
        <Toolbar
          // ... toolbar props
        />
      )
    }
  </div>
);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between fe2fc1f and 891e971.

📒 Files selected for processing (16)
  • app/components/EditorComponent.tsx (8 hunks)
  • app/components/PaginationList.tsx (1 hunks)
  • app/components/ui/Button.tsx (4 hunks)
  • app/components/ui/combobox.tsx (4 hunks)
  • app/graph/GraphDataPanel.tsx (2 hunks)
  • app/graph/GraphDataTable.tsx (1 hunks)
  • app/graph/GraphView.tsx (1 hunks)
  • app/graph/RemoveLabel.tsx (1 hunks)
  • app/graph/Selector.tsx (4 hunks)
  • app/graph/TableView.tsx (1 hunks)
  • app/graph/addLabel.tsx (1 hunks)
  • app/graph/page.tsx (4 hunks)
  • app/graph/selectGraph.tsx (1 hunks)
  • app/graph/toolbar.tsx (1 hunks)
  • app/schema/SchemaView.tsx (2 hunks)
  • app/schema/page.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/components/ui/Button.tsx
  • app/graph/TableView.tsx
🧰 Additional context used
🧬 Code Graph Analysis (2)
app/graph/toolbar.tsx (5)
app/components/ui/Button.tsx (1)
  • Props (11-20)
app/api/graph/model.ts (3)
  • Graph (127-709)
  • Node (45-55)
  • Link (57-70)
lib/utils.ts (3)
  • GraphRef (20-20)
  • handleZoomToFit (132-144)
  • cn (52-54)
components/ui/tooltip.tsx (3)
  • Tooltip (34-34)
  • TooltipTrigger (34-34)
  • TooltipContent (34-34)
app/graph/DeleteElement.tsx (1)
  • DeleteElement (22-80)
app/components/EditorComponent.tsx (4)
app/components/ui/Button.tsx (1)
  • Props (11-20)
app/api/graph/model.ts (2)
  • Graph (127-709)
  • HistoryQuery (9-14)
app/components/provider.ts (1)
  • IndicatorContext (6-6)
app/components/CloseDialog.tsx (1)
  • CloseDialog (13-29)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: build
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Run settings tests
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run tests part 2 (except settings)
🔇 Additional comments (20)
app/graph/RemoveLabel.tsx (3)

1-12: Clean, well-structured component interface.

The component has a clear interface with appropriate props for handling label removal. The optional trigger pattern allows for flexible UI integration.


14-25: Strong default implementation with accessibility consideration.

The default trigger button is properly configured with a descriptive label and disabled state when no label is selected, which prevents invalid operations.


31-41: Well-implemented async handler with proper state management.

The handler correctly manages loading state and handles the async operation flow. The success check ensures the dialog only closes when the operation succeeds.

app/graph/addLabel.tsx (3)

1-12: Well-structured component for label addition.

The component has a clean interface with appropriate props for handling label addition. The optional trigger pattern provides flexibility.


14-23: Good default implementation with clear visual cues.

The default trigger button is properly configured with a descriptive label and visual indicator (Plus icon).


31-44: Well-implemented form submission handler.

The handler correctly prevents default form submission, manages loading state, and properly handles the async flow. Good practice to clear the input and close dialog on success.

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

17-17: Type constraint is appropriately specific.

Limiting the label prop to "Role" or "Type" provides clear type safety and prevents misuse of the component.


25-28: Good implementation of default props within the function signature.

The component handles default props clearly in the function signature while still providing explicit defaults at the end of the file, which is good for documentation.


44-119: Well-structured select component with appropriate accessibility features.

The component provides a robust implementation with tooltips for guidance, proper handling of disabled states, and test IDs for key elements.


64-64: Consistent conditional rendering for tooltip content.

Good use of the indicator context to provide appropriate tooltip content based on server status.


99-116: Well-implemented pagination controls.

The pagination controls provide clear user feedback and appropriate disabling of buttons when they're not applicable.


124-128: Good use of defaultProps for documentation.

Even though defaults are defined in the function signature, the explicit defaultProps helps with documentation and type checking.


30-30:

❌ Incorrect review comment

Verify if openMenage state is necessary.

The component defines an openMenage state that's passed to the Dialog component, but it doesn't appear to be set or used meaningfully in the component.


🏁 Script executed:

#!/bin/bash
# Check if openMenage is used elsewhere in the codebase
grep -r "setOpenMenage" --include="*.tsx" --include="*.ts" app/

Length of output: 710


openMenage state is required for Dialog control

The openMenage flag and its setter are indeed being used to manage the visibility of your Dialog:

  • In app/components/ui/combobox.tsx, you pass
    <Dialog open={openMenage} onOpenChange={setOpenMenage}>
    so the Dialog’s open/close is driven by this state.
  • In app/components/graph/DeleteGraph.tsx, you receive setOpenMenage as a prop and call setOpenMenage(false) to close the Dialog after deletion.
  • Similarly, in app/graph/selectGraph.tsx, the same pattern is used.

Since both openMenage and setOpenMenage are wired into your Dialog components for open/close control, this state is necessary and should be kept.

Likely an incorrect or invalid review comment.

app/components/PaginationList.tsx (4)

8-22: Well-defined generic interface with appropriate props.

The component has a clean, generic interface that handles both string and Query items. The props provide all necessary functionality for pagination, selection, and customization.


42-53: Well-implemented debounced search with appropriate cleanup.

The search implementation uses a timeout for debouncing and properly cleans up on unmount. The callback notification is a good pattern for parent components to react to filtered results.


138-142: Good use of defaultProps for documentation.

The explicit defaultProps helps with documentation and type checking for optional properties.


129-132:

❌ Incorrect review comment

Pagination logic might have edge cases.

The disabled conditions for next/previous buttons might have edge cases that need verification. For example, the "Next page" button is disabled when stepCounter > pageCount - 2, which means it's still enabled when there are no more pages.


🏁 Script executed:

#!/bin/bash
# Test edge cases in pagination logic
echo "Testing edge case where stepCounter = pageCount - 1"
echo "Button should be disabled when stepCounter = pageCount - 1"
echo "Current condition: disabled={stepCounter > pageCount - 2}"
echo "This might mean the button is still enabled when at the last page"

# Recommend thorough testing of these conditions:
echo "1. When there are 0 or 1 pages total"
echo "2. When at the first page"
echo "3. When at the last page"
echo "4. When near the beginning/end for multi-page jumps"

Length of output: 821


Pagination disabled conditions are correct

The expressions

  • disabled={stepCounter > pageCount - 2}
  • disabled={stepCounter > pageCount - 6}

properly disable the “Next page” and “Next 5 pages” buttons once you reach the last page (or when fewer than five pages remain). The corresponding onClick guards also prevent stepping past pageCount - 1. No changes are needed here.

Likely an incorrect or invalid review comment.

app/graph/GraphDataTable.tsx (1)

78-83: Directly mutating object.data and graph can desynchronise React state

object.data[key] = val and delete object.data[key] mutate props passed from the parent and mutate the global graph instance.
If another component relies on the same references, React will not detect the change → stale UI or memo caches.

Consider cloning and passing the new object upward (or exposing dedicated setters on Graph) instead of in-place mutation.
If mutation is unavoidable (performance), wrap it in graph’s own mutator so all observers (e.g. context, redux store) are notified.
[ suggest_optional_refactor ]

Also applies to: 132-135

app/graph/selectGraph.tsx (1)

67-70: Immutable row generation can capture stale session

handleSetRows uses session?.user?.role but is recreated on every render; when the session changes the previously stored row objects retain the old closures.
Add session to the dependency list or compute rows inside the useEffect that already depends on options.

app/graph/GraphDataPanel.tsx (1)

115-159: LGTM! Dialog UI implementation

The implementation of the modal dialog view for the data panel when it has many labels is a good improvement for the user experience. It provides better use of space and maintains all functionality.

Comment on lines +58 to +77
const setProperty = async (key: string, val: string, isUndo: boolean, actionType: ("added" | "set") = "set") => {
const { id } = object
if (!val || val === "") {
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)

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

Missing Content-Type header & ambiguous HTTP verb in setProperty API call

securedFetch sends a JSON body but no Content-Type: application/json header, leaving content negotiation to the server and risking a 415 error.
Additionally, POST is normally used for creation, whereas this branch updates an existing key. PATCH (preferred) or PUT would make the intent clearer.

-const result = await securedFetch(`api/graph/${prepareArg(graph.Id)}/${id}/${key}`, {
-    method: "POST",
-    body: JSON.stringify({ value: val, type })
-}, toast, setIndicator)
+const result = await securedFetch(`api/graph/${prepareArg(graph.Id)}/${id}/${key}`, {
+    method: "PATCH",
+    headers: { "Content-Type": "application/json" },
+    body: JSON.stringify({ value: val, type })
+}, toast, setIndicator)
📝 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 setProperty = async (key: string, val: string, isUndo: boolean, actionType: ("added" | "set") = "set") => {
const { id } = object
if (!val || val === "") {
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)
const setProperty = async (key: string, val: string, isUndo: boolean, actionType: ("added" | "set") = "set") => {
const { id } = object
if (!val || val === "") {
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: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
value: val,
type
})
},
toast,
setIndicator
)
🤖 Prompt for AI Agents
In app/graph/GraphDataTable.tsx between lines 58 and 77, the securedFetch call
sends a JSON body without specifying the Content-Type header, which can cause
server errors. Add a headers object with 'Content-Type': 'application/json' to
the fetch options. Also, change the HTTP method from 'POST' to 'PATCH' to better
reflect that this request updates an existing property rather than creating a
new one.

Comment on lines +46 to +65
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)

if (result.ok) {

const newOptions = options.map((opt) => opt === optionName ? option : opt)
setOptions!(newOptions)

if (setSelectedValue && optionName === selectedValue) setSelectedValue(option)

handleSetRows(newOptions)
}

return result.ok
}
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 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.

🤖 Prompt for AI Agents
In app/graph/selectGraph.tsx between lines 46 and 65, the PATCH request sets the
Content-Type header to application/json but does not include a request body,
which can cause CORS pre-flight issues. To fix this, either add a JSON body with
the updated data (e.g., { newName: option }) or remove the Content-Type header
entirely since the request only uses query parameters.

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

♻️ Duplicate comments (5)
app/schema/page.tsx (3)

87-94: ⚠️ Potential issue

Avoid mutating React state directly

stateSelectedElements.push(selectedElement) mutates the original array instance if selectedElements is empty (because Object.values returns the same reference for arrays). Mutating state outside setState breaks React's immutability guarantees and may cause subtle rendering bugs.

-        const stateSelectedElements = Object.values(selectedElements)
+        let stateSelectedElements = [...selectedElements]

         if (stateSelectedElements.length === 0 && selectedElement) {
-            stateSelectedElements.push(selectedElement)
+            stateSelectedElements = [...stateSelectedElements, selectedElement]
             setSelectedElement(undefined)
         }

95-104: ⚠️ Potential issue

Add error handling for API calls in handleDeleteElement

The Promise.all doesn't handle API failures, and individual fetch operations don't have proper error handling beyond checking result.ok.

await Promise.all(stateSelectedElements.map(async (element) => {
    const { id } = element
    const type = !("source" in element)
-   const result = await securedFetch(`api/schema/${prepareArg(schema.Id)}/${prepareArg(id.toString())}`, {
-       method: "DELETE",
-       body: JSON.stringify({ type }),
-   }, toast, setIndicator)
+   try {
+       const result = await securedFetch(`api/schema/${prepareArg(schema.Id)}/${prepareArg(id.toString())}`, {
+           method: "DELETE",
+           body: JSON.stringify({ type }),
+       }, toast, setIndicator)
+       
+       if (!result.ok) {
+           throw new Error(`Failed to delete element: ${result.statusText}`)
+       }
+   } catch (error) {
+       console.error("Error deleting element:", error)
+       toast({
+           title: "Error",
+           description: `Failed to delete element: ${error.message || "Unknown error"}`,
+           variant: "destructive"
+       })
+       return  // Continue with other deletions even if one fails
+   }
-   if (!result.ok) return

140-140: ⚠️ Potential issue

stateSelectedElements vs selectedElements – wrong collection ends up in removeLinks

You build a local stateSelectedElements copy, possibly augment it with selectedElement, but the subsequent logic (schema.removeLinks) still references the old selectedElements array. As a result, links belonging only to the additionally-pushed element are never removed.

-        schema.removeLinks(setLabels, selectedElements.map((element) => element.id))
+        schema.removeLinks(setLabels, stateSelectedElements.map((element) => element.id))
app/graph/page.tsx (2)

126-137: ⚠️ Potential issue

handleCooldown treats undefined as "running"

When called with no argument (e.g., after runQuery), ticks is undefined, yet the else-branch flags the canvas as running. Pass an explicit 0 or guard for undefined to reflect the real engine state.

        if (ticks === 0) {
            canvas.setAttribute('data-engine-status', 'stop')
        } else {
            canvas.setAttribute('data-engine-status', 'running')
        }

Proper fix:

-       if (ticks === 0) {
+       if (ticks === undefined || ticks === 0) {
            canvas.setAttribute('data-engine-status', 'stop')
        } else {
            canvas.setAttribute('data-engine-status', 'running')
        }

165-169: ⚠️ Potential issue

State array mutated in place – breaks React invariants

selectedElements.push(selectedElement) alters the current state array without using the setter. This may not trigger rerenders and causes unpredictable behavior.

-        if (selectedElements.length === 0 && selectedElement) {
-            selectedElements.push(selectedElement)
-            setSelectedElement(undefined)
-        }
+        let elementsToDelete = [...selectedElements];
+        if (elementsToDelete.length === 0 && selectedElement) {
+            elementsToDelete = [...elementsToDelete, selectedElement];
+            setSelectedElement(undefined)
+        }

Make sure to use elementsToDelete instead of selectedElements throughout the rest of the function.

🧹 Nitpick comments (5)
app/api/schema/[schema]/count/route.ts (1)

18-23: Good refactoring approach, but consider query size limitations

The approach of centralizing graph querying through the internal API endpoint is good for consistency and maintainability. However, be aware that very long Cypher queries might exceed URL length limits when passed as query parameters.

For more complex queries, consider using a POST request with the query in the request body:

-        const result = await fetch(`${request.nextUrl.origin}/api/graph/${schemaName}/?query=${encodeURIComponent(query)}`, {
-            method: "GET",
+        const result = await fetch(`${request.nextUrl.origin}/api/graph/${schemaName}/`, {
+            method: "POST",
             headers: {
                 cookie: request.headers.get('cookie') || '',
+                'Content-Type': 'application/json',
             }
+            body: JSON.stringify({ query })
         })
app/schema/page.tsx (2)

44-54: Add a retry limit to prevent infinite polling

While the polling mechanism is functional, it could benefit from a maximum retry limit to avoid potential infinite loops in case of persistent partial results.

+ let retryCount = 0;
+ const MAX_RETRIES = 5;
  let json = await result.json()

- while (typeof json.result === "number") {
+ while (typeof json.result === "number" && retryCount < MAX_RETRIES) {
+     retryCount++;

147-147: Missing success notification after element deletion

Unlike the graph page implementation that provides a success toast after deletion, the schema page doesn't notify users of successful deletion operations.

        setSelectedElement(undefined)
        setSelectedElements([])
        setData({ ...schema.Elements })
+       toast({
+           title: "Success",
+           description: `${stateSelectedElements.length > 1 ? "Elements" : "Element"} deleted`,
+       })
app/graph/page.tsx (2)

58-68: Add a retry limit to prevent infinite polling

Similar to the schema page, the polling logic should have a maximum retry limit to prevent potential infinite loops.

+ let retryCount = 0;
+ const MAX_RETRIES = 5;
  let json = await result.json()

- while (typeof json.result === "number") {
+ while (typeof json.result === "number" && retryCount < MAX_RETRIES) {
+     retryCount++;

223-224: Redundant state setting

setSelectedElement(undefined) is called twice (lines 213 and 223) which is redundant. Also, setSelectedElements([]) is called twice (lines 212 and 224).

        handleCooldown()
-       setSelectedElement(undefined)
-       setSelectedElements([])
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 891e971 and b2a28de.

📒 Files selected for processing (6)
  • app/api/graph/[graph]/count/route.ts (1 hunks)
  • app/api/schema/[schema]/count/route.ts (1 hunks)
  • app/components/PaginationList.tsx (1 hunks)
  • app/graph/page.tsx (4 hunks)
  • app/graph/selectGraph.tsx (1 hunks)
  • app/schema/page.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/api/graph/[graph]/count/route.ts
  • app/components/PaginationList.tsx
  • app/graph/selectGraph.tsx
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Run tests part 2 (except settings)
  • GitHub Check: Run settings tests
🔇 Additional comments (5)
app/schema/page.tsx (3)

16-30: State initialization looks good with React Context integration

The component successfully transitions from local state to React Context for schema-related state management, and properly initializes component-specific state for multi-selection, cooldown, and other UI features.


152-165: Component composition with props drilling looks clean

The Selector component receives all necessary props for managing selections and handling element deletion, which promotes good component composition.


166-189: SchemaView receives proper props for multi-selection and element management

The SchemaView component is now correctly configured with all props needed for multi-selection, element deletion, and cooldown handling.

app/graph/page.tsx (2)

229-244: Clean and consistent Selector component props

The Selector component receives appropriate props for managing selections, history, and handling element deletion.


246-264: GraphView receives comprehensive props for multi-selection and state management

The GraphView component now receives all necessary props for handling multi-selection, cooldown, and element deletion.

Comment on lines 29 to 31
const data = json.result.data[0]

return NextResponse.json({ result }, { status: 200 })
return NextResponse.json({ result: data }, { status: 200 })
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 response structure validation

The code assumes a specific response structure without validation, which could break if the API response format changes.

Add validation before accessing nested properties:

         const json = await result.json()
-        const data = json.result.data[0]
+        if (!json.result?.data?.length) {
+            throw new Error("Unexpected response format: missing result data");
+        }
+        const data = json.result.data[0]
         
         return NextResponse.json({ result: data }, { status: 200 })
📝 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 = json.result.data[0]
return NextResponse.json({ result }, { status: 200 })
return NextResponse.json({ result: data }, { status: 200 })
const json = await result.json()
if (!json.result?.data?.length) {
throw new Error("Unexpected response format: missing result data")
}
const data = json.result.data[0]
return NextResponse.json({ result: data }, { status: 200 })
🤖 Prompt for AI Agents
In app/api/schema/[schema]/count/route.ts around lines 29 to 31, the code
directly accesses nested properties of the API response without validating the
structure, which risks runtime errors if the response format changes. Add checks
to confirm that json.result and json.result.data exist and that data is an array
with at least one element before accessing json.result.data[0]. If validation
fails, handle the error gracefully, such as returning an appropriate error
response or default value.

}
})

if (!result.ok) throw new Error("Something went wrong")
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

Improve error handling with more specific messaging

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

Include error details from the response for better troubleshooting:

-        if (!result.ok) throw new Error("Something went wrong")
+        if (!result.ok) {
+            const errorText = await result.text();
+            throw new Error(`Failed to fetch graph data: ${result.status} ${result.statusText}. ${errorText}`);
+        }
📝 Committable suggestion

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

Suggested change
if (!result.ok) throw new Error("Something went wrong")
if (!result.ok) {
const errorText = await result.text();
throw new Error(`Failed to fetch graph data: ${result.status} ${result.statusText}. ${errorText}`);
}
🤖 Prompt for AI Agents
In app/api/schema/[schema]/count/route.ts at line 25, the error handling uses a
generic message "Something went wrong" which lacks useful debugging information.
Modify the error throw to include specific error details from the response
object, such as an error message or status code, to provide clearer context for
troubleshooting.

Comment on lines +171 to +207
await Promise.all(selectedElements.map(async (element) => {
const type = !element.source
const result = await securedFetch(`api/graph/${prepareArg(graph.Id)}/${prepareArg(element.id.toString())}`, {
method: "DELETE",
body: JSON.stringify({ type })
}, toast, setIndicator)

if (!result.ok) return

if (type) {
(element as Node).category.forEach((category) => {
const cat = graph.CategoriesMap.get(category)
if (cat) {
cat.elements = cat.elements.filter((e) => e.id !== element.id)
if (cat.elements.length === 0) {
const index = graph.Categories.findIndex(c => c.name === cat.name)
if (index !== -1) {
graph.Categories.splice(index, 1)
graph.CategoriesMap.delete(cat.name)
}
}
}
})
} else {
const category = graph.LabelsMap.get((element as Link).label)
if (category) {
category.elements = category.elements.filter((e) => e.id !== element.id)
if (category.elements.length === 0) {
const index = graph.Labels.findIndex(l => l.name === category.name)
if (index !== -1) {
graph.Labels.splice(index, 1)
graph.LabelsMap.delete(category.name)
}
}
}
}
}))
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 error handling for API calls in handleDeleteElement

Similar to the schema page, the Promise.all for element deletion lacks proper error handling, which could lead to silent failures.

await Promise.all(selectedElements.map(async (element) => {
    const type = !element.source
-   const result = await securedFetch(`api/graph/${prepareArg(graph.Id)}/${prepareArg(element.id.toString())}`, {
-       method: "DELETE",
-       body: JSON.stringify({ type })
-   }, toast, setIndicator)
+   try {
+       const result = await securedFetch(`api/graph/${prepareArg(graph.Id)}/${prepareArg(element.id.toString())}`, {
+           method: "DELETE",
+           body: JSON.stringify({ type })
+       }, toast, setIndicator)
+       
+       if (!result.ok) {
+           throw new Error(`Failed to delete element: ${result.statusText}`)
+       }
+   } catch (error) {
+       console.error("Error deleting element:", error)
+       toast({
+           title: "Error",
+           description: `Failed to delete element: ${error.message || "Unknown error"}`,
+           variant: "destructive"
+       })
+       return  // Continue with other deletions even if one fails
+   }
-   if (!result.ok) return
📝 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
await Promise.all(selectedElements.map(async (element) => {
const type = !element.source
const result = await securedFetch(`api/graph/${prepareArg(graph.Id)}/${prepareArg(element.id.toString())}`, {
method: "DELETE",
body: JSON.stringify({ type })
}, toast, setIndicator)
if (!result.ok) return
if (type) {
(element as Node).category.forEach((category) => {
const cat = graph.CategoriesMap.get(category)
if (cat) {
cat.elements = cat.elements.filter((e) => e.id !== element.id)
if (cat.elements.length === 0) {
const index = graph.Categories.findIndex(c => c.name === cat.name)
if (index !== -1) {
graph.Categories.splice(index, 1)
graph.CategoriesMap.delete(cat.name)
}
}
}
})
} else {
const category = graph.LabelsMap.get((element as Link).label)
if (category) {
category.elements = category.elements.filter((e) => e.id !== element.id)
if (category.elements.length === 0) {
const index = graph.Labels.findIndex(l => l.name === category.name)
if (index !== -1) {
graph.Labels.splice(index, 1)
graph.LabelsMap.delete(category.name)
}
}
}
}
}))
await Promise.all(selectedElements.map(async (element) => {
const type = !element.source
try {
const result = await securedFetch(
`api/graph/${prepareArg(graph.Id)}/${prepareArg(element.id.toString())}`,
{
method: "DELETE",
body: JSON.stringify({ type })
},
toast,
setIndicator
)
if (!result.ok) {
throw new Error(`Failed to delete element: ${result.statusText}`)
}
} catch (error) {
console.error("Error deleting element:", error)
toast({
title: "Error",
description: `Failed to delete element: ${error.message || "Unknown error"}`,
variant: "destructive"
})
return
}
if (type) {
(element as Node).category.forEach((category) => {
const cat = graph.CategoriesMap.get(category)
if (cat) {
cat.elements = cat.elements.filter((e) => e.id !== element.id)
if (cat.elements.length === 0) {
const index = graph.Categories.findIndex(c => c.name === cat.name)
if (index !== -1) {
graph.Categories.splice(index, 1)
graph.CategoriesMap.delete(cat.name)
}
}
}
})
} else {
const category = graph.LabelsMap.get((element as Link).label)
if (category) {
category.elements = category.elements.filter((e) => e.id !== element.id)
if (category.elements.length === 0) {
const index = graph.Labels.findIndex(l => l.name === category.name)
if (index !== -1) {
graph.Labels.splice(index, 1)
graph.LabelsMap.delete(category.name)
}
}
}
}
}))
🤖 Prompt for AI Agents
In app/graph/page.tsx around lines 171 to 207, the Promise.all call deleting
elements does not handle errors from the API calls, risking silent failures.
Wrap the securedFetch call in a try-catch block inside the map callback to catch
any exceptions. In the catch block, log the error or use the toast to notify the
user of the failure. This ensures any API call errors are properly handled and
surfaced.

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') || '',
}
})

Check failure

Code scanning / CodeQL

Server-side request forgery

The [URL](1) of this request depends on a [user-provided value](2).

Copilot Autofix

AI about 20 hours ago

To fix the issue, we need to validate and sanitize the graph parameter before using it in the URL. The best approach is to restrict the graph parameter to a predefined set of allowed values (an allow-list). This ensures that only expected and safe values are used in the URL. If the graph parameter does not match any of the allowed values, the request should be rejected.

Steps to implement the fix:

  1. Define an allow-list of valid graph values.
  2. Check if the graph parameter matches one of the allowed values.
  3. If the graph parameter is invalid, return an error response.
  4. Use the validated graph value in the URL.

Suggested changeset 1
app/api/graph/[graph]/count/route.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/app/api/graph/[graph]/count/route.ts b/app/api/graph/[graph]/count/route.ts
--- a/app/api/graph/[graph]/count/route.ts
+++ b/app/api/graph/[graph]/count/route.ts
@@ -13,2 +13,9 @@
 
+    // Define an allow-list of valid graph names
+    const allowedGraphs = ["graph1", "graph2", "graph3"];
+
+    if (!allowedGraphs.includes(graph)) {
+        return NextResponse.json({ error: "Invalid graph parameter" }, { status: 400 });
+    }
+
     try {
EOF
@@ -13,2 +13,9 @@

// Define an allow-list of valid graph names
const allowedGraphs = ["graph1", "graph2", "graph3"];

if (!allowedGraphs.includes(graph)) {
return NextResponse.json({ error: "Invalid graph parameter" }, { status: 400 });
}

try {
Copilot is powered by AI and may make mistakes. Always verify output.
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') || '',
}
})

Check failure

Code scanning / CodeQL

Server-side request forgery

The [URL](1) of this request depends on a [user-provided value](2).

Copilot Autofix

AI about 20 hours ago

To fix the SSRF vulnerability:

  1. Restrict the schema parameter to a predefined allow-list of valid schema names. This ensures that only known and safe values are used in the URL.
  2. Avoid directly using request.nextUrl.origin in the outgoing request. Instead, use a fixed, trusted base URL for the API endpoint.
  3. Validate and sanitize all user-controlled inputs before incorporating them into the URL.

The changes will involve:

  • Adding an allow-list of valid schema names.
  • Replacing request.nextUrl.origin with a trusted base URL.
  • Updating the fetch call to use the validated schemaName.

Suggested changeset 1
app/api/schema/[schema]/count/route.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/app/api/schema/[schema]/count/route.ts b/app/api/schema/[schema]/count/route.ts
--- a/app/api/schema/[schema]/count/route.ts
+++ b/app/api/schema/[schema]/count/route.ts
@@ -12,8 +12,13 @@
     const { schema } = await params
-    const schemaName = `${schema}_schema`
+    const allowedSchemas = ["schema1", "schema2", "schema3"]; // Define valid schema names
+    if (!allowedSchemas.includes(schema)) {
+        return NextResponse.json({ error: "Invalid schema" }, { status: 400 });
+    }
+    const schemaName = `${schema}_schema`;
 
     try {
-        const query = "MATCH (n) OPTIONAL MATCH (n)-[e]->() WITH count(n) as nodes, count(e) as edges RETURN nodes, edges"
+        const query = "MATCH (n) OPTIONAL MATCH (n)-[e]->() WITH count(n) as nodes, count(e) as edges RETURN nodes, edges";
+        const baseUrl = "https://trusted-api.example.com"; // Use a fixed, trusted base URL
 
-        const result = await fetch(`${request.nextUrl.origin}/api/graph/${schemaName}/?query=${encodeURIComponent(query)}`, {
+        const result = await fetch(`${baseUrl}/api/graph/${schemaName}/?query=${encodeURIComponent(query)}`, {
             method: "GET",
EOF
@@ -12,8 +12,13 @@
const { schema } = await params
const schemaName = `${schema}_schema`
const allowedSchemas = ["schema1", "schema2", "schema3"]; // Define valid schema names
if (!allowedSchemas.includes(schema)) {
return NextResponse.json({ error: "Invalid schema" }, { status: 400 });
}
const schemaName = `${schema}_schema`;

try {
const query = "MATCH (n) OPTIONAL MATCH (n)-[e]->() WITH count(n) as nodes, count(e) as edges RETURN nodes, edges"
const query = "MATCH (n) OPTIONAL MATCH (n)-[e]->() WITH count(n) as nodes, count(e) as edges RETURN nodes, edges";
const baseUrl = "https://trusted-api.example.com"; // Use a fixed, trusted base URL

const result = await fetch(`${request.nextUrl.origin}/api/graph/${schemaName}/?query=${encodeURIComponent(query)}`, {
const result = await fetch(`${baseUrl}/api/graph/${schemaName}/?query=${encodeURIComponent(query)}`, {
method: "GET",
Copilot is powered by AI and may make mistakes. Always verify output.
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 (10)
app/api/graph/[graph]/count/route.ts (1)

17-22: ⚠️ Potential issue

Address SSRF vulnerability by validating the graph parameter

The URL construction uses the user-provided graph parameter directly without validation, creating a potential Server-Side Request Forgery (SSRF) vulnerability.

Apply this fix to validate the graph parameter before using it in a URL:

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

const result = await fetch(`${request.nextUrl.origin}/api/graph/${graph}/?query=${encodeURIComponent(query)}`, {
🧰 Tools
🪛 GitHub Check: CodeQL

[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 (3)

18-23: ⚠️ Potential issue

Address SSRF vulnerability by validating the schema parameter

The URL construction uses the user-provided schema parameter directly without validation, creating a potential Server-Side Request Forgery (SSRF) vulnerability.

Apply this fix to validate the schema parameter before using it in a URL:

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

const result = await fetch(`${request.nextUrl.origin}/api/graph/${schemaName}/?query=${encodeURIComponent(query)}`, {
🧰 Tools
🪛 GitHub Check: CodeQL

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


25-25: 🛠️ Refactor suggestion

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 schema data: ${result.status} ${result.statusText}. ${errorText}`);
+ }

29-29: 🛠️ Refactor suggestion

Add validation for response structure

The code accesses nested properties without validating the response structure, risking runtime errors if the format changes.

- const data = typeof json.result === "number" ? json.result : json.result.data[0] || { nodes: 0, edges: 0 }
+ let data = { nodes: 0, edges: 0 };
+ if (typeof json.result === "number") {
+   data = json.result;
+ } else if (json.result?.data?.length > 0) {
+   data = json.result.data[0];
+ }
app/schema/page.tsx (3)

91-95: ⚠️ Potential issue

Avoid mutating React state directly

Directly pushing to stateSelectedElements could mutate the original array if selectedElements is empty, breaking React's immutability principles.

- const stateSelectedElements = Object.values(selectedElements)
+ let stateSelectedElements = [...selectedElements]

if (stateSelectedElements.length === 0 && selectedElement) {
-    stateSelectedElements.push(selectedElement)
+    stateSelectedElements = [...stateSelectedElements, selectedElement]
     setSelectedElement(undefined)
}

142-142: ⚠️ Potential issue

Fix inconsistent state reference

selectedElements is used here, but the deletion logic uses stateSelectedElements, which could cause inconsistencies if they differ.

- schema.removeLinks(setLabels, selectedElements.map((element) => element.id))
+ schema.removeLinks(setLabels, stateSelectedElements.map((element) => element.id))

97-105: 🛠️ Refactor suggestion

Add proper error handling for API calls

The API call lacks proper error handling, which could lead to silent failures and inconsistent application state.

await Promise.all(stateSelectedElements.map(async (element) => {
    const { id } = element
    const type = !("source" in element)
-   const result = await securedFetch(`api/schema/${prepareArg(schema.Id)}/${prepareArg(id.toString())}`, {
-       method: "DELETE",
-       body: JSON.stringify({ type }),
-   }, toast, setIndicator)
+   try {
+       const result = await securedFetch(`api/schema/${prepareArg(schema.Id)}/${prepareArg(id.toString())}`, {
+           method: "DELETE",
+           body: JSON.stringify({ type }),
+       }, toast, setIndicator)
+       
+       if (!result.ok) {
+           throw new Error(`Failed to delete element: ${result.statusText}`)
+       }
+   } catch (error) {
+       console.error("Error deleting element:", error)
+       toast({
+           title: "Error",
+           description: `Failed to delete element: ${(error as Error).message || "Unknown error"}`,
+           variant: "destructive"
+       })
+       return  // Continue with other deletions even if one fails
+   }
-   if (!result.ok) return
app/graph/page.tsx (3)

167-171: ⚠️ Potential issue

Avoid mutating React state directly

Directly pushing to selectedElements breaks React's immutability principles and may cause unpredictable behavior.

const handleDeleteElement = async () => {
    if (selectedElements.length === 0 && selectedElement) {
-       selectedElements.push(selectedElement)
+       const newSelectedElements = [...selectedElements, selectedElement]
+       setSelectedElements(newSelectedElements)
        setSelectedElement(undefined)
+       
+       // Continue with deletion using the new array
+       await deleteSelectedElements(newSelectedElements)
+       return
    }
+   
+   await deleteSelectedElements(selectedElements)
}

+ // Helper function to encapsulate the deletion logic
+ const deleteSelectedElements = async (elementsToDelete) => {
+   // Move the deletion logic here, using elementsToDelete instead of selectedElements
+   // ...
+ }

128-139: ⚠️ Potential issue

Fix incorrect engine status handling for undefined ticks

The function doesn't properly handle undefined ticks, incorrectly marking the engine as running.

const handleCooldown = (ticks?: number) => {
    setCooldownTicks(ticks)

    const canvas = document.querySelector('.force-graph-container canvas');
    if (!canvas) return
-   if (ticks === 0) {
+   if (ticks === undefined || ticks === 0) {
        canvas.setAttribute('data-engine-status', 'stop')
    } else {
        canvas.setAttribute('data-engine-status', 'running')
    }
}

173-180: 🛠️ Refactor suggestion

Add proper error handling for API calls

The API call lacks proper error handling, which could lead to silent failures and inconsistent application state.

await Promise.all(selectedElements.map(async (element) => {
    const type = !element.source
-   const result = await securedFetch(`api/graph/${prepareArg(graph.Id)}/${prepareArg(element.id.toString())}`, {
-       method: "DELETE",
-       body: JSON.stringify({ type })
-   }, toast, setIndicator)
+   try {
+       const result = await securedFetch(`api/graph/${prepareArg(graph.Id)}/${prepareArg(element.id.toString())}`, {
+           method: "DELETE",
+           body: JSON.stringify({ type })
+       }, toast, setIndicator)
+       
+       if (!result.ok) {
+           throw new Error(`Failed to delete element: ${result.statusText}`)
+       }
+   } catch (error) {
+       console.error("Error deleting element:", error)
+       toast({
+           title: "Error",
+           description: `Failed to delete element: ${(error as Error).message || "Unknown error"}`,
+           variant: "destructive"
+       })
+       return  // Continue with other deletions even if one fails
+   }
-   if (!result.ok) return
📜 Review details

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

📥 Commits

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

📒 Files selected for processing (5)
  • app/api/graph/[graph]/count/route.ts (1 hunks)
  • app/api/schema/[schema]/count/route.ts (1 hunks)
  • app/graph/GraphDetails.tsx (1 hunks)
  • app/graph/page.tsx (4 hunks)
  • app/schema/page.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/graph/GraphDetails.tsx
🧰 Additional context used
🪛 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 1 (except settings)
  • GitHub Check: Run tests part 2 (except settings)
  • GitHub Check: Run settings tests
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run tests part 2 (except settings)

}
})

if (!result.ok) throw new Error("Something went wrong")
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

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}`);
+ }
📝 Committable suggestion

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

Suggested change
if (!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}`);
}
🤖 Prompt for AI Agents
In app/api/graph/[graph]/count/route.ts at line 24, the error thrown uses a
generic message "Something went wrong" which lacks specific details. Modify the
error handling to include more specific information from the result object, such
as an error message or status code, to provide clearer context for debugging
when the operation fails.


const json = await result.json()

const data = typeof json.result === "number" ? json.result : 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.

🛠️ Refactor suggestion

Add validation for response structure

The code accesses nested properties without validating the response structure, risking runtime errors if the format changes.

- const data = typeof json.result === "number" ? json.result : json.result.data[0] || { edges: 0, nodes: 0 }
+ let data = { edges: 0, nodes: 0 };
+ if (typeof json.result === "number") {
+   data = json.result;
+ } else if (json.result?.data?.length > 0) {
+   data = json.result.data[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
const data = typeof json.result === "number" ? json.result : json.result.data[0] || { edges: 0, nodes: 0 }
// Replace the original one‐liner with explicit checks
let data = { edges: 0, nodes: 0 };
if (typeof json.result === "number") {
data = json.result;
} else if (json.result?.data?.length > 0) {
data = json.result.data[0];
}
🤖 Prompt for AI Agents
In app/api/graph/[graph]/count/route.ts at line 28, the code accesses nested
properties of the response without validating the structure, which can cause
runtime errors if the response format changes. Add checks to confirm that
json.result exists and has the expected properties before accessing
json.result.data[0]. Use conditional statements or optional chaining combined
with default values to safely handle unexpected or missing data structures.

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 4 commits May 22, 2025 13:19
…current path. Add Escape key listener in Settings component to navigate back when pressed.
…-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.

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