Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion api/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ async def repo_info(data: RepoRequest, _=Depends(public_or_auth)):
stats = await g.stats()
finally:
await g.close()
info = await async_get_repo_info(data.repo, data.branch)
# Use the parsed project/branch so full graph keys
# (``code:{project}:{branch}``) resolve the same as plain repo names.
info = await async_get_repo_info(g.project, g.branch)

if info is None:
return JSONResponse({"status": f'Missing repository "{data.repo}"'}, status_code=400)
Expand Down
21 changes: 15 additions & 6 deletions app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Drawer, DrawerContent, DrawerDescription, DrawerTitle, DrawerTrigger }
import Input from './components/Input';
import { Labels } from './components/labels';
import { Toolbar } from './components/toolbar';
import { cn, GraphRef, Message, Path, PathData, PathNode } from '@/lib/utils';
import { cn, composeGraphName, GraphRef, Message, Path, PathData, PathNode, projectNameFromURL, RepoOption } from '@/lib/utils';
import type { GraphNode } from '@falkordb/canvas';
import { Toaster } from '@/components/ui/toaster';
import GTM from './GTM';
Expand Down Expand Up @@ -71,7 +71,7 @@ export default function App() {
const [createURL, setCreateURL] = useState("")
const [createOpen, setCreateOpen] = useState(false)
const [tipOpen, setTipOpen] = useState(false)
const [options, setOptions] = useState<string[]>([]);
const [options, setOptions] = useState<RepoOption[]>([]);
const [path, setPath] = useState<Path | undefined>();
const [isSubmit, setIsSubmit] = useState<boolean>(false);
const desktopChartRef = useRef<GraphRef["current"]>(null)
Expand Down Expand Up @@ -138,17 +138,26 @@ export default function App() {
return
}

const graphName = createURL.split('/').pop()!
const json = await result.json()
// Mirror the backend's `urlparse(url).path.split('/')[-1]` so the composed
// graph name matches the one analyze_repo actually created — a raw split
// would keep any query string (".../repo?tab=readme" -> "repo?tab=readme").
const project = projectNameFromURL(createURL)
const option: RepoOption = {
project,
branch: json.branch,
graph: composeGraphName(project, json.branch),
}

setOptions(prev => [...prev, graphName])
setSelectedValue(graphName)
setOptions(prev => [...prev, option])
setSelectedValue(option.graph)
setCreateURL("")
setCreateOpen(false)
setIsSubmit(false)

toast({
title: "Success",
description: `Project ${graphName} created successfully`,
description: `Project ${project} created successfully`,
})
}

Expand Down
6 changes: 3 additions & 3 deletions app/src/components/code-graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Input from './Input';
import { Checkbox } from '@/components/ui/checkbox';
import type { Position } from "./graphView";
import { prepareArg } from '../utils';
import { GraphRef } from "@/lib/utils";
import { GraphRef, RepoOption } from "@/lib/utils";
import { dataToGraphData } from "@falkordb/canvas";
import type { Node as CanvasNode, Link as CanvasLink, GraphData as CanvasData } from "@falkordb/canvas";
import GraphView from "./graphView";
Expand All @@ -29,8 +29,8 @@ interface Props {
setData: Dispatch<SetStateAction<GraphData>>,
onFetchGraph: (graphName: string) => Promise<void>,
onFetchNode: (nodeIds: number[]) => Promise<GraphData>,
options: string[]
setOptions: Dispatch<SetStateAction<string[]>>
options: RepoOption[]
setOptions: Dispatch<SetStateAction<RepoOption[]>>
isShowPath: boolean
setPath: Dispatch<SetStateAction<Path | undefined>>
canvasRef: GraphRef
Expand Down
12 changes: 7 additions & 5 deletions app/src/components/combobox.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { toast } from "@/components/ui/use-toast";
import { useEffect, useState } from "react";
import { RepoOption, repoLabel, toRepoOption } from "@/lib/utils";

const AUTH_HEADERS: HeadersInit = import.meta.env.VITE_SECRET_TOKEN
? { 'Authorization': `Bearer ${import.meta.env.VITE_SECRET_TOKEN}` }
: {};

interface Props {
options: string[]
setOptions: (options: string[]) => void
options: RepoOption[]
setOptions: (options: RepoOption[]) => void
selectedValue: string
onSelectedValue: (value: string) => Promise<void>

Expand Down Expand Up @@ -37,7 +38,8 @@ export default function Combobox({ options, setOptions, selectedValue, onSelecte
}

const json = await result.json()
setOptions(json.repositories)
const repositories: unknown[] = Array.isArray(json.repositories) ? json.repositories : []
setOptions(repositories.map(toRepoOption))
}

useEffect(() => {
Expand Down Expand Up @@ -65,8 +67,8 @@ export default function Combobox({ options, setOptions, selectedValue, onSelecte
{
options.length !== 0 &&
options.map((option) => (
<SelectItem key={option} value={option}>
{option}
<SelectItem key={option.graph} value={option.graph}>
{repoLabel(option)}
</SelectItem>
))
}
Expand Down
51 changes: 51 additions & 0 deletions app/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,57 @@ export type PathData = {
links: any[]
}

export const DEFAULT_BRANCH = "_default"

// A single entry returned by /api/list_repos: `graph` is the underlying
// FalkorDB graph name to query, `project`/`branch` are used for display.
export type RepoOption = {
project: string
branch: string
graph: string
}

// Normalizes a /api/list_repos entry into a RepoOption. Older/production
// backends may still return plain repo name strings instead of
// {project, branch, graph} objects — handle both so the UI never renders
// "undefined (undefined)" or crashes when pointed at a mismatched backend.
export function toRepoOption(entry: unknown): RepoOption {
if (typeof entry === "string") {
return { project: entry, branch: DEFAULT_BRANCH, graph: entry }
}
const repo = entry as Partial<RepoOption> | null | undefined
const branch = repo?.branch ?? DEFAULT_BRANCH
// A backend that reports a project/branch pair without the composed key
// still has to be queried by that key, otherwise the UI would show the
// branch but silently load the default one.
const graph = repo?.graph
?? (repo?.project ? composeGraphName(repo.project, branch) : "")
return {
project: repo?.project ?? graph,
branch,
graph,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

export function repoLabel(repo: RepoOption): string {
return repo.branch === DEFAULT_BRANCH ? repo.project : `${repo.project} (${repo.branch})`
}

export function composeGraphName(project: string, branch?: string | null): string {
return `code:${project}:${branch || DEFAULT_BRANCH}`
}

// Mirrors the backend's `urlparse(url).path.split('/')[-1]`, so the name the
// UI derives for a freshly analyzed repo matches the one the API created.
// Falls back to a plain split for inputs URL() cannot parse.
export function projectNameFromURL(url: string): string {
try {
return new URL(url).pathname.split("/").pop() ?? ""
} catch {
return url.split("/").pop() ?? ""
}
}

export type PathNode = {
id?: number
name?: string
Expand Down
30 changes: 29 additions & 1 deletion e2e/logic/POM/codeGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,35 @@ export default class CodeGraph extends BasePage {

async isAtBottom(): Promise<boolean> {
const { scrollTop, scrollHeight, clientHeight } = await this.getScrollMetrics();
return Math.abs(scrollTop + clientHeight - scrollHeight) < 1;
// Fractional scroll metrics (device pixel ratio / zoom) mean the sum
// rarely lands on an exact integer, so allow a sub-pixel tolerance.
return Math.abs(scrollTop + clientHeight - scrollHeight) <= 2;
}

/**
* Waits for the chat's auto-scroll to settle at the bottom.
*
* The answer streams in and the scroll is animated, so the container
* keeps growing for an indeterminate time after the message is sent —
* polling avoids depending on a fixed delay that is racy on slower CI
* machines. Requires the content to have stopped growing as well, so an
* auto-scroll that reaches the bottom once and then stops following the
* streamed response still fails.
*/
async waitForAtBottom(timeout = 15000): Promise<boolean> {
const pollingInterval = 250;
const deadline = Date.now() + timeout;
let previousHeight = -1;

do {
const { scrollTop, scrollHeight, clientHeight } = await this.getScrollMetrics();
const atBottom = Math.abs(scrollTop + clientHeight - scrollHeight) <= 2;
if (atBottom && scrollHeight === previousHeight) return true;
Comment on lines +387 to +389
previousHeight = scrollHeight;
await this.page.waitForTimeout(pollingInterval);
} while (Date.now() < deadline);

return false;
}

async getpreviousQuestionLoadingImage(): Promise<boolean> {
Expand Down
13 changes: 9 additions & 4 deletions e2e/seed_test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,25 @@ def main():
getattr(graphrag_sdk, "__version__", "?"),
sdk_path,
)
Project(
sdk_project = Project(
name="GraphRAG-SDK",
path=sdk_path,
url="https://github.com/FalkorDB/GraphRAG-SDK",
).analyze_sources()
)
sdk_project.analyze_sources()

for url in REPOS:
logger.info("Seeding %s ...", url)
proj = Project.from_git_repository(url)
proj.analyze_sources()
logger.info("Done seeding %s", url)

ensure_calls_edges("GraphRAG-SDK")
ensure_search_term_variety("GraphRAG-SDK")
# Use the actual composed graph name (e.g. "code:GraphRAG-SDK:_default")
# that analyze_sources() wrote into -- not the bare project name, which
# would silently create/write a separate, unused graph.
graph_name = sdk_project.graph.name
ensure_calls_edges(graph_name)
ensure_search_term_variety(graph_name)

logger.info("All test data seeded successfully.")

Expand Down
3 changes: 1 addition & 2 deletions e2e/tests/chat.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ test.describe("Chat tests", () => {
const { scrollTop } = await chat.getScrollMetrics();
expect(scrollTop).toBeLessThanOrEqual(1);
await chat.sendMessage(Node_Question);
await delay(500); // delay for scroll
expect(await chat.isAtBottom()).toBe(true);
expect(await chat.waitForAtBottom()).toBe(true);
});

test(`Validate consistent UI responses for repeated questions in chat`, async () => {
Expand Down
Loading