Skip to content

Commit

Permalink
npm iMerge branch 'main' into monitor
Browse files Browse the repository at this point in the history
  • Loading branch information
Anchel123 committed Apr 4, 2024
2 parents f7aca5a + 9f5155e commit afac503
Show file tree
Hide file tree
Showing 18 changed files with 824 additions and 38 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Playwright Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest

services:
falkordb:
image: falkordb/falkordb:latest
ports:
- 3000:3000

steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ yarn-error.log*
next-env.d.ts

/.vscode/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
33 changes: 33 additions & 0 deletions app/api/graph/[graph]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth/next";
import authOptions, { getConnection } from "../../auth/[...nextauth]/options";

// eslint-disable-next-line import/prefer-default-export
export async function DELETE(request: NextRequest, { params }: { params: { graph: string } }) {

const session = await getServerSession(authOptions)
const id = session?.user?.id
if (!id) {
return NextResponse.json({ message: "Not authenticated" }, { status: 401 })
}

const client = await getConnection(session.user)
if (!client) {
return NextResponse.json({ message: "Not authenticated" }, { status: 401 })
}

const graphId = params.graph;

try {
if (graphId) {

const graph = client.selectGraph(graphId);

await graph.delete()

return NextResponse.json({ message: `${graphId} graph deleted` })
}
} catch (err: unknown) {
return NextResponse.json({ message: (err as Error).message }, { status: 400 })
}
}
Binary file modified app/favicon.ico
Binary file not shown.
39 changes: 22 additions & 17 deletions app/graph/DataPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";

export default function DataPanel({node}: {node: Node}) {
export default function DataPanel({ node }: { node: Node }) {
return (
<Table>
<TableHeader>
Expand All @@ -12,25 +12,30 @@ export default function DataPanel({node}: {node: Node}) {
</TableHeader>
<TableBody>
{
Object.entries(node).map((row, index) => (
Object.entries(node).filter((row) => Object.values(row)[0] !== "category" && Object.values(row)[0] !== "color").map((row, index) => (
// eslint-disable-next-line react/no-array-index-key
<TableRow key={index}>
{
Object.values(row).map((cell, cellIndex) => (
// eslint-disable-next-line react/no-array-index-key
<TableCell key={cellIndex}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="max-w-96 truncate">
{JSON.stringify(cell)}
</TooltipTrigger>
<TooltipContent>
<p>{JSON.stringify(cell)}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</TableCell>
))
Object.values(row).map((cell, cellIndex) => {

const strCell = JSON.stringify(cell)
const text = cellIndex === 1 ? JSON.parse(strCell) : strCell
return (
// eslint-disable-next-line react/no-array-index-key
<TableCell key={cellIndex}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="max-w-96 truncate">
{text}
</TooltipTrigger>
<TooltipContent>
<p>{text}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</TableCell>
)
})
}
</TableRow>
))
Expand Down
11 changes: 10 additions & 1 deletion app/graph/GraphList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import { useState, useEffect, Dispatch, SetStateAction } from 'react';
import { useToast } from "@/components/ui/use-toast"
import Combobox from '../components/combobox';

interface Props {
onSelectedGraph: Dispatch<SetStateAction<string>>,
onDelete: boolean,
}
// A component that renders an input box for Cypher queries
export default function GraphsList({onSelectedGraph}: { onSelectedGraph: Dispatch<SetStateAction<string>> }) {
export default function GraphsList({onSelectedGraph, onDelete}: Props) {

const [graphs, setGraphs] = useState<string[]>([]);
const [selectedGraph, setSelectedGraph] = useState("");
Expand All @@ -29,6 +33,11 @@ export default function GraphsList({onSelectedGraph}: { onSelectedGraph: Dispatc
})
}, [toast])

useEffect(() => {
setGraphs((prevGraphs: string[]) => [...prevGraphs.filter((graph) => graph !== selectedGraph)])
setSelectedGraph('')
}, [onDelete])

const setSelectedValue = (graph: string) => {
setSelectedGraph(graph)
onSelectedGraph(graph)
Expand Down
15 changes: 9 additions & 6 deletions app/graph/GraphView.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import CytoscapeComponent from "react-cytoscapejs";
import { toast } from "@/components/ui/use-toast";
import cytoscape, { ElementDefinition, EventObject, NodeDataDefinition } from "cytoscape";
import { useRef, useState, useImperativeHandle, Ref } from "react";
import { useRef, useState, useImperativeHandle, forwardRef } from "react";
import { signOut } from "next-auth/react";
import fcose from 'cytoscape-fcose';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
Expand Down Expand Up @@ -89,12 +89,11 @@ export interface GraphViewRef {
}

interface GraphViewProps {
ref: Ref<GraphViewRef>,
graph: Graph,
darkmode: boolean
}

export function GraphView({ graph, darkmode, ref }: GraphViewProps) {
const GraphView = forwardRef(({ graph, darkmode }: GraphViewProps, ref) => {

const [selectedNode, setSelectedNode] = useState<Node | null>(null);

Expand All @@ -103,7 +102,7 @@ export function GraphView({ graph, darkmode, ref }: GraphViewProps) {
const dataPanel = useRef<ImperativePanelHandle>(null)

useImperativeHandle(ref, () => ({
expand: (elements) => {
expand: (elements: ElementDefinition[]) => {
const chart = chartRef.current
if (chart) {
chart.elements().remove()
Expand Down Expand Up @@ -171,7 +170,7 @@ export function GraphView({ graph, darkmode, ref }: GraphViewProps) {
setSelectedNode(node);
dataPanel.current?.expand();
}

return (
<ResizablePanelGroup direction="horizontal">
<ResizablePanel className="h-full flex flex-col">
Expand Down Expand Up @@ -206,4 +205,8 @@ export function GraphView({ graph, darkmode, ref }: GraphViewProps) {


)
}
});

GraphView.displayName = "GraphView";

export default GraphView;
29 changes: 26 additions & 3 deletions app/graph/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,29 @@ const COLORS_ORDER = [
"pink",
]


const NODE_RESERVED_KEYS = ["parent", "id", "position"]
const NODE_ALTERNATIVE_RESERVED_KEYS = ["_parent_", "_id_", "_position_"]
// Used to avoid using reserved words in cytoscape `NodeDataDefinition`
function nodeSafeKey(key: string): string {
const index = NODE_RESERVED_KEYS.indexOf(key);
if (index === -1) {
return key;
}
return NODE_ALTERNATIVE_RESERVED_KEYS[index];
}

const EDGE_RESERVED_KEYS = ["source", "target", "id", "position"]
const EDGE_ALTERNATIVE_RESERVED_KEYS = ["_source_", "_target_", "_parent_", "_id_", "_position_"]
// Used to avoid using reserved words in cytoscape `EdgeDataDefinition`
function edgeSafeKey(key: string): string {
const index = EDGE_RESERVED_KEYS.indexOf(key);
if (index === -1) {
return key;
}
return EDGE_ALTERNATIVE_RESERVED_KEYS[index];
}

export function getCategoryColorName(index: number): string {
const colorIndex = index<COLORS_ORDER.length ? index : 0
return COLORS_ORDER[colorIndex]
Expand Down Expand Up @@ -137,7 +160,7 @@ export class Graph {

const edge: EdgeDataDefinition = { source: sourceId, target: destinationId, label: cell.relationshipType }
Object.entries(cell.properties).forEach(([key, value]) => {
edge[key] = value as string;
edge[edgeSafeKey(key)] = value as string;
});
this.edgesMap.set(cell.id, edge)
this.elements.push({data:edge})
Expand Down Expand Up @@ -192,7 +215,7 @@ export class Graph {
color: getCategoryColorValue(category.index)
}
Object.entries(cell.properties).forEach(([key, value]) => {
node[key] = value as string;
node[nodeSafeKey(key)] = value as string;
});
this.nodesMap.set(cell.id, node)
this.elements.push({data:node})
Expand All @@ -204,7 +227,7 @@ export class Graph {
currentNode.category = category.name;
currentNode.color = getCategoryColorValue(category.index)
Object.entries(cell.properties).forEach(([key, value]) => {
currentNode[key] = value as string;
currentNode[nodeSafeKey(key)] = value as string;
});
newElements.push({data:currentNode})
}
Expand Down
4 changes: 2 additions & 2 deletions app/graph/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Query, QueryState } from "./query";
import { TableView } from "./tableview";
import MetaDataView from "./metadataview";
import { Graph } from "./model";
import { GraphView, GraphViewRef } from "./GraphView";
import GraphView, { GraphViewRef } from "./GraphView";



Expand Down Expand Up @@ -87,7 +87,7 @@ export default function Page() {
<div className="flex flex-col grow border border-gray-300 rounded-lg p-2 overflow-auto">
{
graph.Id &&
<Tabs defaultValue="graph" className="grow flex flex-col justify-center items-center">
<Tabs defaultValue={showGraph ? "graph" : "metaData"} className="grow flex flex-col justify-center items-center">
<TabsList className="border w-fit">
<TabsTrigger value="metaData">MetaData</TabsTrigger>
{showGraph && <TabsTrigger value="data">Data</TabsTrigger>}
Expand Down
73 changes: 70 additions & 3 deletions app/graph/query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
import { useState } from "react";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
import { Menu, Search, Trash2 } from "lucide-react";
import { useToast } from "@/components/ui/use-toast";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import GraphsList from "./GraphList";


Expand All @@ -20,22 +25,84 @@ export function Query({ onSubmit, onQueryUpdate, className = "" }: {
}) {
const [query, setQuery] = useState('');
const [graphName, setGraphName] = useState('');
const [onDelete, setOnDelete] = useState<boolean>(false);
const { toast } = useToast();

onQueryUpdate(new QueryState(query, graphName))

const handleDelete = () => {
fetch(`/api/graph/${encodeURIComponent(graphName)}`, {
method: 'DELETE',
}).then(res => res.json()).then((data) => {
toast({
title: "Delete graph",
description: data.message,
})
setOnDelete(prev => !prev)
setGraphName('')
}).catch(err => {
toast({
title: "Error",
description: (err as Error).message,
})
})
}

return (
<form
className={cn("flex flex-col space-y-3 md:flex-row md:space-x-3 md:space-y-0", className)}
onSubmit={onSubmit}>
<div className="items-center flex flex-row space-x-3">
<Label htmlFor="query" className="text">Query</Label>
<GraphsList onSelectedGraph={setGraphName} />
<GraphsList onDelete={onDelete} onSelectedGraph={setGraphName} />
</div>
<div className="flex flex-row space-x-3 w-full md:w-8/12">
<div className="flex flex-row space-x-3 w-full md:w-8/12 items-center">
<Input id="query" className="border-gray-500 w-full"
placeholder="MATCH (n)-[e]-() RETURN n,e limit 100" type="text" onChange={(event) => setQuery(event.target.value)} />
<Button type="submit">Run</Button>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button type="submit" className="mr-16"><Search /></Button>
</TooltipTrigger>
<TooltipContent>
<p>Query</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<AlertDialog>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>
<Menu />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel className="flex justify-around">Actions</DropdownMenuLabel>
<DropdownMenuSeparator />
{graphName &&
<DropdownMenuItem className="flex justify-around">
<AlertDialogTrigger className="flex flex-row items-center gap-x-2">
<Trash2 />
<span>Delete graph</span>
</AlertDialogTrigger>
</DropdownMenuItem>
}
</DropdownMenuContent>
</DropdownMenu>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure you?</AlertDialogTitle>
<AlertDialogDescription>
Are you absolutely sure you want to delete {graphName}?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={() => handleDelete()}>Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</form>
)
}
Loading

0 comments on commit afac503

Please sign in to comment.