-
Notifications
You must be signed in to change notification settings - Fork 8
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
base: main
Are you sure you want to change the base?
Conversation
…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.
…and schema API routes
…rdb-browser into fix-vulnerability
…QuerySettings component. Add methods for decreasing limits and timeouts, and update tests to validate behavior for negative values and button interactions.
… with other tests
Fix #887 Implement role-based query handling for Read-Only users across graph and schema API routes
fix api vulnerability
Fix #898 Enhance QuerySettings component with tooltips for Timeout and Limit
Fix #901 fix duplicate
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughThis update refactors the application's architecture, UI, API routes, and testing framework. Major changes include context-based state management for graphs and schemas, role-based query execution, UI component redesigns, enhanced test automation with page objects, and security headers. It also updates package versions and introduces new features like multi-selection and batch deletion. Changes
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
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
Possibly related PRs
Suggested labels
Poem
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
npm warn config production Use ✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🔭 Outside diff range comments (2)
app/api/schema/[schema]/[node]/route.ts (1)
52-67
:⚠️ Potential issueConsider 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 issueMissing 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
📒 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 consistencyThis 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 practiceThe 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 practiceThe 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
anduser
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
anduser
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
anduser
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
anduser
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
anduser
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 selectionThe 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 catLength 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
fromrequest.nextUrl.searchParams
and do not callrequest.json()
. No JSON body is expected or used—yoursecuredFetch
call is correct as-is.Likely an incorrect or invalid review comment.
app/components/graph/DeleteGraph.tsx (1)
42-42
: Improved selection handling after deletionThe 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 executionThe 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 goodThe change from
addTimeout
tofillTimeout
better reflects the action of setting a specific timeout value.
43-43
: Method name update looks goodThe change from
addLimit
tofillLimit
better reflects the action of setting a specific limit value.
51-51
: Good addition of settings navigationAdding
clickOnSettings()
after verifying limited query results provides a cleaner test flow and properly closes the interaction cycle.
54-97
: Well-structured boundary testsThese 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 endpointThe 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 goodThe 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 endpointThe 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 conventionGood 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 executionThe 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 controlThe 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 buttonsThese 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 buttonsThese 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 methodsThe
fillLimit
andfillTimeout
methods provide a clean API for setting numeric values in the inputs, simplifying the test code.
63-77
: Well-designed wrapper methods for interactionsThese 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 assertionsThe
getLimit
andgetTimeout
methods will be valuable for assertions in tests, allowing verification of input values after interactions.
app/components/graph/DeleteGraph.tsx
Outdated
|
||
setOpen(false) | ||
setOpenMenage(false) | ||
handleSetRows(options.filter(opt => !opts.includes(opt))) | ||
handleSetRows(options.filter(opt => newNames.includes(opt))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
try { | ||
if (!sourceName) throw new Error("Missing parameter sourceName") | ||
|
||
const result = await client.selectGraph(sourceName).copy(graphId) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 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.
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) |
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 }) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 } | |
) | |
} | |
} |
add tutorial tests (#879)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (4)
app/graph/GraphDataPanel.tsx (2)
84-86
: State updates should use immutable patternsThe 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 labelsSimilar 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 filterThe current implementation could crash if any value in
el.data
is null or undefined sincetoString()
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 changeIf the previous index is larger than the new suggestions list's length,
suggestions[suggestionIndex]
could becomeundefined
, 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 complexityThe 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 viewAlso applies to: 160-236
44-48
: Consider debouncing the dialog decisionThe 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 hookThe 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 logicThe current implementation of
handleSearchElement
looks good, but the filter function passed tohandleZoomToFit
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
📒 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 managementThe 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 stateUsing 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 feedbackThe 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 GraphDataTableExtracting 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 permissionsThe 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 attributesAdding 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 implementationThe
scrollToSuggestion
function provides proper keyboard navigation by automatically scrolling to the highlighted suggestion, which greatly improves accessibility.
83-200
: Great search interface implementationThe 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 buttonThe 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 dropdownThe 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 defaultPropsProviding default values for optional props is a good practice that improves component reusability and prevents potential runtime errors.
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) | ||
} | ||
}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
…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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
app/graph/selectGraph.tsx (1)
46-52
:PATCH
request lacks a body but still declaresContent-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 directiveThe
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 readabilityThe 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 rotationThe 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
📒 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 implementationThe
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 statesThe
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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
<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.
useEffect(() => { | ||
handleSetRows(options) | ||
}, [options]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
… for better user experience.
…current path. Add Escape key listener in Settings component to navigate back when pressed.
…. Update SelectGraph, Selector, and Toolbar components to use 'h-full' for improved layout and responsiveness.
Fix #951 Enhance EditorComponent functionality.
…-second-click Fix #953 Enhance Header component to toggle settings page navigation based on current path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (5)
app/components/Header.tsx (3)
10-11
: Importing the wholepackage.json
bloats the client bundle & leaks internals
import pkg from '@/package.json'
in a client component ships the entirepackage.json
(including dependency list) to every visitor.
Consider creating a small file that only exports the version string.
37-44
: UsingLink
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 usersWhen
session
is still loading (null/undefined),session?.user?.role !== "Read-Only"
evaluates totrue
, 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 detailsThe 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 messagingThe 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 displayThe 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
📒 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 suggestionProps 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 jsxLength of output: 79
Let’s search for all
<DropdownMenuTrigger>
usages and inspect the surrounding code inHeader.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" fiLength of output: 4720
Remove unnecessary preventDefault on DropdownMenuTrigger
The
onClick={(e) => e.preventDefault()}
on your Help menu trigger is not used in any otherDropdownMenuTrigger
(seeselectGraph.tsx
andtoolbar.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 statesThe 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
app/settings/page.tsx
Outdated
useEffect(() => { | ||
window.addEventListener("keydown", (e) => { | ||
if (e.key === "Escape") { | ||
router.back() | ||
} | ||
}) | ||
|
||
return () => { | ||
window.removeEventListener("keydown", (e) => { | ||
if (e.key === "Escape") { | ||
router.back() | ||
} | ||
}) | ||
} | ||
}, [router]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 }] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
const result = await fetch(`${request.nextUrl.origin}/api/graph/${graph}/?query=${encodeURIComponent(query)}`, { | ||
method: "GET", | ||
headers: { | ||
cookie: request.headers.get('cookie') || '', | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 }] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
const result = await fetch(`${request.nextUrl.origin}/api/graph/${schemaName}/?query=${encodeURIComponent(query)}`, { | ||
method: "GET", | ||
headers: { | ||
cookie: request.headers.get('cookie') || '', | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
…lity and maintainability. Use a named function for event handling and ensure proper cleanup on component unmount.
… padding for button variants and adjust layout for AddLabel and RemoveLabel components for improved UI consistency.
…component height for improved UI consistency.
…ing for Primary and Secondary button variants, and modify button alignment for improved UI consistency.
…same-height Fix #962 Make the selector elements the same height
…he-query-input Fix #951 Refactor EditorComponent layout to group buttons
Fix #953 Refactor Escape key listener in Settings component to improve readability and maintainability.
Fix #981 Make buttons the same height
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
🔭 Outside diff range comments (1)
app/graph/Selector.tsx (1)
90-101
:⚠️ Potential issueRemove redundant useEffect hooks to prevent duplicate API calls.
There are three
useEffect
hooks that callgetOptions()
, which will cause redundant API calls:
- Lines 91-92: Calls on mount and dependency changes
- Lines 94-96: Calls when indicator becomes "online"
- Lines 98-101: Redundant call with empty dependencies
The third
useEffect
(lines 98-101) is unnecessary since the first one already handles the initial mount.Apply this diff to remove the redundant hook:
- useEffect(() => { - getOptions() - }, [])
♻️ Duplicate comments (10)
app/graph/selectGraph.tsx (3)
46-65
: PATCH request lacks body but still declares Content-Type.
71-73
: Missing dependencies in useEffect dependency array.
131-131
: Hardcoded dialog title should use the type prop.app/graph/GraphDataTable.tsx (2)
58-77
: Previous review comment still applies - missing Content-Type header and incorrect HTTP methodThe issues identified in the previous review remain unaddressed. The API call is still missing the
Content-Type: application/json
header and usesPOST
instead ofPATCH
for updating existing properties.
125-128
:⚠️ Potential issueMissing Content-Type header in DELETE request
Similar to the setProperty function, this DELETE request sends a JSON body but doesn't specify the Content-Type header, which could cause server errors.
const success = (await securedFetch(`api/graph/${prepareArg(graph.Id)}/${id}/${key}`, { method: "DELETE", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ type }), }, toast, setIndicator)).ok
app/graph/GraphDataPanel.tsx (2)
84-86
: 🛠️ Refactor suggestionUpdate categories immutably after adding a label
After adding a category to the graph, the state should be updated immutably.
graph.addCategory(newLabel, node) -setCategories([...graph.Categories]) -setLabel([...node.category]) +// Ensure immutable updates +const updatedCategories = graph.Categories.map(cat => ({...cat})); +setCategories(updatedCategories); +setLabel([...node.category]);Alternatively, if
graph.Categories
contains the newly added category:+setCategories(prevCategories => { + const newCategory = graph.Categories.find(c => c.name === newLabel); + return newCategory ? [...prevCategories, {...newCategory}] : prevCategories; +});
104-106
: 🛠️ Refactor suggestionUpdate categories immutably after removing a label
Similar to the add label issue, removing a label should update state immutably.
graph.removeCategory(removeLabel, node) -setCategories([...graph.Categories]) -setLabel([...node.category]) +// Ensure immutable updates +setCategories(prevCategories => + prevCategories.filter(c => c.name !== removeLabel).map(cat => ({...cat})) +); +setLabel(prevLabels => prevLabels.filter(l => l !== removeLabel));app/graph/toolbar.tsx (3)
50-52
:⚠️ Potential issuePotential null/undefined error in search filter
The search filter on line 51 could throw a TypeError when
value
is null or undefined. Consider using a safer approach:-Object.values(el.data).some(value => value.toString().toLowerCase().startsWith(searchElement.toLowerCase())) +Object.values(el.data).some(value => + value != null && String(value).toLowerCase().startsWith(searchElement.toLowerCase()) +)
60-66
: 🛠️ Refactor suggestionReset
suggestionIndex
when the suggestions list changesWhen the suggestions array changes, the current
suggestionIndex
might exceed the new array length, causingsuggestions[suggestionIndex]
to be undefined.useEffect(() => { - const timeout = setTimeout(handleOnChange, 500) + const timeout = setTimeout(() => { + handleOnChange() + setSuggestionIndex(0) + }, 500)
93-118
: 🛠️ Refactor suggestionEliminate duplicated keyboard navigation logic
The keyboard event handling is duplicated between the input element and suggestion list, creating maintenance issues.
Extract the shared logic:
+ const handleKeyboardNavigation = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault() + setSearchElement("") + } + + if (e.key === 'Enter' && suggestions[suggestionIndex]) { + e.preventDefault() + handleSearchElement(suggestions[suggestionIndex]) + setSearchElement("") + } + + if (e.key === 'ArrowDown') { + e.preventDefault() + const index = suggestionIndex === suggestions.length - 1 ? 0 : suggestionIndex + 1 + setSuggestionIndex(index) + scrollToSuggestion(index) + } + + if (e.key === 'ArrowUp') { + e.preventDefault() + const index = suggestionIndex === 0 ? suggestions.length - 1 : suggestionIndex - 1 + setSuggestionIndex(index) + scrollToSuggestion(index) + } + }Then replace both handlers with:
- onKeyDown={(e) => { /* ... duplicate code ... */ }} + onKeyDown={handleKeyboardNavigation}Also applies to: 137-162
🧹 Nitpick comments (13)
app/graph/selectGraph.tsx (3)
1-1
: Consider addressing the ESLint rule instead of disabling it.The ESLint disable comment suggests there might be a code organization issue. Consider restructuring the code to avoid the "no-use-before-define" rule violation rather than disabling it.
-/* eslint-disable @typescript-eslint/no-use-before-define */
Move function definitions above their usage or consider extracting helper functions to separate modules.
110-119
: Hardcoded label prop should be dynamic based on type.The
label
prop inPaginationList
is hardcoded as "graph" but should reflect the actualtype
being managed.-label="graph" +label={type.toLowerCase()}
38-38
: Typo in state variable name.The state variable
openMenage
appears to be a typo. It should beopenManage
.-const [openMenage, setOpenMenage] = useState(false) +const [openManage, setOpenManage] = useState(false)And update all references throughout the component:
-<Dialog open={openMenage} onOpenChange={setOpenMenage}> +<Dialog open={openManage} onOpenChange={setOpenManage}> -setOpenMenage={setOpenMenage} +setOpenManage={setOpenManage}app/graph/GraphDataTable.tsx (2)
25-379
: Consider breaking down this large component into smaller, focused componentsThis component handles multiple responsibilities: table rendering, inline editing, add/delete operations, and API calls. Consider extracting smaller components for better maintainability and testability.
Potential extractions:
AttributeRow
component for individual table rowsAddAttributeRow
component for the add new attribute functionalityAttributeActions
component for the edit/delete buttons- Custom hooks for API operations (
useAttributeOperations
)This would improve code organization, make unit testing easier, and enhance reusability.
39-47
: Optimize useEffect dependencies to prevent unnecessary re-rendersThe effect has
setAttributes
in the dependency array, butsetAttributes
is a state setter that doesn't change between renders. This creates an unnecessary dependency.useEffect(() => { if (lastObjId.current !== object.id) { setEditable("") setNewVal("") setNewKey("") setIsAddValue(false) } setAttributes(Object.keys(object.data)) -}, [lastObjId, object, setAttributes, type]) +}, [lastObjId, object, type])app/graph/addLabel.tsx (1)
15-21
: Consider adding a meaningful title to the default trigger button.The default trigger button has an empty
title
attribute, which reduces accessibility for screen readers and tooltips.trigger = <Button variant="Primary" label="Add Label" - title="" + title="Open add label dialog" > <Plus /> </Button>,app/graph/Selector.tsx (1)
42-324
: Consider breaking down this component for better maintainability.The
Selector
component is handling multiple responsibilities and has grown quite large (300+ lines). Consider extracting some logic into separate components:
- Query history dialog (lines 190-296) could be a separate component
- Editor configuration and setup (lines 118-143, 257-283) could be extracted
- The conditional rendering logic for Graph vs Schema mode could be simplified
This would improve readability, testability, and maintainability.
app/graph/RemoveLabel.tsx (3)
16-18
: Fix inconsistent label terminology.There's inconsistency between the default trigger button label ("Delete Label") and the confirm button label ("Remove Label"). This could confuse users.
<Button variant="Delete" - label="Delete Label" + label="Remove Label" title="" disabled={!selectedLabel} >Also applies to: 54-54
19-19
: Provide meaningful title attribute.The empty
title=""
attribute provides no accessibility benefit. Either remove it or provide a descriptive title.variant="Delete" label="Remove Label" - title="" + title="Remove the selected label" disabled={!selectedLabel}
8-12
: Consider making selectedLabel optional with validation.The current interface requires
selectedLabel
but the default trigger button handles the empty case by disabling itself. Consider making it optional and adding proper validation.interface Props { trigger?: React.ReactNode onRemoveLabel: (label: string) => Promise<boolean> - selectedLabel: string + selectedLabel?: string }Then add validation in the handler:
const handleRemoveLabel = async () => { + if (!selectedLabel) { + console.warn('No label selected for removal') + return + } setIsLoading(true)app/components/EditorComponent.tsx (2)
291-293
: Potential performance issue with line counting.The
useEffect
recalculates line numbers on every query change by splitting the string. For large queries, this could be inefficient.Consider optimizing line counting:
useEffect(() => { - setLineNumber(historyQuery.query.split("\n").length) + setLineNumber(historyQuery.query ? historyQuery.query.split("\n").length : 1) }, [historyQuery.query])Alternatively, consider using Monaco Editor's built-in line counting methods when available.
720-734
: Potential code duplication in onChange handlers.The onChange logic for both editors (inline and dialog) is very similar but reversed in the conditional logic. This could be extracted to a common function.
Consider extracting the onChange logic:
+const handleQueryChange = (val: string | undefined, isDialogEditor: boolean) => { + if (historyQuery.counter) { + setHistoryQuery(prev => ({ + ...prev, + query: val || "" + })) + } else { + setHistoryQuery(prev => ({ + ...prev, + query: val || "", + currentQuery: val || "" + })) + } +} // Then use: onChange={(val) => handleQueryChange(val, false)} for inline // And: onChange={(val) => handleQueryChange(val, true)} for dialogapp/graph/toolbar.tsx (1)
165-167
: Use stable keys instead of array indicesUsing array indices as keys can cause React rendering issues when the suggestions list changes. Consider using a stable identifier.
-suggestions.map((suggestion, index) => - // eslint-disable-next-line react/no-array-index-key - <li key={index}> +suggestions.map((suggestion, index) => + <li key={`${suggestion.id}-${suggestion.category ? 'node' : 'link'}`}>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
app/components/EditorComponent.tsx
(9 hunks)app/components/ui/Button.tsx
(4 hunks)app/graph/GraphDataPanel.tsx
(2 hunks)app/graph/GraphDataTable.tsx
(1 hunks)app/graph/RemoveLabel.tsx
(1 hunks)app/graph/Selector.tsx
(4 hunks)app/graph/addLabel.tsx
(1 hunks)app/graph/selectGraph.tsx
(1 hunks)app/graph/toolbar.tsx
(1 hunks)app/settings/page.tsx
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/settings/page.tsx
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: Run tests part 2 (except settings)
- GitHub Check: Run tests part 1 (except settings)
- GitHub Check: Run tests part 1 (except settings)
- GitHub Check: Run tests part 2 (except settings)
🔇 Additional comments (24)
app/graph/selectGraph.tsx (1)
165-168
: 🛠️ Refactor suggestionMissing error handling in onDuplicate callback.
The callback function should handle potential errors when updating state, especially if
setOptions
orsetSelectedValue
could fail.onDuplicate={(duplicateName) => { - setSelectedValue(duplicateName) - setOptions!([...options, duplicateName]) + try { + setSelectedValue(duplicateName) + setOptions!([...options, duplicateName]) + } catch (error) { + console.error('Failed to update after duplication:', error) + toast({ + title: "Error", + description: "Failed to update selection after duplication", + variant: "destructive" + }) + } }}Likely an incorrect or invalid review comment.
app/graph/addLabel.tsx (6)
1-7
: Import organization looks good.The imports are well-organized and follow a logical pattern with external libraries first, followed by internal components.
9-12
: Props interface is well-designed.The interface properly defines the expected props with appropriate TypeScript types. The optional trigger with required callback pattern provides good flexibility.
25-29
: State management structure is appropriate.The component uses appropriate state variables for dialog control, input value, and loading state management.
31-44
: Form submission handler has good error handling structure.The async handler properly prevents default form submission and uses try-finally for cleanup. The success-based conditional logic for resetting state is well-implemented.
57-63
: Input component implementation is well-structured.The input has proper test ID, controlled value, and change handler. Good use of data-testid for testing purposes.
65-77
: Button configuration is comprehensive.Both buttons have appropriate variants, labels, titles, and types. The loading state integration with the submit button is well-implemented.
app/graph/Selector.tsx (2)
73-88
: Well-implemented API fetching with offline handling.The
getOptions
callback properly handles offline state and auto-selection logic. Good use ofuseCallback
for optimization.
118-143
: Excellent keyboard shortcut implementation for query submission.The editor configuration provides intuitive keyboard shortcuts:
- Ctrl/Cmd+Enter for submit
- Shift+Enter for new line
- Enter for submit (when suggestions aren't visible)
This enhances the user experience significantly.
app/components/ui/Button.tsx (6)
8-8
: LGTM! Variant type extension follows established pattern.The addition of "Delete" to the Variant type is consistent with the existing string literal union pattern and enables delete-specific styling.
19-19
: LGTM! Good design for tooltip customization.Adding the optional
tooltipVariant
property enhances component flexibility by allowing independent tooltip styling while maintaining backward compatibility.
35-48
: LGTM! Delete variant styling follows established pattern.The new "Delete" case in the switch statement follows the same pattern as other variants, appropriately using
border-red-500
for visual distinction of destructive actions.
54-54
: LGTM! Well-designed default parameter assignment.The
tooltipVariant = variant
default assignment ensures backward compatibility while enabling tooltip customization when needed.
71-76
: LGTM! Good UX improvement with text truncation.Wrapping the label in a
<p>
element withtruncate
class prevents text overflow and improves visual presentation while maintaining semantic structure.
81-81
: LGTM! Consistent Delete variant tooltip styling.The conditional red styling for Delete variant tooltips maintains visual consistency with the button's red border theme and provides appropriate visual cues for destructive actions.
app/graph/RemoveLabel.tsx (1)
26-29
: LGTM! Good state management structure.The component properly uses React hooks and context for state management. The separation of dialog state and loading state is clean and appropriate.
app/graph/GraphDataPanel.tsx (1)
115-241
: Well-structured conditional rendering with proper access control!The implementation cleanly handles both dialog and inline display modes, with appropriate role-based access control for label management operations.
app/components/EditorComponent.tsx (5)
19-42
: Good practice: Extracting theme configuration to a reusable function.The
setTheme
function is well-structured and provides a clean way to configure Monaco Editor themes. The color scheme and token definitions look appropriate for a dark theme.
79-83
: Configuration update looks appropriate.The addition of
overflowWidgetsDomNode: undefined
and hidden scrollbars aligns with creating a cleaner editor interface.
232-233
: Good practice: Separate refs for different editor instances.Having separate refs for the inline editor (
editorRef
) and dialog editor (dialogEditorRef
) is a good approach for managing two independent Monaco Editor instances.
45-49
:❌ Incorrect review comment
Verify the breaking change in runQuery signature.
The
runQuery
prop signature has changed from(query: string, timeout?: number) => Promise<boolean>
to(query: string) => Promise<void>
. This is a breaking change that needs verification across the codebase.
🏁 Script executed:
#!/bin/bash # Description: Find all usages of runQuery to ensure they match the new signature # Expected: All calls should only pass query string, no timeout parameter or return value handling # Search for runQuery function calls rg -A 3 -B 1 "runQuery\(" --type tsx --type ts # Search for runQuery prop definitions rg -A 2 -B 1 "runQuery:" --type tsx --type tsLength of output: 150
🏁 Script executed:
#!/bin/bash # Description: Re-verify all usages and definitions of runQuery in TS/TSX files # 1. Find all invocations of runQuery rg -n --glob '*.ts' --glob '*.tsx' "runQuery\s*\(" -C2 # 2. Identify any calls passing more than one argument (i.e., potential timeout usage) rg -n --glob '*.ts' --glob '*.tsx' -e "runQuery\s*\([^,]+,[^)]*" -C2 # 3. Find all prop/type definitions of runQuery rg -n --glob '*.ts' --glob '*.tsx' "runQuery\s*:" -C2Length of output: 8157
Ignore runQuery signature break alert
The
runQuery
change applies only to theEditorComponent
prop (now(query: string) => Promise<void>
) and does not affect theapicalls.runQuery
used in your e2e tests (which remainrunQuery(graphName: string, query: string, role?: string): Promise<RunQueryResponse>
). No calls to the Editor’s prop pass a timeout or expect a boolean return, and your tests continue to call the unchanged API method. No further action is needed.Likely an incorrect or invalid review comment.
662-739
:✅ Verification successful
Verify dialog accessibility and behavior.
The dialog structure looks well-implemented with proper accessibility features (VisuallyHidden components for screen readers). However, there are a few concerns:
- The dialog has
disableClose
prop but includes a close button - verify this behavior is intentional- Both editors share the same
handleEditorDidMount
function but have different refs
🏁 Script executed:
#!/bin/bash # Description: Check for DialogContent disableClose prop usage and CloseDialog component implementation # Expected: Verify if disableClose prop conflicts with CloseDialog behavior # Search for CloseDialog component definition rg -A 10 -B 2 "CloseDialog" --type tsx --type ts # Search for disableClose prop usage in DialogContent rg -A 5 -B 2 "disableClose" --type tsx --type tsLength of output: 153
I’ll re-run searches without the tsx/ts type filters and pull in the component definitions and import context to confirm behaviors:
🏁 Script executed:
#!/bin/bash # Show imports at top of EditorComponent for context sed -n '1,50p' app/components/EditorComponent.tsx # Find CloseDialog declaration/usages rg -g '*.tsx' -g '*.ts' -n "CloseDialog" -A 10 -B 2 # Find DialogContent declaration to see what disableClose does rg -g '*.tsx' -g '*.ts' -n "DialogContent" -A 10 -B 2 # Find all disableClose usages rg -g '*.tsx' -g '*.ts' -n "disableClose" -A 5 -B 2 # Locate handleEditorDidMount implementation rg -g '*.tsx' -g '*.ts' -n "handleEditorDidMount" -A 10 -B 5 # Locate editor refs rg -g '*.tsx' -g '*.ts' -n "dialogEditorRef" -A 3 -B 3 rg -g '*.tsx' -g '*.ts' -n "editorRef" -A 3 -B 3Length of output: 68839
No changes needed: custom close control and shared mount logic are intentional
- The
disableClose
prop onDialogContent
simply removes the primitive’s default close button; you’ve explicitly added a<CloseDialog>
in the header to render your custom close control.- The single
handleEditorDidMount
function installs the same editor commands and behavior in both the main and maximized editors, whileeditorRef
anddialogEditorRef
each track their own instance for focus and API calls.Everything aligns with the design system and works as intended—no updates required.
app/graph/toolbar.tsx (2)
11-21
: Well-structured Props interfaceThe refactored Props interface provides better type safety and clearer component API with appropriate optional properties.
82-265
: Well-implemented search and control interfaceThe refactored component provides excellent functionality:
- Debounced search with keyboard navigation
- Clean dropdown menu for add operations
- Proper conditional rendering and test attributes
- Good accessibility with ARIA attributes
type={type} | ||
/> | ||
<DuplicateGraph | ||
selectedValue={rows.filter(opt => opt.checked).map(opt => opt.cells[0].value as string)[0]} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential runtime error with array access.
Accessing the first element with [0]
could cause a runtime error if the filtered array is empty. Consider adding a safeguard.
-selectedValue={rows.filter(opt => opt.checked).map(opt => opt.cells[0].value as string)[0]}
+selectedValue={rows.filter(opt => opt.checked).map(opt => opt.cells[0].value as string)[0] || ""}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
selectedValue={rows.filter(opt => opt.checked).map(opt => opt.cells[0].value as string)[0]} | |
selectedValue={rows.filter(opt => opt.checked) | |
.map(opt => opt.cells[0].value as string)[0] || ""} |
🤖 Prompt for AI Agents
In app/graph/selectGraph.tsx at line 161, the code filters the rows for checked
options and then accesses the first element's value directly, which could cause
a runtime error if no options are checked and the filtered array is empty. Add a
safeguard by checking if the filtered array has any elements before accessing
the first one, or provide a default value to prevent potential errors.
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" }] }))) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing the handleSetRows function to improve performance.
The handleSetRows
function creates new objects on every call and depends on session
and handleSetOption
. This could cause unnecessary re-renders and should be memoized.
+import { useCallback } from "react";
-const handleSetRows = (opts: string[]) => {
- setRows(opts.map(opt => session?.user?.role === "Admin" ? ({ checked: false, name: opt, cells: [{ value: opt, onChange: (value: string) => handleSetOption(value, opt), type: "text" }] }) : ({ checked: false, name: opt, cells: [{ value: opt, type: "readonly" }] })))
-}
+const handleSetRows = useCallback((opts: string[]) => {
+ setRows(opts.map(opt => session?.user?.role === "Admin" ? ({ checked: false, name: opt, cells: [{ value: opt, onChange: (value: string) => handleSetOption(value, opt), type: "text" }] }) : ({ checked: false, name: opt, cells: [{ value: opt, type: "readonly" }] })))
+}, [session?.user?.role, handleSetOption])
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const handleSetRows = (opts: string[]) => { | |
setRows(opts.map(opt => session?.user?.role === "Admin" ? ({ checked: false, name: opt, cells: [{ value: opt, onChange: (value: string) => handleSetOption(value, opt), type: "text" }] }) : ({ checked: false, name: opt, cells: [{ value: opt, type: "readonly" }] }))) | |
} | |
// At the top of app/graph/selectGraph.tsx | |
import { useCallback } from "react"; | |
… | |
// Replace the existing handleSetRows definition | |
const handleSetRows = useCallback((opts: string[]) => { | |
setRows( | |
opts.map(opt => | |
session?.user?.role === "Admin" | |
? { | |
checked: false, | |
name: opt, | |
cells: [ | |
{ | |
value: opt, | |
onChange: (value: string) => handleSetOption(value, opt), | |
type: "text", | |
}, | |
], | |
} | |
: { | |
checked: false, | |
name: opt, | |
cells: [{ value: opt, type: "readonly" }], | |
} | |
) | |
); | |
}, [session?.user?.role, handleSetOption]); |
🤖 Prompt for AI Agents
In app/graph/selectGraph.tsx at lines 67 to 69, the handleSetRows function is
recreated on every render, which may cause unnecessary re-renders. Memoize
handleSetRows using useCallback, including session and handleSetOption as
dependencies, to optimize performance and prevent redundant object creation.
<Input | ||
data-testid="DataPanelAddAttributeKey" | ||
ref={ref => !newKey ? ref?.focus() : undefined} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Potential accessibility issue with conditional focus
The conditional focus logic using ref={ref => !newKey ? ref?.focus() : undefined}
may not work reliably and could cause accessibility issues for screen readers.
-<Input
- data-testid="DataPanelAddAttributeKey"
- ref={ref => !newKey ? ref?.focus() : undefined}
- className="w-full"
- value={newKey}
- onChange={(e) => setNewKey(e.target.value)}
- onKeyDown={handleAddKeyDown}
-/>
+<Input
+ data-testid="DataPanelAddAttributeKey"
+ autoFocus
+ className="w-full"
+ value={newKey}
+ onChange={(e) => setNewKey(e.target.value)}
+ onKeyDown={handleAddKeyDown}
+/>
Alternatively, use a useEffect
hook to focus the input when isAddValue
becomes true.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<Input | |
data-testid="DataPanelAddAttributeKey" | |
ref={ref => !newKey ? ref?.focus() : undefined} | |
<Input | |
data-testid="DataPanelAddAttributeKey" | |
autoFocus | |
className="w-full" | |
value={newKey} | |
onChange={(e) => setNewKey(e.target.value)} | |
onKeyDown={handleAddKeyDown} | |
/> |
🤖 Prompt for AI Agents
In app/graph/GraphDataTable.tsx around lines 324 to 326, the focus is
conditionally set directly within the ref callback, which may not reliably focus
the input and could cause accessibility issues. To fix this, remove the inline
focus logic from the ref and instead use a useEffect hook that triggers when the
relevant state (such as isAddValue) changes to true, then call focus() on the
input element. This approach ensures consistent and accessible focus management.
session?.user?.role !== "Read-Only" && ( | ||
editable === key ? | ||
<> | ||
<Button | ||
data-testid="DataPanelSetAttributeConfirm" | ||
indicator={indicator} | ||
variant="button" | ||
onClick={(e) => { | ||
e.stopPropagation() | ||
setProperty(key, newVal, true) | ||
}} | ||
isLoading={isSetLoading} | ||
> | ||
<Check size={20} /> | ||
</Button> | ||
{ | ||
!isSetLoading && | ||
<Button | ||
data-testid="DataPanelSetAttributeCancel" | ||
variant="button" | ||
onClick={(e) => { | ||
e.stopPropagation() | ||
handleSetEditable("", "") | ||
}} | ||
> | ||
<X size={20} /> | ||
</Button> | ||
} | ||
</> | ||
: hover === key && | ||
<> | ||
<Button | ||
data-testid="DataPanelSetAttribute" | ||
variant="button" | ||
onClick={() => handleSetEditable(key, object.data[key])} | ||
disabled={isAddValue} | ||
> | ||
<Pencil size={20} /> | ||
</Button> | ||
<DialogComponent | ||
trigger={ | ||
<Button | ||
data-testid="DataPanelDeleteAttribute" | ||
variant="button" | ||
title="Delete Attribute" | ||
> | ||
<Trash2 size={20} /> | ||
</Button> | ||
} | ||
title="Delete Attribute" | ||
description="Are you sure you want to delete this attribute?" | ||
> | ||
<div className="flex justify-end gap-4"> | ||
<Button | ||
data-testid="DataPanelDeleteAttributeConfirm" | ||
variant="Delete" | ||
label="Delete" | ||
onClick={() => removeProperty(key)} | ||
isLoading={isRemoveLoading} | ||
/> | ||
<CloseDialog | ||
data-testid="DataPanelDeleteAttributeCancel" | ||
label="Cancel" | ||
variant="Cancel" | ||
/> | ||
</div> | ||
</DialogComponent> | ||
</> | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add proper ARIA labels for better accessibility
The edit and delete buttons lack proper ARIA labels, making them less accessible to screen reader users.
<Button
data-testid="DataPanelSetAttribute"
variant="button"
+ aria-label={`Edit attribute ${key}`}
onClick={() => handleSetEditable(key, object.data[key])}
disabled={isAddValue}
>
<Pencil size={20} />
</Button>
<Button
data-testid="DataPanelDeleteAttribute"
variant="button"
+ aria-label={`Delete attribute ${key}`}
title="Delete Attribute"
>
<Trash2 size={20} />
</Button>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
session?.user?.role !== "Read-Only" && ( | |
editable === key ? | |
<> | |
<Button | |
data-testid="DataPanelSetAttributeConfirm" | |
indicator={indicator} | |
variant="button" | |
onClick={(e) => { | |
e.stopPropagation() | |
setProperty(key, newVal, true) | |
}} | |
isLoading={isSetLoading} | |
> | |
<Check size={20} /> | |
</Button> | |
{ | |
!isSetLoading && | |
<Button | |
data-testid="DataPanelSetAttributeCancel" | |
variant="button" | |
onClick={(e) => { | |
e.stopPropagation() | |
handleSetEditable("", "") | |
}} | |
> | |
<X size={20} /> | |
</Button> | |
} | |
</> | |
: hover === key && | |
<> | |
<Button | |
data-testid="DataPanelSetAttribute" | |
variant="button" | |
onClick={() => handleSetEditable(key, object.data[key])} | |
disabled={isAddValue} | |
> | |
<Pencil size={20} /> | |
</Button> | |
<DialogComponent | |
trigger={ | |
<Button | |
data-testid="DataPanelDeleteAttribute" | |
variant="button" | |
title="Delete Attribute" | |
> | |
<Trash2 size={20} /> | |
</Button> | |
} | |
title="Delete Attribute" | |
description="Are you sure you want to delete this attribute?" | |
> | |
<div className="flex justify-end gap-4"> | |
<Button | |
data-testid="DataPanelDeleteAttributeConfirm" | |
variant="Delete" | |
label="Delete" | |
onClick={() => removeProperty(key)} | |
isLoading={isRemoveLoading} | |
/> | |
<CloseDialog | |
data-testid="DataPanelDeleteAttributeCancel" | |
label="Cancel" | |
variant="Cancel" | |
/> | |
</div> | |
</DialogComponent> | |
</> | |
) | |
// …inside the hover===key branch of your JSX… | |
<Button | |
data-testid="DataPanelSetAttribute" | |
variant="button" | |
aria-label={`Edit attribute ${key}`} | |
onClick={() => handleSetEditable(key, object.data[key])} | |
disabled={isAddValue} | |
> | |
<Pencil size={20} /> | |
</Button> | |
<DialogComponent | |
trigger={ | |
<Button | |
data-testid="DataPanelDeleteAttribute" | |
variant="button" | |
title="Delete Attribute" | |
aria-label={`Delete attribute ${key}`} | |
> | |
<Trash2 size={20} /> | |
</Button> | |
} | |
title="Delete Attribute" | |
description="Are you sure you want to delete this attribute?" | |
> | |
<div className="flex justify-end gap-4"> | |
<Button | |
data-testid="DataPanelDeleteAttributeConfirm" | |
variant="Delete" | |
label="Delete" | |
onClick={() => removeProperty(key)} | |
isLoading={isRemoveLoading} | |
/> | |
<CloseDialog | |
data-testid="DataPanelDeleteAttributeCancel" | |
label="Cancel" | |
variant="Cancel" | |
/> | |
</div> | |
</DialogComponent> | |
// …rest of your JSX… |
🤖 Prompt for AI Agents
In app/graph/GraphDataTable.tsx around lines 202 to 270, the edit and delete
buttons do not have ARIA labels, which affects accessibility for screen reader
users. Add descriptive ARIA labels to each button, such as aria-label="Edit
attribute" and aria-label="Delete attribute", to clearly convey their purpose to
assistive technologies.
AddLabel.defaultProps = { | ||
trigger: undefined | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove deprecated defaultProps usage.
The defaultProps
pattern is deprecated in modern React. Since you're already using default parameters in the function signature (line 15), this declaration is redundant and should be removed.
-AddLabel.defaultProps = {
- trigger: undefined
-}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
AddLabel.defaultProps = { | |
trigger: undefined | |
} |
🤖 Prompt for AI Agents
In app/graph/addLabel.tsx around lines 84 to 86, the use of
AddLabel.defaultProps to set a default value for trigger is deprecated in modern
React. Remove the defaultProps assignment and rely solely on default parameters
in the function signature to define default values for props.
RemoveLabel.defaultProps = { | ||
trigger: undefined | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Replace deprecated defaultProps with default parameters.
The defaultProps
pattern is deprecated in modern React with TypeScript. Use default parameters in the function signature instead.
-export default function RemoveLabel({
- selectedLabel,
- trigger = <Button
- variant="Delete"
- label="Delete Label"
- title=""
- disabled={!selectedLabel}
- >
- <Trash2 />
- </Button>,
- onRemoveLabel,
-}: Props) {
+export default function RemoveLabel({
+ selectedLabel,
+ trigger,
+ onRemoveLabel,
+}: Props) {
+ const defaultTrigger = (
+ <Button
+ variant="Delete"
+ label="Delete Label"
+ title=""
+ disabled={!selectedLabel}
+ >
+ <Trash2 />
+ </Button>
+ );
-RemoveLabel.defaultProps = {
- trigger: undefined
-}
Then use trigger || defaultTrigger
in the DialogComponent:
- trigger={trigger}
+ trigger={trigger || defaultTrigger}
🤖 Prompt for AI Agents
In app/graph/RemoveLabel.tsx lines 70 to 72, the use of defaultProps is
deprecated in modern React with TypeScript. Remove the defaultProps assignment
and instead set a default value for the trigger parameter directly in the
function signature. Then, replace any usage of trigger inside the component with
trigger || defaultTrigger to ensure default behavior.
toast({ | ||
title: "Error", | ||
description: "Please fill in the value field", | ||
variant: "destructive" | ||
}) | ||
return false | ||
} | ||
try { | ||
if (actionType === "set") setIsSetLoading(true) | ||
const result = await securedFetch(`api/graph/${prepareArg(graph.Id)}/${id}/${key}`, { | ||
method: "POST", | ||
body: JSON.stringify({ | ||
value: val, | ||
type | ||
}) | ||
}, toast, setIndicator) | ||
|
||
if (result.ok) { | ||
|
||
graph.setProperty(key, val, id) | ||
|
||
const value = obj.data[key] | ||
setObj({ ...obj, data: { ...obj.data, [key]: val } }) | ||
|
||
handleSetEditable("", "") | ||
toast({ | ||
title: "Success", | ||
description: `Attribute ${actionType}`, | ||
variant: "default", | ||
action: isUndo ? <ToastButton onClick={() => setProperty(key, value, false)} /> : undefined | ||
}) | ||
} | ||
|
||
return result.ok | ||
} finally { | ||
if (actionType === "set") setIsSetLoading(false) | ||
} | ||
} | ||
|
||
const handleAddValue = async (key: string, value: string) => { | ||
if (!key || key === "" || !value || value === "") { | ||
toast({ | ||
title: "Error", | ||
description: "Please fill in both fields", | ||
variant: "destructive" | ||
}) | ||
return | ||
} | ||
try { | ||
setIsAddLoading(true) | ||
const success = await setProperty(key, value, false, "added") | ||
if (!success) return | ||
setIsAddValue(false) | ||
setNewKey("") | ||
setNewVal("") | ||
} finally { | ||
setIsAddLoading(false) | ||
} | ||
} | ||
|
||
const removeProperty = async (key: string) => { | ||
try { | ||
setIsRemoveLoading(true) | ||
const { id } = obj | ||
const success = (await securedFetch(`api/graph/${prepareArg(graph.Id)}/${id}/${key}`, { | ||
method: "DELETE", | ||
body: JSON.stringify({ type }), | ||
}, toast, setIndicator)).ok | ||
|
||
if (success) { | ||
const value = obj.data[key] | ||
|
||
graph.removeProperty(key, id) | ||
delete obj.data[key] | ||
setObj({ ...obj }) | ||
|
||
toast({ | ||
title: "Success", | ||
description: "Attribute removed", | ||
action: <ToastButton onClick={() => handleAddValue(key, value)} />, | ||
variant: "default" | ||
}) | ||
} | ||
|
||
return success | ||
} finally { | ||
setIsRemoveLoading(false) | ||
} | ||
} | ||
|
||
const handleAddKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => { | ||
if (e.key === "Escape") { | ||
setIsAddValue(false) | ||
setNewKey("") | ||
setNewVal("") | ||
return | ||
if (labelsListRef.current) { | ||
setShowAsDialog(labelsListRef.current.clientHeight > 80) | ||
} | ||
}, [labelsListRef.current?.clientHeight]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix useEffect dependency to avoid potential issues.
The dependency labelsListRef.current?.clientHeight
won't trigger effect re-runs because React doesn't track changes to ref properties. This could lead to stale state.
Consider using a ResizeObserver or removing the dependency:
useEffect(() => {
if (labelsListRef.current) {
setShowAsDialog(labelsListRef.current.clientHeight > 80)
}
-}, [labelsListRef.current?.clientHeight])
+}, []) // Run once on mount, or use ResizeObserver for dynamic updates
Alternatively, implement a ResizeObserver to properly track size changes:
useEffect(() => {
const element = labelsListRef.current;
if (!element) return;
const observer = new ResizeObserver(() => {
setShowAsDialog(element.clientHeight > 80);
});
observer.observe(element);
return () => observer.disconnect();
}, []);
🤖 Prompt for AI Agents
In app/graph/GraphDataPanel.tsx around lines 44 to 48, the useEffect hook
depends on labelsListRef.current?.clientHeight, which won't trigger re-renders
because React does not track changes to ref properties. This may cause the
showAsDialog state to become stale if the element's height changes dynamically.
To fix this, replace the current useEffect with a ResizeObserver that observes
the element's size changes and updates showAsDialog accordingly, and set the
dependency array to empty to run only once on mount.
useEffect(() => { | ||
if (monacoEditor) { | ||
addSuggestions(monacoEditor) | ||
} | ||
}, [monacoEditor, graphIdRef.current]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Potential unnecessary re-execution of suggestions.
The useEffect
for addSuggestions
will re-run whenever graphIdRef.current
changes, but graphIdRef.current
is a primitive value that may not trigger the effect as expected.
Use graph.Id
directly in the dependency array:
useEffect(() => {
if (monacoEditor) {
addSuggestions(monacoEditor)
}
-}, [monacoEditor, graphIdRef.current])
+}, [monacoEditor, graph.Id])
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
useEffect(() => { | |
if (monacoEditor) { | |
addSuggestions(monacoEditor) | |
} | |
}, [monacoEditor, graphIdRef.current]) | |
useEffect(() => { | |
if (monacoEditor) { | |
addSuggestions(monacoEditor) | |
} | |
}, [monacoEditor, graph.Id]) |
🤖 Prompt for AI Agents
In app/components/EditorComponent.tsx around lines 400 to 404, the useEffect
hook depends on graphIdRef.current, which is a ref and may not trigger
re-execution as intended. To fix this, replace graphIdRef.current with graph.id
(or the appropriate primitive value) directly in the dependency array to ensure
the effect runs when the graph's ID changes. This prevents unnecessary
re-renders and ensures addSuggestions is called appropriately.
|| el.label && (el as Link).label.toLowerCase().includes(searchElement.toLowerCase()) | ||
|| el.category && (el as Node).category.some(c => c.toLowerCase().includes(searchElement.toLowerCase())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use optional chaining for safer property access
The code accesses optional properties without proper null checks, which could cause runtime errors.
-|| el.label && (el as Link).label.toLowerCase().includes(searchElement.toLowerCase())
-|| el.category && (el as Node).category.some(c => c.toLowerCase().includes(searchElement.toLowerCase()))
+|| (el as Link).label?.toLowerCase().includes(searchElement.toLowerCase())
+|| (el as Node).category?.some(c => c.toLowerCase().includes(searchElement.toLowerCase()))
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/graph/toolbar.tsx on lines 53 to 54, the code accesses properties like
el.label and el.category without checking if they are defined, which could lead
to runtime errors. Use optional chaining to safely access these properties, such
as el?.label and el?.category, before calling methods like toLowerCase() or
some(). This ensures the code only attempts to access properties if they exist.
…ency. Adjust button labels, editor height calculations, and layout styles. Update Selector and SelectGraph components for better alignment and height.
Fix #986 Refactor EditorComponent and Graph components for improved UI consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🔭 Outside diff range comments (1)
app/graph/Selector.tsx (1)
90-101
:⚠️ Potential issueRemove redundant useEffect hook.
The useEffect at lines 98-101 with an empty dependency array is redundant. The component already calls
getOptions()
through the first useEffect (lines 90-92) which runs on mount and whenevergetOptions
changes.Remove the redundant useEffect:
useEffect(() => { if (indicator === "online") getOptions() }, [indicator, getOptions]) - useEffect(() => { - getOptions() - }, [])
♻️ Duplicate comments (2)
app/graph/Selector.tsx (1)
144-153
:⚠️ Potential issueAdd null checks to prevent runtime errors.
The function uses non-null assertion operators on optional props without checking if they exist. This could cause runtime errors if
runQuery
orhistoryQuery
are undefined.Apply this diff to add proper null checks:
const handleSubmit = async () => { + if (!runQuery || !historyQuery) return try { setIsLoading(true) - await runQuery!(historyQuery!.query.trim()) + await runQuery(historyQuery.query.trim()) setQueriesOpen(false) } finally { setIsLoading(false) } }app/components/EditorComponent.tsx (1)
405-409
:⚠️ Potential issueFix incorrect dependency in useEffect.
The
useEffect
usesgraphIdRef.current
in the dependency array, but ref values don't trigger re-renders when changed. Usegraph.Id
directly instead.Apply this diff:
useEffect(() => { if (monacoEditor) { addSuggestions(monacoEditor) } -}, [monacoEditor, graphIdRef.current]) +}, [monacoEditor, graph.Id])
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
app/components/EditorComponent.tsx
(9 hunks)app/graph/GraphDataTable.tsx
(1 hunks)app/graph/Selector.tsx
(4 hunks)app/graph/selectGraph.tsx
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- app/graph/GraphDataTable.tsx
- app/graph/selectGraph.tsx
⏰ Context from checks skipped due to timeout of 90000ms (6)
- GitHub Check: Run settings tests
- GitHub Check: Run tests part 1 (except settings)
- GitHub Check: Run tests part 2 (except settings)
- GitHub Check: Run tests part 1 (except settings)
- GitHub Check: Run tests part 2 (except settings)
- GitHub Check: Run settings tests
🔇 Additional comments (1)
app/components/EditorComponent.tsx (1)
245-248
: Well-structured height calculation with useMemo.The editor height calculation is now properly memoized and cleanly structured using
useMemo
. This is a good improvement over inline calculations.
<PaginationList | ||
label="Query" | ||
className="w-[40%] bg-background rounded-lg overflow-hidden" | ||
isSelected={(item) => historyQuery.queries.findIndex(q => q.text === item.text) + 1 === historyQuery.counter} | ||
afterSearchCallback={(newFilteredList) => { | ||
if (newFilteredList.every(q => q.text !== historyQuery.query)) { | ||
setHistoryQuery(prev => ({ | ||
...prev, | ||
counter: 0 | ||
})) | ||
} | ||
focusEditorAtEnd() | ||
}} | ||
dataTestId="queryHistory" | ||
list={historyQuery.queries.reverse()} | ||
step={STEP} | ||
onClick={(counter) => { | ||
setHistoryQuery(prev => ({ | ||
...prev, | ||
counter: historyQuery.queries.findIndex(q => q.text === counter) + 1 | ||
})) | ||
|
||
setTab("query") | ||
}} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix array mutation and optimize duplicate array operations.
Two issues in the PaginationList usage:
reverse()
mutates the original arrayfindIndex
is called twice for the same purpose
Apply this diff to fix both issues:
+const reversedQueries = [...historyQuery.queries].reverse()
+const getCurrentIndex = (query: { text: string }) => historyQuery.queries.findIndex(q => q.text === query.text) + 1
<PaginationList
label="Query"
className="w-[40%] bg-background rounded-lg overflow-hidden"
- isSelected={(item) => historyQuery.queries.findIndex(q => q.text === item.text) + 1 === historyQuery.counter}
+ isSelected={(item) => getCurrentIndex(item) === historyQuery.counter}
afterSearchCallback={(newFilteredList) => {
if (newFilteredList.every(q => q.text !== historyQuery.query)) {
setHistoryQuery(prev => ({
...prev,
counter: 0
}))
}
focusEditorAtEnd()
}}
dataTestId="queryHistory"
- list={historyQuery.queries.reverse()}
+ list={reversedQueries}
step={STEP}
onClick={(counter) => {
setHistoryQuery(prev => ({
...prev,
- counter: historyQuery.queries.findIndex(q => q.text === counter) + 1
+ counter: getCurrentIndex(counter)
}))
setTab("query")
}}
/>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<PaginationList | |
label="Query" | |
className="w-[40%] bg-background rounded-lg overflow-hidden" | |
isSelected={(item) => historyQuery.queries.findIndex(q => q.text === item.text) + 1 === historyQuery.counter} | |
afterSearchCallback={(newFilteredList) => { | |
if (newFilteredList.every(q => q.text !== historyQuery.query)) { | |
setHistoryQuery(prev => ({ | |
...prev, | |
counter: 0 | |
})) | |
} | |
focusEditorAtEnd() | |
}} | |
dataTestId="queryHistory" | |
list={historyQuery.queries.reverse()} | |
step={STEP} | |
onClick={(counter) => { | |
setHistoryQuery(prev => ({ | |
...prev, | |
counter: historyQuery.queries.findIndex(q => q.text === counter) + 1 | |
})) | |
setTab("query") | |
}} | |
/> | |
// Create a reversed copy to avoid mutating historyQuery.queries | |
const reversedQueries = [...historyQuery.queries].reverse() | |
// Cache the index lookup to avoid duplicate findIndex calls | |
const getCurrentIndex = (query: { text: string }) => | |
historyQuery.queries.findIndex(q => q.text === query.text) + 1 | |
<PaginationList | |
label="Query" | |
className="w-[40%] bg-background rounded-lg overflow-hidden" | |
isSelected={(item) => getCurrentIndex(item) === historyQuery.counter} | |
afterSearchCallback={(newFilteredList) => { | |
if (newFilteredList.every(q => q.text !== historyQuery.query)) { | |
setHistoryQuery(prev => ({ | |
...prev, | |
counter: 0 | |
})) | |
} | |
focusEditorAtEnd() | |
}} | |
dataTestId="queryHistory" | |
list={reversedQueries} | |
step={STEP} | |
onClick={(counter) => { | |
setHistoryQuery(prev => ({ | |
...prev, | |
counter: getCurrentIndex(counter) | |
})) | |
setTab("query") | |
}} | |
/> |
🤖 Prompt for AI Agents
In app/graph/Selector.tsx between lines 213 and 237, the code uses reverse()
directly on historyQuery.queries, which mutates the original array, and calls
findIndex twice redundantly. To fix this, create a reversed copy of
historyQuery.queries without mutating the original by using slice().reverse(),
and store the result of findIndex in a variable to reuse it instead of calling
it twice. Update the isSelected and onClick handlers accordingly to use the
stored index.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Documentation
Tests
Chores