From 6a178b625cf77e69fb602dfcec8c8cb9ab819de6 Mon Sep 17 00:00:00 2001 From: Gal Shubeli Date: Mon, 30 Mar 2026 19:36:29 +0300 Subject: [PATCH 1/3] fix: strip demo graph prefix for display and fix schema table names The list graphs endpoint now returns structured objects with id, name, and is_demo fields so the frontend can display demo graphs without the GENERAL_PREFIX while still using the full id for API calls. Hardcoded demo graph name checks are replaced with the is_demo flag. Also fixes table names not rendering in the schema viewer by using schemaNode.name instead of the undefined node.displayName[1]. Co-Authored-By: Claude Opus 4.6 (1M context) --- api/routes/graphs.py | 12 +++++++++++- app/src/components/chat/ChatInterface.tsx | 4 ++-- app/src/components/schema/SchemaViewer.tsx | 2 +- app/src/pages/Index.tsx | 8 ++++---- app/src/types/api.ts | 5 +++-- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/api/routes/graphs.py b/api/routes/graphs.py index f0a7d036..c9ea1d88 100644 --- a/api/routes/graphs.py +++ b/api/routes/graphs.py @@ -50,7 +50,17 @@ async def list_graphs(request: Request): Requires authentication. """ graphs = await list_databases(request.state.user_id, GENERAL_PREFIX) - return JSONResponse(content=graphs) + + result = [] + for graph_id in graphs: + is_demo = bool(GENERAL_PREFIX and graph_id.startswith(GENERAL_PREFIX)) + if is_demo: + display_name = graph_id[len(GENERAL_PREFIX):] + else: + display_name = graph_id + result.append({"id": graph_id, "name": display_name, "is_demo": is_demo}) + + return JSONResponse(content=result) @graphs_router.get( diff --git a/app/src/components/chat/ChatInterface.tsx b/app/src/components/chat/ChatInterface.tsx index 6f43691a..5bd4dcd4 100644 --- a/app/src/components/chat/ChatInterface.tsx +++ b/app/src/components/chat/ChatInterface.tsx @@ -522,8 +522,8 @@ const ChatInterface = ({ {/* Bottom Section with Suggestions and Input */}
- {/* Suggestion Cards - Only show for DEMO_CRM database */} - {(selectedGraph?.id === 'DEMO_CRM' || selectedGraph?.name === 'DEMO_CRM') && ( + {/* Suggestion Cards - Only show for demo databases */} + {selectedGraph?.is_demo && ( { event.stopPropagation(); // Prevent dropdown from closing/selecting // Check if this is a demo database - const isDemo = graphId.startsWith('general_'); - + const isDemo = graphs.find(g => g.id === graphId)?.is_demo || false; + if (isRefreshingSchema) return; // Show the delete confirmation modal setDatabaseToDelete({ id: graphId, name: graphName, isDemo }); @@ -532,7 +532,7 @@ const Index = () => {

Graph-Powered Text-to-SQL

{selectedGraph ? ( - {selectedGraph.name === 'DEMO_CRM' ? 'CRM' : selectedGraph.name} + {selectedGraph.name} ) : ( @@ -570,7 +570,7 @@ const Index = () => { {graphs.map((graph) => { - const isDemo = graph.id.startsWith('general_'); + const isDemo = graph.is_demo || false; return ( Date: Mon, 30 Mar 2026 19:50:14 +0300 Subject: [PATCH 2/3] fix: address PR review - move demo classification to list_databases Move is_demo classification into list_databases so it comes from the data source rather than re-inferring via startsWith in the route. This prevents user graphs whose name happens to start with GENERAL_PREFIX from being misclassified as demo graphs. Also makes is_demo required on the Graph type and passes isDemo directly to handleDeleteGraph. Co-Authored-By: Claude Opus 4.6 (1M context) --- api/core/schema_loader.py | 34 +++++++++++++++++++++------------- api/routes/graphs.py | 12 +----------- app/src/pages/Index.tsx | 7 ++----- app/src/services/database.ts | 14 ++++++-------- app/src/types/api.ts | 2 +- 5 files changed, 31 insertions(+), 38 deletions(-) diff --git a/api/core/schema_loader.py b/api/core/schema_loader.py index bb4dcedb..93c9494b 100644 --- a/api/core/schema_loader.py +++ b/api/core/schema_loader.py @@ -142,23 +142,31 @@ async def generate(): return generate() -async def list_databases(user_id: str, general_prefix: Optional[str] = None) -> list[str]: +async def list_databases( + user_id: str, general_prefix: Optional[str] = None +) -> list[dict]: """ - This route is used to list all the graphs (databases names) that are available in the database. + List all graphs (database names) available for the user. + + Returns a list of dicts with keys ``id`` (the full FalkorDB graph name + used for API calls), ``name`` (the display name with namespace prefix + stripped) and ``is_demo`` (whether the graph is a shared demo graph). """ user_graphs = await db.list_graphs() - # Only include graphs that start with user_id + '_', and strip the prefix - filtered_graphs = [ - graph[len(f"{user_id}_") :] - for graph in user_graphs - if graph.startswith(f"{user_id}_") - ] + result: list[dict] = [] + + # User-owned graphs: strip the user_id prefix for display + for graph in user_graphs: + if graph.startswith(f"{user_id}_"): + display_name = graph[len(f"{user_id}_"):] + result.append({"id": display_name, "name": display_name, "is_demo": False}) + # Demo/shared graphs: strip the general prefix for display if general_prefix: - demo_graphs = [ - graph for graph in user_graphs if graph.startswith(general_prefix) - ] - filtered_graphs = filtered_graphs + demo_graphs + for graph in user_graphs: + if graph.startswith(general_prefix): + display_name = graph[len(general_prefix):] + result.append({"id": graph, "name": display_name, "is_demo": True}) - return filtered_graphs + return result diff --git a/api/routes/graphs.py b/api/routes/graphs.py index c9ea1d88..f0a7d036 100644 --- a/api/routes/graphs.py +++ b/api/routes/graphs.py @@ -50,17 +50,7 @@ async def list_graphs(request: Request): Requires authentication. """ graphs = await list_databases(request.state.user_id, GENERAL_PREFIX) - - result = [] - for graph_id in graphs: - is_demo = bool(GENERAL_PREFIX and graph_id.startswith(GENERAL_PREFIX)) - if is_demo: - display_name = graph_id[len(GENERAL_PREFIX):] - else: - display_name = graph_id - result.append({"id": graph_id, "name": display_name, "is_demo": is_demo}) - - return JSONResponse(content=result) + return JSONResponse(content=graphs) @graphs_router.get( diff --git a/app/src/pages/Index.tsx b/app/src/pages/Index.tsx index 21028e6e..6912b8e2 100644 --- a/app/src/pages/Index.tsx +++ b/app/src/pages/Index.tsx @@ -177,11 +177,8 @@ const Index = () => { } }; - const handleDeleteGraph = async (graphId: string, graphName: string, event: React.MouseEvent) => { + const handleDeleteGraph = async (graphId: string, graphName: string, isDemo: boolean, event: React.MouseEvent) => { event.stopPropagation(); // Prevent dropdown from closing/selecting - - // Check if this is a demo database - const isDemo = graphs.find(g => g.id === graphId)?.is_demo || false; if (isRefreshingSchema) return; // Show the delete confirmation modal @@ -586,7 +583,7 @@ const Index = () => { className={`h-6 w-6 p-0 opacity-0 group-hover:opacity-100 transition-opacity ${ isDemo || isRefreshingSchema || isChatProcessing ? 'cursor-not-allowed opacity-40' : 'hover:bg-red-600 hover:text-white' }`} - onClick={(e) => { if (isDemo || isRefreshingSchema || isChatProcessing) return; handleDeleteGraph(graph.id, graph.name, e); }} + onClick={(e) => { if (isDemo || isRefreshingSchema || isChatProcessing) return; handleDeleteGraph(graph.id, graph.name, isDemo, e); }} disabled={isDemo || isRefreshingSchema} title={isDemo ? 'Demo databases cannot be deleted' : (isRefreshingSchema ? 'Refreshing schema...' : `Delete ${graph.name}`)} data-testid={`delete-graph-btn-${graph.id}`} diff --git a/app/src/services/database.ts b/app/src/services/database.ts index a74f4e96..f1ff046c 100644 --- a/app/src/services/database.ts +++ b/app/src/services/database.ts @@ -37,21 +37,19 @@ export class DatabaseService { const data = await response.json(); console.log('Graphs data received:', data); - // Backend returns array of strings like ["northwind", "chinook"] - // Transform to Graph objects + // Backend returns array of objects with id, name, is_demo const graphNames = data.graphs || data || []; - + if (Array.isArray(graphNames) && graphNames.length > 0 && typeof graphNames[0] === 'string') { - // Transform string array to Graph objects + // Legacy fallback: transform string array to Graph objects return graphNames.map((name: string) => ({ id: name, name: name, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), + is_demo: false, })); } - - // If already objects, return as is + + // Already objects from the backend return graphNames; } catch (error) { // Backend not available - return empty array for demo mode diff --git a/app/src/types/api.ts b/app/src/types/api.ts index a64f212b..a0207b45 100644 --- a/app/src/types/api.ts +++ b/app/src/types/api.ts @@ -22,7 +22,7 @@ export interface Graph { description?: string; created_at?: string; updated_at?: string; - is_demo?: boolean; + is_demo: boolean; table_count?: number; schema?: any; } From 5e50530c0b8493b33b9416ece9153e86fef8c0c6 Mon Sep 17 00:00:00 2001 From: Gal Shubeli Date: Mon, 30 Mar 2026 20:09:20 +0300 Subject: [PATCH 3/3] fix: update E2E tests to handle new graph list response format The getGraphs helper now extracts id strings from the object response so existing E2E tests that call string methods like endsWith continue to work with the new {id, name, is_demo} response format. Co-Authored-By: Claude Opus 4.6 (1M context) --- e2e/logic/api/apiCalls.ts | 8 +++++++- e2e/logic/api/apiResponses.ts | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/e2e/logic/api/apiCalls.ts b/e2e/logic/api/apiCalls.ts index 6ffd2279..5c60f340 100644 --- a/e2e/logic/api/apiCalls.ts +++ b/e2e/logic/api/apiCalls.ts @@ -8,6 +8,7 @@ import type { LoginResponse, SignupResponse, LogoutResponse, + GraphListItem, GraphsListResponse, GraphDataResponse, GraphUploadResponse, @@ -156,7 +157,12 @@ export default class ApiCalls { undefined, this.defaultRequestContext ); - return await response.json(); + const data = await response.json(); + // Backend now returns objects with {id, name, is_demo}; extract ids for backward compat + if (Array.isArray(data) && data.length > 0 && typeof data[0] === 'object') { + return (data as GraphListItem[]).map((g) => g.id); + } + return data; } catch (error) { throw new Error( `Failed to get graphs. \n Error: ${(error as Error).message}` diff --git a/e2e/logic/api/apiResponses.ts b/e2e/logic/api/apiResponses.ts index b2a1bb38..ebcbc8bd 100644 --- a/e2e/logic/api/apiResponses.ts +++ b/e2e/logic/api/apiResponses.ts @@ -31,6 +31,12 @@ export interface LogoutResponse { // ==================== GRAPH/DATABASE RESPONSES ==================== +export interface GraphListItem { + id: string; + name: string; + is_demo: boolean; +} + export type GraphsListResponse = string[]; export interface GraphColumn {