Skip to content

Commit db56dcd

Browse files
authored
Merge branch 'staging' into add-create-repo
2 parents 5a97eaf + d1ed6c1 commit db56dcd

21 files changed

Lines changed: 701 additions & 273 deletions

.github/workflows/playwright.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,20 @@ jobs:
3333
npm install
3434
npm run build
3535
NEXTAUTH_SECRET=SECRET npm start & npx playwright test --reporter=dot,list
36+
- name: Ensure required directories exist
37+
run: |
38+
mkdir -p playwright-report
39+
mkdir -p playwright-report/artifacts
3640
- uses: actions/upload-artifact@v4
37-
if: ${{ !cancelled() }}
41+
if: always()
3842
with:
3943
name: playwright-report
4044
path: playwright-report/
4145
retention-days: 30
46+
- name: Upload failed test screenshots
47+
if: always()
48+
uses: actions/upload-artifact@v4
49+
with:
50+
name: failed-test-screenshots
51+
path: playwright-report/artifacts/
52+
retention-days: 30
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Release image to DockerHub
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
tags: ["v*.*.*"]
7+
branches:
8+
- main
9+
10+
jobs:
11+
build-and-release:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Set tags
18+
run: |
19+
if ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }}; then
20+
echo "TAGS=falkordb/code-graph-frontend:latest,falkordb/code-graph-frontend:${{ github.ref_name }}" >> $GITHUB_ENV
21+
else
22+
echo "TAGS=falkordb/code-graph-frontend:edge" >> $GITHUB_ENV
23+
fi
24+
25+
- name: Login to DockerHub
26+
uses: docker/login-action@v3
27+
with:
28+
username: ${{ secrets.DOCKER_USERNAME }}
29+
password: ${{ secrets.DOCKER_PASSWORD }}
30+
31+
- name: Build image
32+
uses: docker/build-push-action@v5
33+
with:
34+
context: .
35+
file: ./Dockerfile
36+
push: true
37+
tags: ${{ env.TAGS }}

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Use a Node.js base image
2-
FROM node:20
2+
FROM node:22
33

44
# Set working directory
55
WORKDIR /app

README.md

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,80 @@
88
## Getting Started
99
[Live Demo](https://code-graph.falkordb.com/)
1010

11+
## Run locally
12+
This project is composed of three pieces:
13+
14+
1. FalkorDB Graph DB - this is where your graphs are stored and queried
15+
2. Code-Graph-Backend - backend logic
16+
3. Code-Graph-Frontend - website
17+
18+
You'll need to start all three components:
19+
1120
### Run FalkorDB
1221

1322
```bash
1423
docker run -p 6379:6379 -it --rm falkordb/falkordb
1524
```
1625

17-
### Install node packages
26+
### Run Code-Graph-Backend
27+
28+
#### Clone the Backend
1829

1930
```bash
20-
npm install
31+
git clone https://github.com/FalkorDB/code-graph-backend.git
2132
```
2233

23-
### Set your OpenAI key
34+
#### Setup environment variables
35+
36+
`SECRET_TOKEN` - user defined token used to authorize the request
2437

38+
```bash
39+
export FALKORDB_HOST=localhost FALKORDB_PORT=6379 \
40+
OPENAI_API_KEY=<YOUR OPENAI_API_KEY> SECRET_TOKEN=<YOUR_SECRECT_TOKEN> \
41+
FLASK_RUN_HOST=0.0.0.0 FLASK_RUN_PORT=5000
2542
```
26-
export OPENAI_API_KEY=YOUR_OPENAI_API_KEY
43+
44+
#### Install dependencies & run
45+
46+
```bash
47+
cd code-graph-backend
48+
49+
pip install --no-cache-dir -r requirements.txt
50+
51+
flask --app api/index.py run --debug > flask.log 2>&1 &
52+
2753
```
2854

29-
### Run the development server
55+
### Run Code-Graph-Frontend
56+
57+
#### Clone the Frontend
3058

3159
```bash
60+
git clone https://github.com/FalkorDB/code-graph.git
61+
```
62+
63+
#### Setup environment variables
64+
65+
```bash
66+
export BACKEND_URL=http://${FLASK_RUN_HOST}:${FLASK_RUN_PORT} \
67+
SECRET_TOKEN=<YOUR_SECRECT_TOKEN> OPENAI_API_KEY=<YOUR_OPENAI_API_KEY>
68+
```
69+
70+
#### Install dependencies & run
71+
72+
```bash
73+
cd code-graph
74+
npm install
3275
npm run dev
3376
```
3477

35-
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
78+
### Process a local repository
79+
```bash
80+
curl -X POST http://127.0.0.1:5000/analyze_folder -H "Content-Type: application/json" -d '{"path": "<PATH_TO_LOCAL_REPO>", "ignore": ["./.github", "./sbin", "./.git","./deps", "./bin", "./build"]}' -H "Authorization: <YOUR_SECRECT_TOKEN>"
81+
```
82+
83+
Note: At the moment code-graph can analyze both the C & Python source files.
84+
Support for additional languages e.g. JavaScript, Go, Java is planned to be added
85+
in the future.
86+
87+
Browse to [http://localhost:3000](http://localhost:3000)

app/components/chat.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
387387
parentClassName="w-full"
388388
graph={graph}
389389
onValueChange={({ name, id }) => setPath(prev => ({ start: { name, id }, end: prev?.end }))}
390-
value={path?.start?.name}
390+
value={path?.start?.name || ""}
391391
placeholder="Start typing starting point"
392392
type="text"
393393
icon={<ChevronDown color="gray" />}
@@ -397,7 +397,7 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
397397
<Input
398398
parentClassName="w-full"
399399
graph={graph}
400-
value={path?.end?.name}
400+
value={path?.end?.name || ""}
401401
onValueChange={({ name, id }) => setPath(prev => ({ end: { name, id }, start: prev?.start }))}
402402
placeholder="Start typing end point"
403403
type="text"

app/components/code-graph.tsx

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Dispatch, RefObject, SetStateAction, useContext, useEffect, useRef, useState } from "react";
2-
import { GraphData, Node } from "./model";
2+
import { GraphData, Link, Node } from "./model";
33
import { GraphContext } from "./provider";
44
import { Toolbar } from "./toolbar";
55
import { Labels } from "./labels";
6-
import { GitFork, Search, X } from "lucide-react";
6+
import { Download, GitFork, Search, X } from "lucide-react";
77
import ElementMenu from "./elementMenu";
88
import Combobox from "./combobox";
99
import { toast } from '@/components/ui/use-toast';
@@ -21,7 +21,7 @@ const GraphView = dynamic(() => import('./graphView'));
2121
interface Props {
2222
data: GraphData,
2323
setData: Dispatch<SetStateAction<GraphData>>,
24-
onFetchGraph: (graphName: string) => void,
24+
onFetchGraph: (graphName: string) => Promise<void>,
2525
onFetchNode: (nodeIds: number[]) => Promise<GraphData>,
2626
options: string[]
2727
setOptions: Dispatch<SetStateAction<string[]>>
@@ -55,7 +55,7 @@ export function CodeGraph({
5555
let graph = useContext(GraphContext)
5656

5757
const [url, setURL] = useState("");
58-
const [selectedObj, setSelectedObj] = useState<Node>();
58+
const [selectedObj, setSelectedObj] = useState<Node | Link>();
5959
const [selectedObjects, setSelectedObjects] = useState<Node[]>([]);
6060
const [position, setPosition] = useState<Position>();
6161
const [graphName, setGraphName] = useState<string>("");
@@ -145,9 +145,10 @@ export function CodeGraph({
145145
}
146146

147147
run()
148+
148149
}, [graphName])
149150

150-
function handleSelectedValue(value: string) {
151+
async function handleSelectedValue(value: string) {
151152
setGraphName(value)
152153
onFetchGraph(value)
153154
}
@@ -166,29 +167,38 @@ export function CodeGraph({
166167
}
167168

168169
const deleteNeighbors = (nodes: Node[]) => {
170+
169171
if (nodes.length === 0) return;
170-
172+
173+
const expandedNodes: Node[] = []
174+
171175
graph.Elements = {
172-
nodes: graph.Elements.nodes.map(node => {
176+
nodes: graph.Elements.nodes.filter(node => {
177+
if (!node.collapsed) return true
178+
173179
const isTarget = graph.Elements.links.some(link => link.target.id === node.id && nodes.some(n => n.id === link.source.id));
180+
181+
debugger
174182

175-
if (!isTarget || !node.collapsed) return node
183+
if (!isTarget) return true
176184

177-
if (node.expand) {
178-
node.expand = false
179-
deleteNeighbors([node])
185+
const deleted = graph.NodesMap.delete(Number(node.id))
186+
187+
if (deleted && node.expand) {
188+
expandedNodes.push(node)
180189
}
181190

182-
graph.NodesMap.delete(Number(node.id))
183-
}).filter(node => node !== undefined),
191+
return false
192+
}),
184193
links: graph.Elements.links
185194
}
195+
196+
deleteNeighbors(expandedNodes)
186197

187198
graph.removeLinks()
188199
}
189200

190201
const handleExpand = async (nodes: Node[], expand: boolean) => {
191-
192202
if (expand) {
193203
const elements = await onFetchNode(nodes.map(n => n.id))
194204

@@ -216,12 +226,11 @@ export function CodeGraph({
216226

217227
const handleSearchSubmit = (node: any) => {
218228
const chart = chartRef.current
219-
229+
220230
if (chart) {
221-
const n = { name: node.properties.name, id: node.id }
222231

223232
let chartNode = graph.Elements.nodes.find(n => n.id == node.id)
224-
233+
225234
if (!chartNode?.visible) {
226235
if (!chartNode) {
227236
chartNode = graph.extend({ nodes: [node], edges: [] }).nodes[0]
@@ -233,8 +242,8 @@ export function CodeGraph({
233242
graph.visibleLinks(true, [chartNode!.id])
234243
setData({ ...graph.Elements })
235244
}
236-
237-
setSearchNode(n)
245+
246+
setSearchNode(chartNode)
238247
setTimeout(() => {
239248
chart.zoomToFit(1000, 150, (n: NodeObject<Node>) => n.id === chartNode!.id);
240249
}, 0)
@@ -252,6 +261,33 @@ export function CodeGraph({
252261
setData({ ...graph.Elements })
253262
}
254263

264+
const handleDownloadImage = async () => {
265+
try {
266+
const canvas = document.querySelector('.force-graph-container canvas') as HTMLCanvasElement;
267+
if (!canvas) {
268+
toast({
269+
title: "Error",
270+
description: "Canvas not found",
271+
variant: "destructive",
272+
});
273+
return;
274+
}
275+
276+
const dataURL = canvas.toDataURL('image/webp');
277+
const link = document.createElement('a');
278+
link.href = dataURL;
279+
link.download = `${graphName}.webp`;
280+
link.click();
281+
} catch (error) {
282+
console.error('Error downloading graph image:', error);
283+
toast({
284+
title: "Error",
285+
description: "Failed to download image. Please try again.",
286+
variant: "destructive",
287+
});
288+
}
289+
};
290+
255291
return (
256292
<div className="h-full w-full flex flex-col gap-4 p-8 bg-gray-100">
257293
<header className="flex flex-col gap-4">
@@ -271,8 +307,7 @@ export function CodeGraph({
271307
<div className='flex gap-4'>
272308
<Input
273309
graph={graph}
274-
value={searchNode.name}
275-
onValueChange={({ name }) => setSearchNode({ name })}
310+
onValueChange={(node) => setSearchNode(node)}
276311
icon={<Search />}
277312
handleSubmit={handleSearchSubmit}
278313
node={searchNode}
@@ -345,6 +380,12 @@ export function CodeGraph({
345380
className="pointer-events-auto"
346381
chartRef={chartRef}
347382
/>
383+
<button
384+
className="pointer-events-auto bg-white p-2 rounded-md"
385+
onClick={handleDownloadImage}
386+
>
387+
<Download />
388+
</button>
348389
</div>
349390
</div>
350391
<ElementMenu
@@ -371,7 +412,7 @@ export function CodeGraph({
371412
setSelectedObjects={setSelectedObjects}
372413
setPosition={setPosition}
373414
onFetchNode={onFetchNode}
374-
deleteNeighbors={deleteNeighbors}
415+
handleExpand={handleExpand}
375416
isShowPath={isShowPath}
376417
setPath={setPath}
377418
isPathResponse={isPathResponse}

0 commit comments

Comments
 (0)