Skip to content

Commit

Permalink
Merge branch 'main' into monitor
Browse files Browse the repository at this point in the history
  • Loading branch information
AviAvni committed Apr 4, 2024
2 parents afac503 + 1ff0ca3 commit 21858f7
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 122 deletions.
2 changes: 1 addition & 1 deletion app/graph/GraphView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ const GraphView = forwardRef(({ graph, darkmode }: GraphViewProps, ref) => {
setSelectedNode(node);
dataPanel.current?.expand();
}

return (
<ResizablePanelGroup direction="horizontal">
<ResizablePanel className="h-full flex flex-col">
Expand Down
189 changes: 104 additions & 85 deletions app/graph/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function nodeSafeKey(key: string): string {
const index = NODE_RESERVED_KEYS.indexOf(key);
if (index === -1) {
return key;
}
}
return NODE_ALTERNATIVE_RESERVED_KEYS[index];
}

Expand All @@ -42,17 +42,17 @@ 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
const colorIndex = index < COLORS_ORDER.length ? index : 0
return COLORS_ORDER[colorIndex]
}

function getCategoryColorValue(index=0): string {
const colorIndex = index<COLORS_ORDER.length ? index : 0
function getCategoryColorValue(index = 0): string {
const colorIndex = index < COLORS_ORDER.length ? index : 0
const colorName = COLORS_ORDER[colorIndex]

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -121,7 +121,7 @@ export class Graph {
get Data(): any[] {
return this.data;
}

public static empty(): Graph {
return new Graph("", [], [], new Map<string, Category>(), new Map<number, NodeDataDefinition>(), new Map<number, EdgeDataDefinition>())
}
Expand All @@ -134,6 +134,92 @@ export class Graph {
return graph
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
private extendNode(cell: any, newElements: ElementDefinition[]) {
// check if category already exists in categories
let category = this.categoriesMap.get(cell.labels[0])
if (!category) {
category = { name: cell.labels[0], index: this.categoriesMap.size, show: true }
this.categoriesMap.set(category.name, category)
this.categories.push(category)
}

// check if node already exists in nodes or fake node was created
const currentNode = this.nodesMap.get(cell.id)
if (!currentNode) {
const node: NodeDataDefinition = {
id: cell.id.toString(),
name: cell.id.toString(),
category: category.name,
color: getCategoryColorValue(category.index)
}
Object.entries(cell.properties).forEach(([key, value]) => {
node[nodeSafeKey(key)] = value as string;
});
this.nodesMap.set(cell.id, node)
this.elements.push({ data: node })
newElements.push({ data: node })
} else if (currentNode.category === "") {
// set values in a fake node
currentNode.id = cell.id.toString();
currentNode.name = cell.id.toString();
currentNode.category = category.name;
currentNode.color = getCategoryColorValue(category.index)
Object.entries(cell.properties).forEach(([key, value]) => {
currentNode[nodeSafeKey(key)] = value as string;
});
newElements.push({ data: currentNode })
}
return newElements
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
private extendEdge(cell: any, newElements: ElementDefinition[]) {
const currentEdge = this.edgesMap.get(cell.id)
if (!currentEdge) {
const sourceId = cell.sourceId.toString();
const destinationId = cell.destinationId.toString()

const edge: EdgeDataDefinition = { source: sourceId, target: destinationId, label: cell.relationshipType }
Object.entries(cell.properties).forEach(([key, value]) => {
edge[edgeSafeKey(key)] = value as string;
});
this.edgesMap.set(cell.id, edge)
this.elements.push({ data: edge })
newElements.push({ data: edge })

// creates a fakeS node for the source and target
let source = this.nodesMap.get(cell.sourceId)
if (!source) {
source = {
id: cell.sourceId.toString(),
name: cell.sourceId.toString(),
value: "",
category: "",
color: getCategoryColorValue()
}
this.nodesMap.set(cell.sourceId, source)
this.elements.push({ data: source })
newElements.push({ data: source })
}

let destination = this.nodesMap.get(cell.destinationId)
if (!destination) {
destination = {
id: cell.destinationId.toString(),
name: cell.destinationId.toString(),
value: "",
category: "",
color: getCategoryColorValue()
}
this.nodesMap.set(cell.destinationId, destination)
this.elements.push({ data: destination })
newElements.push({ data: destination })
}
}
return newElements
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public extend(results: any): ElementDefinition[] {

Expand All @@ -151,86 +237,19 @@ export class Graph {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Object.values(row).forEach((cell: any) => {
if (cell instanceof Object) {
if (cell.relationshipType) {

const currentEdge = this.edgesMap.get(cell.id)
if (!currentEdge) {
const sourceId = cell.sourceId.toString();
const destinationId = cell.destinationId.toString()

const edge: EdgeDataDefinition = { source: sourceId, target: destinationId, label: cell.relationshipType }
Object.entries(cell.properties).forEach(([key, value]) => {
edge[edgeSafeKey(key)] = value as string;
});
this.edgesMap.set(cell.id, edge)
this.elements.push({data:edge})
newElements.push({data:edge})

// creates a fakeS node for the source and target
let source = this.nodesMap.get(cell.sourceId)
if (!source) {
source = {
id: cell.sourceId.toString(),
name: cell.sourceId.toString(),
value: "",
category: "",
color: getCategoryColorValue()
}
this.nodesMap.set(cell.sourceId, source)
this.elements.push({data:source})
newElements.push({data:source})
}

let destination = this.nodesMap.get(cell.destinationId)
if (!destination) {
destination = {
id: cell.destinationId.toString(),
name: cell.destinationId.toString(),
value: "",
category: "",
color: getCategoryColorValue()
}
this.nodesMap.set(cell.destinationId, destination)
this.elements.push({data:destination})
newElements.push({data:destination})
}
}
if (cell.nodes) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
cell.nodes.forEach((node: any) => {
this.extendNode(node, newElements)
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
cell.edges.forEach((edge: any) => {
this.extendEdge(edge, newElements)
})
} else if (cell.relationshipType) {
this.extendEdge(cell, newElements)
} else if (cell.labels) {

// check if category already exists in categories
let category = this.categoriesMap.get(cell.labels[0])
if (!category) {
category = { name: cell.labels[0], index: this.categoriesMap.size, show: true }
this.categoriesMap.set(category.name, category)
this.categories.push(category)
}

// check if node already exists in nodes or fake node was created
const currentNode = this.nodesMap.get(cell.id)
if (!currentNode) {
const node: NodeDataDefinition = {
id: cell.id.toString(),
name: cell.id.toString(),
category: category.name,
color: getCategoryColorValue(category.index)
}
Object.entries(cell.properties).forEach(([key, value]) => {
node[nodeSafeKey(key)] = value as string;
});
this.nodesMap.set(cell.id, node)
this.elements.push({data:node})
newElements.push({data:node})
} else if (currentNode.category === ""){
// set values in a fake node
currentNode.id = cell.id.toString();
currentNode.name = cell.id.toString();
currentNode.category = category.name;
currentNode.color = getCategoryColorValue(category.index)
Object.entries(cell.properties).forEach(([key, value]) => {
currentNode[nodeSafeKey(key)] = value as string;
});
newElements.push({data:currentNode})
}
this.extendNode(cell, newElements)
}
}
})
Expand Down
33 changes: 22 additions & 11 deletions app/graph/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import { toast } from "@/components/ui/use-toast";
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { signOut } from "next-auth/react";
import { useTheme } from "next-themes";
Expand All @@ -27,9 +27,20 @@ function validateGraphSelection(graphName: string): boolean {

export default function Page() {
const [graph, setGraph] = useState(Graph.empty());
const [value, setValue] = useState<string>("");
const [metaData, setMetaData] = useState<string[]>([]);
const [showGraph, setShowGraph] = useState<boolean>(true);

const [showData, setShowData] = useState<boolean>(true);

useEffect(() => {
if (showGraph) {
setValue("graph")
}else if(showData) {
setValue("data")
}else {
setValue("metadata")
}
}, [showData, showGraph])
const graphView = useRef<GraphViewRef>(null)


Expand All @@ -53,7 +64,7 @@ export default function Page() {
// Proposed abstraction for improved modularity
if (!validateGraphSelection(state.graphName)) return;

const q = state.query.trim() || "MATCH (n)-[e]-() RETURN n,e limit 100";
const q = state.query.trim() || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) RETURN n,e,m limit 100";

const result = await fetch(`/api/graph?graph=${prepareArg(state.graphName)}&query=${prepareArg(q)}`, {
method: 'GET',
Expand All @@ -73,11 +84,11 @@ export default function Page() {
}

const json = await result.json()
setMetaData(json.result.metadata)
const newGraph = Graph.create(state.graphName, json.result)
setGraph(newGraph)
setMetaData(json.result.metadata)
setShowGraph((!!json.result.data && json.result.data.length > 0))

setShowGraph(!!newGraph.Categories && newGraph.Categories.length > 0)
setShowData(!!newGraph.Data && newGraph.Data.length > 0)
graphView.current?.expand(newGraph.Elements)
}

Expand All @@ -87,13 +98,13 @@ export default function Page() {
<div className="flex flex-col grow border border-gray-300 rounded-lg p-2 overflow-auto">
{
graph.Id &&
<Tabs defaultValue={showGraph ? "graph" : "metaData"} className="grow flex flex-col justify-center items-center">
<Tabs value={value} 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>}
{showGraph && <TabsTrigger value="graph">Graph</TabsTrigger>}
<TabsTrigger value="metadata" onClick={() => setValue("metadata")}>Metadata</TabsTrigger>
{showData && <TabsTrigger value="data" onClick={() => setValue("data")}>Data</TabsTrigger>}
{showGraph && <TabsTrigger value="graph" onClick={() => setValue("graph")}>Graph</TabsTrigger>}
</TabsList>
<TabsContent value="metaData" className="grow w-full">
<TabsContent value="metadata" className="grow w-full">
<MetaDataView metadata={metaData} />
</TabsContent>
<TabsContent value="data" className="grow w-full flex-[1_1_0] overflow-auto">
Expand Down
2 changes: 1 addition & 1 deletion app/graph/query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function Query({ onSubmit, onQueryUpdate, className = "" }: {
</div>
<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)} />
placeholder="MATCH (n) OPTIONAL MATCH (n)-[e]-(m) RETURN n,e,m limit 100" type="text" onChange={(event) => setQuery(event.target.value)} />
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
Expand Down
56 changes: 48 additions & 8 deletions app/graph/tableview.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
'use client'

import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { JSONTree } from "react-json-tree"
import { transparent } from "tailwindcss/colors";
import { Graph } from "./model";

// eslint-disable-next-line import/prefer-default-export
export function TableView({ graph }: { graph: Graph }) {


return (
<Table>
<TableCaption>A list of results</TableCaption>
Expand All @@ -23,14 +29,48 @@ export function TableView({ graph }: { graph: Graph }) {
// 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) => {
// eslint-disable-next-line no-useless-escape
const text = JSON.stringify(cell).replace(/[\[\]{}:,]/g, (match) => {
switch (match) {
case ":": return ': ';
case ",": return ', ';
default: return '';
}
});
return (
// eslint-disable-next-line react/no-array-index-key
<TableCell key={cellIndex}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="max-w-96 truncate">
{typeof cell === "object" ? (
<JSONTree
data={cell}
shouldExpandNodeInitially={() => false}
theme={{
// background
base00: transparent,
// number of keys keys
base03: transparent,
// value number
// base09: transparent,
// value string
// base0B: transparent,
// keys and triangles
// base0D: transparent,
}}
/>
) : (text)}
</TooltipTrigger>
<TooltipContent>
<p>{text}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</TableCell>
)
})
}
</TableRow>
))
Expand Down
Loading

0 comments on commit 21858f7

Please sign in to comment.