From 64f2a32b482317db558f12e6f2c11a46f23d1881 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 15 Jan 2025 13:41:07 +0200 Subject: [PATCH 01/65] Fix typo in keywords, optimize suggestion handling, and update cursor context key initialization in EditorComponent --- app/components/EditorComponent.tsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/app/components/EditorComponent.tsx b/app/components/EditorComponent.tsx index c8b28db7..da19d34e 100644 --- a/app/components/EditorComponent.tsx +++ b/app/components/EditorComponent.tsx @@ -78,7 +78,7 @@ const KEYWORDS = [ "ORDER BY", "SKIP", "LIMIT", - "MARGE", + "MERGE", "DELETE", "SET", "WITH", @@ -357,6 +357,8 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre if (!graph.Id || (!monacoInstance && !monacoI)) return const m = monacoI || monacoInstance const sug: Suggestions = getEmptySuggestions() + + sugProvider?.dispose() await Promise.all([ addLabelsSuggestions(sug.labels), @@ -463,11 +465,6 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre run() }, [graph]) - - useEffect(() => { - getSuggestions() - }, [graph.Id]) - useEffect(() => { const interval = setInterval(() => { getSuggestions() @@ -475,8 +472,9 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre return () => { clearInterval(interval) + sugProvider?.dispose() } - }, []) + }, [graph.Id]) const handleEditorWillMount = (monacoI: Monaco) => { monacoI.languages.setMonarchTokensProvider('custom-language', { @@ -557,7 +555,6 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre updatePlaceholderVisibility(); }); - // Initial check updatePlaceholderVisibility(); setMonacoInstance(monacoI) @@ -572,7 +569,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre setBlur(false) }) - const isFirstLine = e.createContextKey('isFirstLine', false as boolean); + const isFirstLine = e.createContextKey('isFirstLine', true); // Update the context key value based on the cursor position e.onDidChangeCursorPosition(() => { From ec456eca4dd29fb0bd2c5b18df238c404a9e981d Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 15 Jan 2025 13:44:58 +0200 Subject: [PATCH 02/65] Refactor ForceGraph component to improve resize handling and remove unused props --- app/components/ForceGraph.tsx | 26 ++++++++++++++++++++------ app/graph/GraphView.tsx | 8 ++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/app/components/ForceGraph.tsx b/app/components/ForceGraph.tsx index 0380c22c..10144fee 100644 --- a/app/components/ForceGraph.tsx +++ b/app/components/ForceGraph.tsx @@ -24,7 +24,6 @@ interface Props { type?: "schema" | "graph" isAddElement?: boolean setSelectedNodes?: Dispatch> - isCollapsed: boolean } const NODE_SIZE = 6 @@ -43,7 +42,6 @@ export default function ForceGraph({ type = "graph", isAddElement = false, setSelectedNodes, - isCollapsed }: Props) { const [parentWidth, setParentWidth] = useState(0) @@ -60,10 +58,26 @@ export default function ForceGraph({ }, [chartRef, data.links.length, data.nodes.length]) useEffect(() => { - if (!parentRef.current) return - setParentWidth(parentRef.current.clientWidth) - setParentHeight(parentRef.current.clientHeight) - }, [parentRef.current?.clientWidth, parentRef.current?.clientHeight, isCollapsed]) + const handleResize = () => { + if (!parentRef.current) return + setParentWidth(parentRef.current.clientWidth) + setParentHeight(parentRef.current.clientHeight) + } + + handleResize() + + const resizeObserver = new ResizeObserver(handleResize) + if (parentRef.current) { + resizeObserver.observe(parentRef.current) + } + + window.addEventListener('resize', handleResize) + + return () => { + resizeObserver.disconnect() + window.removeEventListener('resize', handleResize) + } + }, []) const onFetchNode = async (node: Node) => { const result = await securedFetch(`/api/graph/${graph.Id}/${node.id}`, { diff --git a/app/graph/GraphView.tsx b/app/graph/GraphView.tsx index 0128c162..3c0a779b 100644 --- a/app/graph/GraphView.tsx +++ b/app/graph/GraphView.tsx @@ -21,10 +21,7 @@ import Button from "../components/ui/Button"; import TableView from "./TableView"; const ForceGraph = dynamic(() => import("../components/ForceGraph"), { ssr: false }); - -const EditorComponent = dynamic(() => import("../components/EditorComponent"), { - ssr: false -}) +const EditorComponent = dynamic(() => import("../components/EditorComponent"), {ssr: false}) function GraphView({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, fetchCount, session }: { graph: Graph @@ -278,7 +275,7 @@ function GraphView({ graph, selectedElement, setSelectedElement, runQuery, histo title={!maximize ? "Maximize" : "Minimize"} onClick={() => setMaximize(prev => !prev)} > - {maximize ? : } + {!maximize ? : }
{cooldownTicks === undefined ? : } @@ -292,7 +289,6 @@ function GraphView({ graph, selectedElement, setSelectedElement, runQuery, histo />
Date: Wed, 15 Jan 2025 13:46:57 +0200 Subject: [PATCH 03/65] fix preferences view --- app/api/graph/model.ts | 2 +- app/graph/View.tsx | 10 ++++++---- app/schema/SchemaView.tsx | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/api/graph/model.ts b/app/api/graph/model.ts index 91df1a2e..27ae450a 100644 --- a/app/api/graph/model.ts +++ b/app/api/graph/model.ts @@ -209,7 +209,7 @@ export class Graph { return new Graph(graphName || "", [], [], { nodes: [], links: [] }, new Map(), new Map(), new Map(), new Map(), colors) } - public static create(id: string, results: any, colors?: string[]): Graph { + public static create(id: string, results: { data: Data, metadata: any[] }, colors?: string[]): Graph { const graph = Graph.empty(undefined, colors) graph.extend(results) graph.id = id diff --git a/app/graph/View.tsx b/app/graph/View.tsx index 201fa0af..a33c2aa9 100644 --- a/app/graph/View.tsx +++ b/app/graph/View.tsx @@ -20,7 +20,9 @@ export default function View({ graph, setGraph, selectedValue }: { const handlePreferencesChange = (colors?: string[]) => { setGraph(Graph.create(graph.Id, { data: graph.Data, metadata: graph.Metadata }, colors || colorsArr)) - if (colors) return + if (colors) { + localStorage.removeItem(graph.Id) + } localStorage.setItem(graph.Id, JSON.stringify(colorsArr)); } @@ -40,9 +42,9 @@ export default function View({ graph, setGraph, selectedValue }: { className="w-[30%] h-[50%]" title="Preferences" > -
+

Legends

-
    +
      { colorsArr.map((c, i) => (
    • setHover(c)} onMouseLeave={(() => setHover(""))} key={c} className={cn(`flex gap-8 items-center`)}> @@ -154,7 +156,7 @@ export default function View({ graph, setGraph, selectedValue }: {
Date: Thu, 16 Jan 2025 14:25:26 +0200 Subject: [PATCH 05/65] change the schema elements properties format in the db --- app/api/graph/model.ts | 49 ++++++++++++++++++++++-------- app/graph/Selector.tsx | 2 +- app/graph/View.tsx | 2 +- app/graph/page.tsx | 4 +-- app/schema/SchemaCreateElement.tsx | 4 +-- app/schema/SchemaDataPanel.tsx | 2 +- app/schema/SchemaView.tsx | 17 ++++++++--- app/schema/page.tsx | 2 +- 8 files changed, 57 insertions(+), 25 deletions(-) diff --git a/app/api/graph/model.ts b/app/api/graph/model.ts index 27ae450a..ec94948b 100644 --- a/app/api/graph/model.ts +++ b/app/api/graph/model.ts @@ -1,9 +1,33 @@ +/* eslint-disable one-var */ /* eslint-disable no-param-reassign */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { EdgeDataDefinition, NodeDataDefinition } from 'cytoscape'; import { LinkObject, NodeObject } from 'react-force-graph-2d'; +const getSchemaValue = (value: string): string[] => { + let unique, required, type, description + if (value.includes("!")) { + value = value.replace("!", "") + unique = "true" + } else { + unique = "false" + } + if (value.includes("*")) { + value = value.replace("*", "") + required = "true" + } else { + required = "false" + } + if (value.includes("-")) { + [type, description] = value.split("-") + } else { + type = "string" + description = "" + } + return [type, description, unique, required] +} + export type Node = NodeObject<{ id: number, category: string[], @@ -209,14 +233,14 @@ export class Graph { return new Graph(graphName || "", [], [], { nodes: [], links: [] }, new Map(), new Map(), new Map(), new Map(), colors) } - public static create(id: string, results: { data: Data, metadata: any[] }, colors?: string[]): Graph { + public static create(id: string, results: { data: Data, metadata: any[] }, isCollapsed: boolean, isSchema: boolean, colors?: string[],): Graph { const graph = Graph.empty(undefined, colors) - graph.extend(results) + graph.extend(results, isCollapsed, isSchema) graph.id = id return graph } - public extendNode(cell: NodeCell, collapsed = false) { + public extendNode(cell: NodeCell, collapsed: boolean, isSchema: boolean) { // check if category already exists in categories const categories = this.createCategory(cell.labels.length === 0 ? [""] : cell.labels) // check if node already exists in nodes or fake node was created @@ -233,7 +257,7 @@ export class Graph { data: {} } Object.entries(cell.properties).forEach(([key, value]) => { - node.data[key] = value as string; + node.data[key] = isSchema ? getSchemaValue(value) : value; }); this.nodesMap.set(cell.id, node) this.elements.nodes.push(node) @@ -248,7 +272,7 @@ export class Graph { currentNode.expand = false currentNode.collapsed = collapsed Object.entries(cell.properties).forEach(([key, value]) => { - currentNode.data[key] = value as string; + currentNode.data[key] = isSchema ? getSchemaValue(value) : value; }); // remove empty category if there are no more empty nodes category @@ -272,9 +296,8 @@ export class Graph { return currentNode } - public extendEdge(cell: LinkCell, collapsed = false) { + public extendEdge(cell: LinkCell, collapsed: boolean, isSchema: boolean) { const label = this.createLabel(cell.relationshipType) - const currentEdge = this.linksMap.get(cell.id) if (!currentEdge) { @@ -369,7 +392,7 @@ export class Graph { } Object.entries(cell.properties).forEach(([key, value]) => { - link.data[key] = value as string; + link.data[key] = isSchema ? getSchemaValue(value) : value; }); this.linksMap.set(cell.id, link) @@ -381,7 +404,7 @@ export class Graph { return currentEdge } - public extend(results: { data: Data, metadata: any[] }, collapsed = false): (Node | Link)[] { + public extend(results: { data: Data, metadata: any[] }, collapsed = false, isSchema = false): (Node | Link)[] { const newElements: (Node | Link)[] = [] const data = results?.data @@ -399,15 +422,15 @@ export class Graph { if (cell instanceof Object) { if (cell.nodes) { cell.nodes.forEach((node: any) => { - newElements.push(this.extendNode(node, collapsed)) + newElements.push(this.extendNode(node, collapsed, isSchema)) }) cell.edges.forEach((edge: any) => { - newElements.push(this.extendEdge(edge, collapsed)) + newElements.push(this.extendEdge(edge, collapsed, isSchema)) }) } else if (cell.relationshipType) { - newElements.push(this.extendEdge(cell, collapsed)) + newElements.push(this.extendEdge(cell, collapsed, isSchema)) } else if (cell.labels) { - newElements.push(this.extendNode(cell, collapsed)) + newElements.push(this.extendNode(cell, collapsed, isSchema)) } } }) diff --git a/app/graph/Selector.tsx b/app/graph/Selector.tsx index 96ad75f3..6f4f8138 100644 --- a/app/graph/Selector.tsx +++ b/app/graph/Selector.tsx @@ -84,7 +84,7 @@ export default function Selector({ onChange, graphName, setGraphName, queries, r const json = await result.json() if (json.result) { - setSchema(Graph.create(name, json.result)) + setSchema(Graph.create(name, json.result, false, true)) } } onChange(name) diff --git a/app/graph/View.tsx b/app/graph/View.tsx index a33c2aa9..9b19def3 100644 --- a/app/graph/View.tsx +++ b/app/graph/View.tsx @@ -19,7 +19,7 @@ export default function View({ graph, setGraph, selectedValue }: { const [editable, setEditable] = useState("") const handlePreferencesChange = (colors?: string[]) => { - setGraph(Graph.create(graph.Id, { data: graph.Data, metadata: graph.Metadata }, colors || colorsArr)) + setGraph(Graph.create(graph.Id, { data: graph.Data, metadata: graph.Metadata }, false, true, colors || colorsArr)) if (colors) { localStorage.removeItem(graph.Id) } diff --git a/app/graph/page.tsx b/app/graph/page.tsx index 760ece24..7e89fdb7 100644 --- a/app/graph/page.tsx +++ b/app/graph/page.tsx @@ -87,7 +87,7 @@ export default function Page() { const queryArr = queries.some(q => q.text === query) ? queries : [...queries, { text: query, metadata: result.metadata }] setQueries(queryArr) localStorage.setItem("query history", JSON.stringify(queryArr)) - const g = Graph.create(graphName, result, graph.Colors) + const g = Graph.create(graphName, result, false, false, graph.Colors) setGraph(g) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -100,7 +100,7 @@ export default function Page() { const queryArr = queries.some(q => q.text === query) ? queries : [...queries, { text: query, metadata: result.metadata }] setQueries(queryArr) localStorage.setItem("query history", JSON.stringify(queryArr)) - setGraph(Graph.create(graphName, result)) + setGraph(Graph.create(graphName, result, false, false, graph.Colors)) setHistoryQuery(query) setQueriesOpen(false) } diff --git a/app/schema/SchemaCreateElement.tsx b/app/schema/SchemaCreateElement.tsx index 45e700aa..13d9f354 100644 --- a/app/schema/SchemaCreateElement.tsx +++ b/app/schema/SchemaCreateElement.tsx @@ -63,7 +63,7 @@ export default function SchemaCreateElement({ onCreate, onExpand, selectedNodes, const handleSetAttribute = (isUndo: boolean, att?: [string, string[]]) => { const newAtt = att || attribute - + if (!newAtt[0] || newAtt[1].some((v) => !v)) { toast({ title: "Error", @@ -194,7 +194,7 @@ export default function SchemaCreateElement({ onCreate, onExpand, selectedNodes, /> }
-

{attributes.length} Attributes

+

{attributes.length} Attributes

diff --git a/app/schema/SchemaDataPanel.tsx b/app/schema/SchemaDataPanel.tsx index bc36331d..4daa9d4f 100644 --- a/app/schema/SchemaDataPanel.tsx +++ b/app/schema/SchemaDataPanel.tsx @@ -143,7 +143,7 @@ export default function SchemaDataPanel({ obj, onExpand, onSetAttributes, onRemo

{label}

-

{attributes.length} Attributes

+

{attributes.length} Attributes

diff --git a/app/schema/SchemaView.tsx b/app/schema/SchemaView.tsx index f9019eb2..0bfaab41 100644 --- a/app/schema/SchemaView.tsx +++ b/app/schema/SchemaView.tsx @@ -28,10 +28,19 @@ interface Props { } const getCreateQuery = (type: boolean, selectedNodes: [Node, Node], attributes: [string, string[]][], label?: string) => { + const formateAttributes: [string, string][] = attributes.map((att) => { + const [key, [t, d, u, r]] = att + let val = `${t}` + if (u === "true") val += "!" + if (r === "true") val += "*" + if (d) val += `-${d}` + return [key, val] + }) + if (type) { - return `CREATE (n${label ? `:${label}` : ""}${attributes?.length > 0 ? ` {${attributes.map(([k, [t, d, u, r]]) => `${k}: ["${t}", "${d}", "${u}", "${r}"]`).join(",")}}` : ""}) RETURN n` + return `CREATE (n${label ? `:${label}` : ""}${formateAttributes?.length > 0 ? ` {${formateAttributes.map(([k, v]) => `${k}: "${v}"`).join(",")}}` : ""}) RETURN n` } - return `MATCH (a), (b) WHERE ID(a) = ${selectedNodes[0].id} AND ID(b) = ${selectedNodes[1].id} CREATE (a)-[e${label ? `:${label}` : ""}${attributes?.length > 0 ? ` {${attributes.map(([k, [t, d, u, un]]) => `${k}: ["${t}", "${d}", "${u}", "${un}"]`).join(",")}}` : ""}]->(b) RETURN e` + return `MATCH (a), (b) WHERE ID(a) = ${selectedNodes[0].id} AND ID(b) = ${selectedNodes[1].id} CREATE (a)-[e${label ? `:${label}` : ""}${formateAttributes?.length > 0 ? ` {${formateAttributes.map(([k, v]) => `${k}: "${v}"`).join(",")}}` : ""}]->(b) RETURN e` } export default function SchemaView({ schema, fetchCount, session }: Props) { @@ -292,10 +301,10 @@ export default function SchemaView({ schema, fetchCount, session }: Props) { const json = await result.json() if (isAddEntity) { - schema.extendNode(json.result.data[0].n) + schema.extendNode(json.result.data[0].n, false, true) setIsAddEntity(false) } else { - schema.extendEdge(json.result.data[0].e, true) + schema.extendEdge(json.result.data[0].e, false, true) setIsAddRelation(false) } diff --git a/app/schema/page.tsx b/app/schema/page.tsx index 0b57ea4f..64e4d215 100644 --- a/app/schema/page.tsx +++ b/app/schema/page.tsx @@ -46,7 +46,7 @@ export default function Page() { if (!result.ok) return const json = await result.json() const colors = localStorage.getItem(schemaName)?.split(/[[\]",]/).filter(c => c) - setSchema(Graph.create(schemaName, json.result, colors)) + setSchema(Graph.create(schemaName, json.result, false, true, colors)) fetchCount() From 86d6fea8f487168a821d93a682383ddbdeab4797 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 20 Jan 2025 12:11:34 +0200 Subject: [PATCH 06/65] commit --- app/components/ForceGraph.tsx | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/app/components/ForceGraph.tsx b/app/components/ForceGraph.tsx index 10144fee..e6a16b88 100644 --- a/app/components/ForceGraph.tsx +++ b/app/components/ForceGraph.tsx @@ -50,34 +50,26 @@ export default function ForceGraph({ const parentRef = useRef(null) const toast = useToast() - useEffect(() => { - if (!chartRef.current || data.nodes.length === 0 || data.links.length === 0) return - chartRef.current.d3Force('link').id((link: any) => link.id).distance(50) - chartRef.current.d3Force('charge').strength(-300) - chartRef.current.d3Force('center').strength(0.05) - }, [chartRef, data.links.length, data.nodes.length]) - - useEffect(() => { + useEffect(() => { const handleResize = () => { if (!parentRef.current) return setParentWidth(parentRef.current.clientWidth) setParentHeight(parentRef.current.clientHeight) } - handleResize() + window.addEventListener('resize', handleResize) + + const observer = new ResizeObserver(handleResize) - const resizeObserver = new ResizeObserver(handleResize) if (parentRef.current) { - resizeObserver.observe(parentRef.current) + observer.observe(parentRef.current) } - window.addEventListener('resize', handleResize) - return () => { - resizeObserver.disconnect() window.removeEventListener('resize', handleResize) + observer.disconnect() } - }, []) + }, [parentRef]) const onFetchNode = async (node: Node) => { const result = await securedFetch(`/api/graph/${graph.Id}/${node.id}`, { From c019e8256159a8d372445a73516981c1b8672149 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 21 Jan 2025 16:21:19 +0200 Subject: [PATCH 07/65] upgrade to node22 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index db11d29c..dfd37408 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-alpine AS base +FROM node:22-alpine AS base # Install dependencies only when needed FROM base AS deps @@ -68,4 +68,4 @@ ENV HOSTNAME "0.0.0.0" # server.js is created by next build from the standalone output # https://nextjs.org/docs/pages/api-reference/next-config-js/output -CMD ["node", "server.js"] \ No newline at end of file +CMD ["node", "server.js"] From 97206a44022a38b576f5a5edf3f45da0f0b70250 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 21 Jan 2025 16:23:28 +0200 Subject: [PATCH 08/65] upgrade to node22 --- .github/workflows/nextjs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nextjs.yml b/.github/workflows/nextjs.yml index e86401d4..0bffc7d2 100644 --- a/.github/workflows/nextjs.yml +++ b/.github/workflows/nextjs.yml @@ -36,7 +36,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v3 with: - node-version: "18" + node-version: "22" cache: ${{ steps.detect-package-manager.outputs.manager }} - name: Restore cache From ecf24d7c431e4d335a0efd1a7296b40ce2a41617 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 22 Jan 2025 10:17:31 +0200 Subject: [PATCH 09/65] commit --- app/components/ForceGraph.tsx | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/app/components/ForceGraph.tsx b/app/components/ForceGraph.tsx index 10144fee..2cd19bdb 100644 --- a/app/components/ForceGraph.tsx +++ b/app/components/ForceGraph.tsx @@ -48,6 +48,7 @@ export default function ForceGraph({ const [parentHeight, setParentHeight] = useState(0) const [hoverElement, setHoverElement] = useState() const parentRef = useRef(null) + const lastClick = useRef<{ date: Date, name: string }>({ date: new Date(), name: "" }) const toast = useToast() useEffect(() => { @@ -119,19 +120,29 @@ export default function ForceGraph({ graph.removeLinks() } - const handleNodeRightClick = async (node: Node) => { + const handleNodeClick = async (node: Node) => { + + const now = new Date() + const { date, name } = lastClick.current + + if (now.getTime() - date.getTime() < 1000 && name === (node.data.name || node.id.toString())) { + return + } + if (!node.expand) { await onFetchNode(node) } else { deleteNeighbors([node]) } + + lastClick.current = { date: new Date(), name: node.data.name || node.id.toString() } } const handleHover = (element: Node | Link | null) => { setHoverElement(element === null ? undefined : element) } - const handleClick = (element: Node | Link, evt: MouseEvent) => { + const handleRightClick = (element: Node | Link, evt: MouseEvent) => { if (!("source" in element) && isAddElement) { if (setSelectedNodes) { setSelectedNodes(prev => { @@ -263,11 +274,11 @@ export default function ForceGraph({ ctx.fillText(link.label, 0, 0); ctx.restore() }} - onNodeClick={handleClick} - onLinkClick={handleClick} + onNodeClick={handleNodeClick} onNodeHover={handleHover} onLinkHover={handleHover} - onNodeRightClick={handleNodeRightClick} + onNodeRightClick={handleRightClick} + onLinkRightClick={handleRightClick} onBackgroundClick={handleUnselected} onBackgroundRightClick={handleUnselected} onEngineStop={() => { From a63aef2988cb2ef24457eab75be559451e7a9117 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:25:19 +0200 Subject: [PATCH 10/65] Update auth.setup.ts --- e2e/tests/auth.setup.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/e2e/tests/auth.setup.ts b/e2e/tests/auth.setup.ts index 97c85499..f516bc55 100644 --- a/e2e/tests/auth.setup.ts +++ b/e2e/tests/auth.setup.ts @@ -13,6 +13,7 @@ setup("admin authentication", async () => { try { const browserWrapper = new BrowserWrapper(); const loginPage = await browserWrapper.createNewPage(LoginPage, urls.loginUrl); + await browserWrapper.setPageToFullScreen(); await loginPage.clickOnConnect(); await loginPage.dismissDialogAtStart(); const context = browserWrapper.getContext(); @@ -37,6 +38,7 @@ userRoles.forEach(({ name, file, userName }) => { try { const browserWrapper = new BrowserWrapper(); const loginPage = await browserWrapper.createNewPage(LoginPage, urls.loginUrl); + await browserWrapper.setPageToFullScreen(); await loginPage.connectWithCredentials(userName, user.password); await loginPage.dismissDialogAtStart(); const context = browserWrapper.getContext(); From ac6baf6918bf7307a4e846bc452c21dbae2ae71d Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Wed, 22 Jan 2025 19:51:13 +0200 Subject: [PATCH 11/65] update config settings tests --- e2e/config/settingsConfigData.json | 2 +- e2e/tests/settingsConfig.spec.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/e2e/config/settingsConfigData.json b/e2e/config/settingsConfigData.json index ae70e255..3616177e 100644 --- a/e2e/config/settingsConfigData.json +++ b/e2e/config/settingsConfigData.json @@ -35,7 +35,7 @@ {"role": "QUERY_MEM_CAPACITY", "description": "modify queryMemCapacity", "input": "20", "expected": true}, {"role": "VKEY_MAX_ENTITY_COUNT", "description": "modify vKeyMaxEntityCount", "input": "20", "expected": true}, {"role": "CMD_INFO", "description": "modify cmdInfo", "input": "yes", "expected": true}, - {"role": "MAX_INFO_QUERIES", "description": "modify maxInfoQueries", "input": "20", "expected": true} + {"role": "MAX_INFO_QUERIES", "description": "modify maxInfoQueries", "input": "999", "expected": true} ] } \ No newline at end of file diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index 9b36bb4a..30417fd5 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -25,6 +25,7 @@ test.describe('Settings Tests', () => { await apiCall.modifySettingsRole(roles.maxQueuedQueries, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.maxQueuedQueries) + await apiCall.modifySettingsRole(roles.maxTimeOut, "25") expect(value === input).toBe(expected) }); }) @@ -36,6 +37,7 @@ test.describe('Settings Tests', () => { await apiCall.modifySettingsRole(roles.maxTimeOut, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.maxTimeOut) + await apiCall.modifySettingsRole(roles.maxTimeOut, "0") expect(value === input).toBe(expected) }); }) @@ -47,6 +49,7 @@ test.describe('Settings Tests', () => { await apiCall.modifySettingsRole(roles.defaultTimeOut, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.defaultTimeOut) + await apiCall.modifySettingsRole(roles.maxTimeOut, "0") expect(value === input).toBe(expected) }); }) @@ -58,6 +61,7 @@ test.describe('Settings Tests', () => { await apiCall.modifySettingsRole(roles.resultSetSize, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.resultSetSize) + await apiCall.modifySettingsRole(roles.maxTimeOut, "1000") expect(value === input).toBe(expected) }); }) @@ -81,6 +85,7 @@ test.describe('Settings Tests', () => { await apiCall.modifySettingsRole(roles.vKeyMaxEntityCount, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.vKeyMaxEntityCount) + await apiCall.modifySettingsRole(roles.queryMemCapacity, "100000") expect(value === input).toBe(expected) }); }) @@ -92,6 +97,7 @@ test.describe('Settings Tests', () => { await apiCall.modifySettingsRole(roles.cmdInfo, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.cmdInfo) + await apiCall.modifySettingsRole(roles.queryMemCapacity, "yes") expect(value === input).toBe(expected) }); }) @@ -103,6 +109,7 @@ test.describe('Settings Tests', () => { await apiCall.modifySettingsRole(roles.maxInfoQueries, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.maxInfoQueries) + await apiCall.modifySettingsRole(roles.queryMemCapacity, "1000") expect(value === input).toBe(expected) }); }) From 9bd266a6e83746dc19e303109d207ea34fbc1f72 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jan 2025 14:36:01 +0200 Subject: [PATCH 12/65] change bg and display logo, version and copy rights --- app/components/Header.tsx | 2 +- app/globals.css | 6 +----- app/layout.tsx | 2 +- app/page.tsx | 14 +++++++++++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 66c05915..3dec72ab 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -97,7 +97,7 @@ export default function Header({ onSetGraphName }: Props) { -
+
) } diff --git a/app/globals.css b/app/globals.css index 830ec076..28ca906e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -60,11 +60,7 @@ @apply h-full w-full flex flex-col bg-background; } - .LandingPage { - background: linear-gradient(180deg, #EC806C 0%, #B66EBD 43.41%, #7568F2 100%); - } - - .Top { + .Gradient { background: linear-gradient(90deg, #EC806C 0%, #B66EBD 43.41%, #7568F2 100%); } diff --git a/app/layout.tsx b/app/layout.tsx index 141d0f53..185eaa86 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -24,7 +24,7 @@ export default function RootLayout({ // caused by mismatched client/server content caused by next-themes return ( - + {children} diff --git a/app/page.tsx b/app/page.tsx index 2fd5e7c6..4c6b09f9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,13 +1,21 @@ "use client"; import Spinning from "@/app/components/ui/spinning"; +import Image from "next/image"; +import pkg from '@/package.json'; export default function Home() { return ( -
-
- +
+
+ +
+
+

Version: {`{${pkg.version}}`}

+

All Rights Reserved © 2024 - {new Date().getFullYear()} falkordb.com

+
+
) } From e509ef446d5847ac280fa924fbe80e4dc851ff4f Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jan 2025 14:39:18 +0200 Subject: [PATCH 13/65] adjust the bg better and fix interaction with the dropzone --- app/components/ui/Dropzone.tsx | 2 +- app/login/LoginForm.tsx | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/components/ui/Dropzone.tsx b/app/components/ui/Dropzone.tsx index 93426311..1ab97cbc 100644 --- a/app/components/ui/Dropzone.tsx +++ b/app/components/ui/Dropzone.tsx @@ -56,7 +56,7 @@ function Dropzone({ filesCount = false, className = "", withTable = false, disab Or Browse
- :

Upload Certificate

+ :

Upload Certificate

}
{ diff --git a/app/login/LoginForm.tsx b/app/login/LoginForm.tsx index be315cb7..3c8458bc 100644 --- a/app/login/LoginForm.tsx +++ b/app/login/LoginForm.tsx @@ -118,12 +118,10 @@ export default function LoginForm() { } return ( -
+
-
- -
+
-
+
); } From 1e0c160eca9095d17d8f8447def8a280c2ee641f Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jan 2025 14:48:32 +0200 Subject: [PATCH 14/65] Refactor GraphView and Toolbar components to enhance UI and functionality - Updated dynamic import for EditorComponent in GraphView for better readability. - Modified Toolbar to conditionally display add buttons based on the new displayAdd prop. - Removed hardcoded creation date from Selector component. - Adjusted disabled state logic in SchemaView and Toolbar to account for user roles. - Improved animation control visibility in GraphView based on graph elements. --- app/graph/GraphView.tsx | 29 ++++++++++++--------- app/graph/Selector.tsx | 1 - app/graph/toolbar.tsx | 55 +++++++++++++++++++++------------------ app/schema/SchemaView.tsx | 4 +-- 4 files changed, 48 insertions(+), 41 deletions(-) diff --git a/app/graph/GraphView.tsx b/app/graph/GraphView.tsx index 3c0a779b..583294d5 100644 --- a/app/graph/GraphView.tsx +++ b/app/graph/GraphView.tsx @@ -21,7 +21,7 @@ import Button from "../components/ui/Button"; import TableView from "./TableView"; const ForceGraph = dynamic(() => import("../components/ForceGraph"), { ssr: false }); -const EditorComponent = dynamic(() => import("../components/EditorComponent"), {ssr: false}) +const EditorComponent = dynamic(() => import("../components/EditorComponent"), { ssr: false }) function GraphView({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, fetchCount, session }: { graph: Graph @@ -256,7 +256,7 @@ function GraphView({ graph, selectedElement, setSelectedElement, runQuery, histo deleteDisabled={(Object.values(selectedElements).length === 0 && !selectedElement) || session?.user.role === "Read-Only"} onDeleteElement={handleDeleteElement} chartRef={chartRef} - addDisabled + displayAdd={false} /> { isCollapsed && graph.Id && @@ -277,17 +277,20 @@ function GraphView({ graph, selectedElement, setSelectedElement, runQuery, histo > {!maximize ? : } -
- {cooldownTicks === undefined ? : } - { - handleCooldown(cooldownTicks === undefined ? 0 : undefined) - }} - /> -
+ { + graph.getElements().length > 0 && +
+ {cooldownTicks === undefined ? : } + { + handleCooldown(cooldownTicks === undefined ? 0 : undefined) + }} + /> +
+ } -

Created on 2/2 24

{nodesCount} Nodes

|

{edgesCount} Edges diff --git a/app/graph/toolbar.tsx b/app/graph/toolbar.tsx index ba2762ec..fc3d8ffb 100644 --- a/app/graph/toolbar.tsx +++ b/app/graph/toolbar.tsx @@ -8,7 +8,6 @@ import Button from "../components/ui/Button"; import DeleteElement from "./DeleteElement"; interface Props { - addDisabled?: boolean, disabled?: boolean, // eslint-disable-next-line @typescript-eslint/no-explicit-any chartRef: React.RefObject, @@ -16,18 +15,19 @@ interface Props { onAddEntity?: () => void, onAddRelation?: () => void, deleteDisabled?: boolean, - selectedElementsLength: number + selectedElementsLength: number, + displayAdd: boolean } export default function Toolbar({ disabled, - addDisabled, chartRef, onDeleteElement, onAddEntity, onAddRelation, deleteDisabled, - selectedElementsLength + selectedElementsLength, + displayAdd }: Props) { const [deleteOpen, setDeleteOpen] = useState(false) @@ -55,26 +55,31 @@ export default function Toolbar({ return (
- - + { + displayAdd && + <> + + + + } 1 ? "elements" : "element"}?`} open={deleteOpen} @@ -85,7 +90,7 @@ export default function Toolbar({ className="text-nowrap" variant="Primary" label="Delete" - disabled={deleteDisabled} + disabled={deleteDisabled || disabled} > diff --git a/app/schema/SchemaView.tsx b/app/schema/SchemaView.tsx index 0bdb6aa7..56b497df 100644 --- a/app/schema/SchemaView.tsx +++ b/app/schema/SchemaView.tsx @@ -328,7 +328,7 @@ export default function SchemaView({ schema, fetchCount, session }: Props) {
{ setIsAddEntity(true) @@ -346,7 +346,7 @@ export default function SchemaView({ schema, fetchCount, session }: Props) { }} onDeleteElement={handleDeleteElement} chartRef={chartRef} - addDisabled={session?.user.role === "Read-Only" || !schema.Id} + displayAdd /> { isCollapsed && From 7d70e910f1f669f523f006c4dec094d631f97e30 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jan 2025 15:11:24 +0200 Subject: [PATCH 15/65] Enhance Header component with new help features - Added a Sheet component for displaying additional information about the application. --- app/components/Header.tsx | 118 ++++++++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 44 deletions(-) diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 66c05915..ed08c081 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -9,6 +9,9 @@ import { cn } from "@/lib/utils"; import { useRouter, usePathname } from "next/navigation"; import { signOut, useSession } from "next-auth/react"; import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger } from "@/components/ui/navigation-menu"; +import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; +import pkg from '@/package.json'; +import { VisuallyHidden } from "@radix-ui/react-visually-hidden"; import Button from "./ui/Button"; import CreateGraph from "./CreateGraph"; @@ -22,7 +25,7 @@ export default function Header({ onSetGraphName }: Props) { const type = pathname.includes("/schema") ? "Schema" : "Graph" const inCreate = pathname.includes("/create") const { data: session } = useSession() - + return (
@@ -49,52 +52,79 @@ export default function Header({ onSetGraphName }: Props) {
- - - + + + + + + + + + +

Help

+
+ + + +
+ { + !inCreate && + + } -
- - - -

Help

-
- - - -
- { - !inCreate && - - } - -
-
+ + + + + +
+ +

We Make AI Reliable

+

+ Delivering a scalable, + low-latency graph database designed for development teams managing + structured and unstructured interconnected data in real-time or interactive environments. +

+
+
+

Version: {`{${pkg.version}}`}

+

All Rights Reserved © 2024 - {new Date().getFullYear()} falkordb.com

+
+
+
From 72b96e9dac11f3aa2011296743d40e947209a3e1 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 23 Jan 2025 16:11:43 +0200 Subject: [PATCH 16/65] remove blur on input to be able to click on submit --- app/graph/GraphDataPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/graph/GraphDataPanel.tsx b/app/graph/GraphDataPanel.tsx index abe0c406..30f106fd 100644 --- a/app/graph/GraphDataPanel.tsx +++ b/app/graph/GraphDataPanel.tsx @@ -294,7 +294,6 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, value={newVal} onChange={(e) => setNewVal(e.target.value)} onKeyDown={handleSetKeyDown} - onBlur={() => handleSetEditable("", "")} /> :
) diff --git a/lib/utils.ts b/lib/utils.ts index 059aa9d6..a5256ea8 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -41,4 +41,19 @@ export function prepareArg(arg: string) { return encodeURIComponent(arg.trim()) } -export const defaultQuery = (q?: string) => q || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) return n,e,m LIMIT 100" \ No newline at end of file +export const defaultQuery = (q?: string) => q || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) return n,e,m LIMIT 100" + +export const lightenColor = (hex: string): string => { + // Remove the # if present + const color = hex.replace('#', ''); + // Convert to RGB + const r = parseInt(color.slice(0, 2), 16); + const g = parseInt(color.slice(2, 4), 16); + const b = parseInt(color.slice(4, 6), 16); + // Mix with white (add 20% of the remaining distance to white) + const lightR = Math.min(255, r + Math.floor((255 - r) * 0.2)); + const lightG = Math.min(255, g + Math.floor((255 - g) * 0.2)); + const lightB = Math.min(255, b + Math.floor((255 - b) * 0.2)); + // Convert back to hex + return `#${lightR.toString(16).padStart(2, '0')}${lightG.toString(16).padStart(2, '0')}${lightB.toString(16).padStart(2, '0')}`; +} \ No newline at end of file From a70a6cd2d29978a92875b4945bf518852317bf50 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Sun, 26 Jan 2025 12:02:40 +0200 Subject: [PATCH 19/65] commit --- app/graph/labels.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/graph/labels.tsx b/app/graph/labels.tsx index 0a116454..e2d245b6 100644 --- a/app/graph/labels.tsx +++ b/app/graph/labels.tsx @@ -1,5 +1,5 @@ import { useRef, useState } from "react"; -import { cn } from "@/lib/utils"; +import { cn, lightenColor } from "@/lib/utils"; import { ChevronDown, ChevronUp } from "lucide-react"; import { Category, Graph } from "../api/graph/model"; import Button from "../components/ui/Button"; @@ -57,7 +57,7 @@ export default function Labels({ graph, categories, onClick, label, className = setReload(prev => !prev) }} > -
+
)) From 5a75ad85b39e0b3b1be5f11c1efad9ad3f5d05c8 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 27 Jan 2025 12:27:27 +0200 Subject: [PATCH 20/65] update config tests --- e2e/config/settingsConfigData.json | 14 ++--- e2e/tests/settingsConfig.spec.ts | 87 +++++++++++++++++++----------- 2 files changed, 63 insertions(+), 38 deletions(-) diff --git a/e2e/config/settingsConfigData.json b/e2e/config/settingsConfigData.json index 3616177e..69d7c96c 100644 --- a/e2e/config/settingsConfigData.json +++ b/e2e/config/settingsConfigData.json @@ -28,13 +28,13 @@ { "input": "yes", "description": "valid input - yes value", "expected": true } ], "roleModificationData": [ - {"role": "MAX_QUEUED_QUERIES", "description": "modify maxQueuedQueries", "input": "20", "expected": true}, - {"role": "TIMEOUT_MAX", "description": "modify maxTimeOut", "input": "25", "expected": true}, - {"role": "TIMEOUT_DEFAULT", "description": "modify defaultTimeOut", "input": "10", "expected": true}, - {"role": "RESULTSET_SIZE", "description": "modify resultSetSize", "input": "20", "expected": true}, - {"role": "QUERY_MEM_CAPACITY", "description": "modify queryMemCapacity", "input": "20", "expected": true}, - {"role": "VKEY_MAX_ENTITY_COUNT", "description": "modify vKeyMaxEntityCount", "input": "20", "expected": true}, - {"role": "CMD_INFO", "description": "modify cmdInfo", "input": "yes", "expected": true}, + {"role": "MAX_QUEUED_QUERIES", "description": "modify maxQueuedQueries", "input": "24", "expected": true}, + {"role": "TIMEOUT_MAX", "description": "modify maxTimeOut", "input": "1", "expected": true}, + {"role": "TIMEOUT_DEFAULT", "description": "modify defaultTimeOut", "input": "1", "expected": true}, + {"role": "RESULTSET_SIZE", "description": "modify resultSetSize", "input": "10001", "expected": true}, + {"role": "QUERY_MEM_CAPACITY", "description": "modify queryMemCapacity", "input": "1", "expected": true}, + {"role": "VKEY_MAX_ENTITY_COUNT", "description": "modify vKeyMaxEntityCount", "input": "100001", "expected": true}, + {"role": "CMD_INFO", "description": "modify cmdInfo", "input": "no", "expected": true}, {"role": "MAX_INFO_QUERIES", "description": "modify maxInfoQueries", "input": "999", "expected": true} ] } diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index 30417fd5..a8d15510 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -17,7 +17,7 @@ test.describe('Settings Tests', () => { await browser.closeBrowser(); }) - Data.inputDataRejectsZero.forEach(({ input, description, expected }) => { + Data.inputDataRejectsZero.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.maxQueuedQueries} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() @@ -25,109 +25,134 @@ test.describe('Settings Tests', () => { await apiCall.modifySettingsRole(roles.maxQueuedQueries, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.maxQueuedQueries) - await apiCall.modifySettingsRole(roles.maxTimeOut, "25") - expect(value === input).toBe(expected) + expect(value === input).toBe(expected); + if (index === Data.inputDataRejectsZero.length - 1) { + await apiCall.modifySettingsRole(roles.maxQueuedQueries, "25") + } }); }) - Data.maxTimeOut.forEach(({ input, description, expected }) => { + Data.maxTimeOut.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.maxTimeOut} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await apiCall.modifySettingsRole(roles.maxTimeOut, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.maxTimeOut) - await apiCall.modifySettingsRole(roles.maxTimeOut, "0") - expect(value === input).toBe(expected) + expect(value === input).toBe(expected); + if (index === Data.maxTimeOut.length - 1) { + await apiCall.modifySettingsRole(roles.maxTimeOut, "0") + } }); }) - Data.inputDataAcceptsZero.forEach(({ input, description, expected }) => { + Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.defaultTimeOut} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await apiCall.modifySettingsRole(roles.defaultTimeOut, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.defaultTimeOut) - await apiCall.modifySettingsRole(roles.maxTimeOut, "0") - expect(value === input).toBe(expected) + expect(value === input).toBe(expected); + if (index === Data.inputDataAcceptsZero.length - 1) { + await apiCall.modifySettingsRole(roles.defaultTimeOut, "0") + } }); }) - Data.inputDataAcceptsZero.forEach(({ input, description, expected }) => { + Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.resultSetSize} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await apiCall.modifySettingsRole(roles.resultSetSize, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.resultSetSize) - await apiCall.modifySettingsRole(roles.maxTimeOut, "1000") - expect(value === input).toBe(expected) + expect(value === input).toBe(expected); + if (index === Data.inputDataAcceptsZero.length - 1) { + await apiCall.modifySettingsRole(roles.resultSetSize, "10000") + } }); }) - Data.inputDataAcceptsZero.forEach(({ input, description, expected }) => { + Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.queryMemCapacity} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await apiCall.modifySettingsRole(roles.queryMemCapacity, input) await settingsConfigPage.refreshPage() - const value = await settingsConfigPage.getRoleContentValue(roles.queryMemCapacity) - await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") // update to default values - expect(value === input).toBe(expected) + const value = await settingsConfigPage.getRoleContentValue(roles.queryMemCapacity) + expect(value === input).toBe(expected); + if (index === Data.inputDataAcceptsZero.length - 1) { + await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") + } }); }) - Data.inputDataAcceptsZero.forEach(({ input, description, expected }) => { + Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.vKeyMaxEntityCount} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await apiCall.modifySettingsRole(roles.vKeyMaxEntityCount, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.vKeyMaxEntityCount) - await apiCall.modifySettingsRole(roles.queryMemCapacity, "100000") expect(value === input).toBe(expected) + if (index === Data.inputDataAcceptsZero.length - 1) { + await apiCall.modifySettingsRole(roles.vKeyMaxEntityCount, "100000") + } }); }) - Data.CMDData.forEach(({ input, description, expected }) => { + Data.CMDData.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.cmdInfo} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await apiCall.modifySettingsRole(roles.cmdInfo, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.cmdInfo) - await apiCall.modifySettingsRole(roles.queryMemCapacity, "yes") expect(value === input).toBe(expected) + if (index === Data.CMDData.length - 1) { + await apiCall.modifySettingsRole(roles.cmdInfo, "yes") + } }); }) - Data.inputDataAcceptsZero.forEach(({ input, description, expected }) => { + Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.maxInfoQueries} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await apiCall.modifySettingsRole(roles.maxInfoQueries, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.maxInfoQueries) - await apiCall.modifySettingsRole(roles.queryMemCapacity, "1000") - expect(value === input).toBe(expected) + expect(value === input).toBe(expected); + if (index === Data.inputDataAcceptsZero.length - 1) { + await apiCall.modifySettingsRole(roles.queryMemCapacity, "1000"); + } }); }) - Data.roleModificationData.forEach(({ role, input, description, expected }) => { + Data.roleModificationData.forEach(({ role, input, description, expected }, index) => { test(`@admin Modify ${role} via UI validation via API: Input value: ${input} description: ${description}`, async () => { + const apiCall = new ApiCalls() + console.log("before: ",String((await apiCall.getSettingsRoleValue(role)).config[1])); const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) await settingsConfigPage.modifyRoleValue(role, input) - const apiCall = new ApiCalls() let value = String((await apiCall.getSettingsRoleValue(role)).config[1]); + console.log("after: ",value); // Convert numeric values to yes/no for boolean settings - if (value === '1') { - value = 'yes'; - } else if (value === '0') { - value = 'no'; + value = value === '1' ? 'yes' : value === '0' ? 'no' : value; + expect(value === input).toBe(expected); + if (index === Data.roleModificationData.length - 1) { + await apiCall.modifySettingsRole(roles.maxQueuedQueries, "25") + await apiCall.modifySettingsRole(roles.maxTimeOut, "0") + await apiCall.modifySettingsRole(roles.defaultTimeOut, "0") + await apiCall.modifySettingsRole(roles.resultSetSize, "10000") + await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") + await apiCall.modifySettingsRole(roles.vKeyMaxEntityCount, "100000") + await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") + await apiCall.modifySettingsRole(roles.vKeyMaxEntityCount, "100000") + await apiCall.modifySettingsRole(roles.cmdInfo, "yes") + await apiCall.modifySettingsRole(roles.queryMemCapacity, "1000") } - await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") // update to default values - expect(value === input).toBe(expected) }); }) From e734415fec018d94683b4cd6735dfec13b6b6f84 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 27 Jan 2025 12:50:46 +0200 Subject: [PATCH 21/65] update tests --- e2e/config/settingsConfigData.json | 1 + e2e/tests/settingsConfig.spec.ts | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/e2e/config/settingsConfigData.json b/e2e/config/settingsConfigData.json index 69d7c96c..069a86a2 100644 --- a/e2e/config/settingsConfigData.json +++ b/e2e/config/settingsConfigData.json @@ -29,6 +29,7 @@ ], "roleModificationData": [ {"role": "MAX_QUEUED_QUERIES", "description": "modify maxQueuedQueries", "input": "24", "expected": true}, + {"role": "TIMEOUT", "description": "modify timeOut", "input": "1001", "expected": true}, {"role": "TIMEOUT_MAX", "description": "modify maxTimeOut", "input": "1", "expected": true}, {"role": "TIMEOUT_DEFAULT", "description": "modify defaultTimeOut", "input": "1", "expected": true}, {"role": "RESULTSET_SIZE", "description": "modify resultSetSize", "input": "10001", "expected": true}, diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index a8d15510..c8bf952c 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -32,6 +32,21 @@ test.describe('Settings Tests', () => { }); }) + Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { + test(`@admin Modify ${roles.maxQueuedQueries} via API validation via UI: Input value: ${input} description: ${description}`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + const apiCall = new ApiCalls() + await new Promise(resolve => { setTimeout(resolve, 1000) }); + await apiCall.modifySettingsRole(roles.TimeOut, input) + await settingsConfigPage.refreshPage() + const value = await settingsConfigPage.getRoleContentValue(roles.TimeOut) + expect(value === input).toBe(expected); + if (index === Data.inputDataAcceptsZero.length - 1) { + await apiCall.modifySettingsRole(roles.TimeOut, "1000") + } + }); + }) + Data.maxTimeOut.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.maxTimeOut} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) @@ -143,13 +158,12 @@ test.describe('Settings Tests', () => { expect(value === input).toBe(expected); if (index === Data.roleModificationData.length - 1) { await apiCall.modifySettingsRole(roles.maxQueuedQueries, "25") + await apiCall.modifySettingsRole(roles.TimeOut, "1000") await apiCall.modifySettingsRole(roles.maxTimeOut, "0") await apiCall.modifySettingsRole(roles.defaultTimeOut, "0") await apiCall.modifySettingsRole(roles.resultSetSize, "10000") await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") await apiCall.modifySettingsRole(roles.vKeyMaxEntityCount, "100000") - await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") - await apiCall.modifySettingsRole(roles.vKeyMaxEntityCount, "100000") await apiCall.modifySettingsRole(roles.cmdInfo, "yes") await apiCall.modifySettingsRole(roles.queryMemCapacity, "1000") } From 1e7b07501ef35258caa3c9d1a98759f19dc2afc4 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 27 Jan 2025 13:51:50 +0200 Subject: [PATCH 22/65] Update settingsConfig.spec.ts --- e2e/tests/settingsConfig.spec.ts | 107 +++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 25 deletions(-) diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index c8bf952c..4fdc959f 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -33,7 +33,7 @@ test.describe('Settings Tests', () => { }) Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { - test(`@admin Modify ${roles.maxQueuedQueries} via API validation via UI: Input value: ${input} description: ${description}`, async () => { + test(`@admin Modify ${roles.TimeOut} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await new Promise(resolve => { setTimeout(resolve, 1000) }); @@ -145,30 +145,87 @@ test.describe('Settings Tests', () => { }); }) - Data.roleModificationData.forEach(({ role, input, description, expected }, index) => { - test(`@admin Modify ${role} via UI validation via API: Input value: ${input} description: ${description}`, async () => { - const apiCall = new ApiCalls() - console.log("before: ",String((await apiCall.getSettingsRoleValue(role)).config[1])); - const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) - await settingsConfigPage.modifyRoleValue(role, input) - let value = String((await apiCall.getSettingsRoleValue(role)).config[1]); - console.log("after: ",value); - // Convert numeric values to yes/no for boolean settings - value = value === '1' ? 'yes' : value === '0' ? 'no' : value; - expect(value === input).toBe(expected); - if (index === Data.roleModificationData.length - 1) { - await apiCall.modifySettingsRole(roles.maxQueuedQueries, "25") - await apiCall.modifySettingsRole(roles.TimeOut, "1000") - await apiCall.modifySettingsRole(roles.maxTimeOut, "0") - await apiCall.modifySettingsRole(roles.defaultTimeOut, "0") - await apiCall.modifySettingsRole(roles.resultSetSize, "10000") - await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") - await apiCall.modifySettingsRole(roles.vKeyMaxEntityCount, "100000") - await apiCall.modifySettingsRole(roles.cmdInfo, "yes") - await apiCall.modifySettingsRole(roles.queryMemCapacity, "1000") - } - }); - }) + test(`@admin Modify maxQueuedQueries via UI validation via API: Input value: 24`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.maxQueuedQueries, "24") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.maxQueuedQueries)).config[1]); + expect(value === "24").toBe(true); + await apiCall.modifySettingsRole(roles.maxQueuedQueries, "25") + }); + + test(`@admin Modify TimeOut via UI validation via API: Input value: 1001`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.TimeOut, "1001") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.TimeOut)).config[1]); + expect(value === "1001").toBe(true); + await apiCall.modifySettingsRole(roles.TimeOut, "1000") + }); + + test(`@admin Modify maxTimeOut via UI validation via API: Input value: 1`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.maxTimeOut, "1") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.maxTimeOut)).config[1]); + expect(value === "1").toBe(true); + await apiCall.modifySettingsRole(roles.maxTimeOut, "0") + }); + + test(`@admin Modify defaultTimeOut via UI validation via API: Input value: 1`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.defaultTimeOut, "1") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.defaultTimeOut)).config[1]); + expect(value === "1").toBe(true); + await apiCall.modifySettingsRole(roles.defaultTimeOut, "0") + }); + + test(`@admin Modify resultSetSize via UI validation via API: Input value: 10001`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.resultSetSize, "10001") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.resultSetSize)).config[1]); + expect(value === "10001").toBe(true); + await apiCall.modifySettingsRole(roles.resultSetSize, "10000") + }); + + test(`@admin Modify queryMemCapacity via UI validation via API: Input value: 1`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.queryMemCapacity, "1") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.queryMemCapacity)).config[1]); + expect(value === "1").toBe(true); + await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") + }); + + test(`@admin Modify vKeyMaxEntityCount via UI validation via API: Input value: 100001`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.vKeyMaxEntityCount, "100001") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.vKeyMaxEntityCount)).config[1]); + expect(value === "100001").toBe(true); + await apiCall.modifySettingsRole(roles.vKeyMaxEntityCount, "100000") + }); + + test(`@admin Modify cmdInfo via UI validation via API: Input value: no`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.cmdInfo, "no") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.cmdInfo)).config[1]); + value = value === '1' ? 'yes' : value === '0' ? 'no' : value; + expect(value === "no").toBe(true); + await apiCall.modifySettingsRole(roles.cmdInfo, "yes") + }); + test(`@admin Modify maxInfoQueries via UI validation via API: Input value: 999`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.maxInfoQueries, "999") + await new Promise(resolve => { setTimeout(resolve, 1000) }); + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.maxInfoQueries)).config[1]); + expect(value === "999").toBe(true); + await apiCall.modifySettingsRole(roles.maxInfoQueries, "1000") + }); }) \ No newline at end of file From 47d76a58475711fc48e0477437d07a87610c254f Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 27 Jan 2025 14:18:08 +0200 Subject: [PATCH 23/65] Update settingsConfig.spec.ts --- e2e/tests/settingsConfig.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index 4fdc959f..54226f85 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -138,9 +138,10 @@ test.describe('Settings Tests', () => { await apiCall.modifySettingsRole(roles.maxInfoQueries, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.maxInfoQueries) + console.log(value); expect(value === input).toBe(expected); if (index === Data.inputDataAcceptsZero.length - 1) { - await apiCall.modifySettingsRole(roles.queryMemCapacity, "1000"); + await apiCall.modifySettingsRole(roles.maxInfoQueries, "1000"); } }); }) @@ -224,6 +225,8 @@ test.describe('Settings Tests', () => { await new Promise(resolve => { setTimeout(resolve, 1000) }); const apiCall = new ApiCalls() let value = String((await apiCall.getSettingsRoleValue(roles.maxInfoQueries)).config[1]); + console.log(value); + expect(value === "999").toBe(true); await apiCall.modifySettingsRole(roles.maxInfoQueries, "1000") }); From 2875315b6ef52c1a0c7cb4d02e83cd72daf2fcd9 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 27 Jan 2025 16:27:06 +0200 Subject: [PATCH 24/65] commit --- app/components/EditorComponent.tsx | 459 ++++++++++++----------------- 1 file changed, 188 insertions(+), 271 deletions(-) diff --git a/app/components/EditorComponent.tsx b/app/components/EditorComponent.tsx index da19d34e..59b483ec 100644 --- a/app/components/EditorComponent.tsx +++ b/app/components/EditorComponent.tsx @@ -7,21 +7,13 @@ import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; import { Editor, Monaco } from "@monaco-editor/react" import { useEffect, useRef, useState } from "react" import * as monaco from "monaco-editor"; -import { prepareArg, securedFetch } from "@/lib/utils"; import { Maximize2 } from "lucide-react"; -import { Session } from "next-auth"; +import { prepareArg, securedFetch } from "@/lib/utils"; import { useToast } from "@/components/ui/use-toast"; +import { Session } from "next-auth"; import { Graph } from "../api/graph/model"; import Button from "./ui/Button"; -type Suggestions = { - keywords: monaco.languages.CompletionItem[], - labels: monaco.languages.CompletionItem[], - relationshipTypes: monaco.languages.CompletionItem[], - propertyKeys: monaco.languages.CompletionItem[], - functions: monaco.languages.CompletionItem[] -} - interface Props { currentQuery: string historyQueries: string[] @@ -29,7 +21,6 @@ interface Props { maximize: boolean runQuery: (query: string) => void graph: Graph - isCollapsed: boolean data: Session | null } @@ -184,52 +175,59 @@ const FUNCTIONS = [ "vec.cosineDistance", ] +const SUGGESTIONS: monaco.languages.CompletionItem[] = KEYWORDS.map(key => ({ + insertText: key, + label: key, + kind: monaco.languages.CompletionItemKind.Keyword, + range: new monaco.Range(1, 1, 1, 1), + detail: "(keyword)" +})) + const MAX_HEIGHT = 20 const LINE_HEIGHT = 38 -const getEmptySuggestions = (): Suggestions => ({ - keywords: KEYWORDS.map(key => ({ - insertText: key, - label: key, - kind: monaco.languages.CompletionItemKind.Keyword, - range: new monaco.Range(1, 1, 1, 1), - detail: "(keyword)" - })), - labels: [], - relationshipTypes: [], - propertyKeys: [], - functions: [] -}) - const PLACEHOLDER = "Type your query here to start" -export default function EditorComponent({ currentQuery, historyQueries, setCurrentQuery, maximize, runQuery, graph, isCollapsed, data }: Props) { +export default function EditorComponent({ currentQuery, historyQueries, setCurrentQuery, maximize, runQuery, graph, data }: Props) { const [query, setQuery] = useState(currentQuery) const placeholderRef = useRef(null) const [monacoInstance, setMonacoInstance] = useState() - const [prevGraphName, setPrevGraphName] = useState("") const [sugProvider, setSugProvider] = useState() - const [suggestions, setSuggestions] = useState(getEmptySuggestions()) - const [lineNumber, setLineNumber] = useState(0) + const [lineNumber, setLineNumber] = useState(1) + const [blur, setBlur] = useState(false) + const { toast } = useToast() const submitQuery = useRef(null) const editorRef = useRef(null) - const [blur, setBlur] = useState(false) + const containerRef = useRef(null) const historyRef = useRef({ historyQueries, currentQuery, historyCounter: historyQueries.length }) - const { toast } = useToast() useEffect(() => { historyRef.current.historyQueries = historyQueries }, [historyQueries, currentQuery]) useEffect(() => { - if (!editorRef.current) return - editorRef.current.layout(); - }, [isCollapsed]) + if (!containerRef.current) return + + const handleResize = () => { + editorRef.current?.layout() + } + + window.addEventListener("resize", handleResize) + + const observer = new ResizeObserver(handleResize) + + observer.observe(containerRef.current) + + return () => { + window.removeEventListener("resize", handleResize) + observer.disconnect() + } + }, [containerRef.current]) useEffect(() => { setQuery(currentQuery) @@ -258,159 +256,76 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre }); } - const addSuggestions = (MonacoI: Monaco, sug?: monaco.languages.CompletionItem[], procedures?: monaco.languages.CompletionItem[]) => { - - sugProvider?.dispose() - const provider = MonacoI.languages.registerCompletionItemProvider('custom-language', { - triggerCharacters: ["."], - provideCompletionItems: (model, position) => { - const word = model.getWordUntilPosition(position) - const range = new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn) - - return { - suggestions: [ - ...(sug || []).map(s => ({ ...s, range })), - ...suggestions.keywords.map(s => ({ ...s, range })), - ...(procedures || []).map(s => ({ ...s, range, })) - ] + const fetchSuggestions = async (q: string, detail: string): Promise => { + const result = await securedFetch(`api/graph/${graph.Id}/?query=${prepareArg(q)}&role=${data?.user.role}`, { + method: 'GET', + }, toast) + + if (!result) return [] + + const json = await result.json() + + if (json.result.data.length === 0) return [] + + return json.result.data.map(({ sug }: { sug: string }) => ({ + insertTextRules: detail === '(function)' ? monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet : undefined, + insertText: detail === '(function)' ? `${sug}(\${0})` : sug, + label: detail === '(function)' ? `${sug}()` : sug, + kind: (() => { + switch (detail) { + case '(function)': + return monaco.languages.CompletionItemKind.Function; + case '(property key)': + return monaco.languages.CompletionItemKind.Property; + default: + return monaco.languages.CompletionItemKind.Variable; } - }, - }) - - setSugProvider(provider) - } - - const addLabelsSuggestions = async (sug: monaco.languages.CompletionItem[]) => { - const labelsQuery = `CALL db.labels()` - - await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(labelsQuery)}&role=${data?.user.role}`, { - method: "GET" - }, toast).then((res) => res.json()).then((json) => { - json.result.data.forEach(({ label }: { label: string }) => { - sug.push({ - label, - kind: monaco.languages.CompletionItemKind.TypeParameter, - insertText: label, - range: new monaco.Range(1, 1, 1, 1), - detail: "(label)" - }) - }) - }) - } - - const addRelationshipTypesSuggestions = async (sug: monaco.languages.CompletionItem[]) => { - const relationshipTypeQuery = `CALL db.relationshipTypes()` - - await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(relationshipTypeQuery)}&role=${data?.user.role}`, { - method: "GET" - }, toast).then((res) => res.json()).then((json) => { - json.result.data.forEach(({ relationshipType }: { relationshipType: string }) => { - sug.push({ - label: relationshipType, - kind: monaco.languages.CompletionItemKind.TypeParameter, - insertText: relationshipType, - range: new monaco.Range(1, 1, 1, 1), - detail: "(relationship type)" - }) - }) - }) - } - - const addPropertyKeysSuggestions = async (sug: monaco.languages.CompletionItem[]) => { - const propertyKeysQuery = `CALL db.propertyKeys()` - - await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(propertyKeysQuery)}&role=${data?.user.role}`, { - method: "GET" - }, toast).then((res) => res.json()).then((json) => { - json.result.data.forEach(({ propertyKey }: { propertyKey: string }) => { - sug.push({ - label: propertyKey, - kind: monaco.languages.CompletionItemKind.Property, - insertText: propertyKey, - range: new monaco.Range(1, 1, 1, 1), - detail: "(property)" - }) - }) - }) + })(), + range: new monaco.Range(1, 1, 1, 1), + detail + })) } - const addFunctionsSuggestions = async (functions: monaco.languages.CompletionItem[]) => { - const proceduresQuery = `CALL dbms.procedures() YIELD name` - await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(proceduresQuery)}&role=${data?.user.role}`, { - method: "GET" - }, toast).then((res) => res.json()).then((json) => { - [...json.result.data.map(({ name }: { name: string }) => name), ...FUNCTIONS].forEach((name: string) => { - functions.push({ - label: name, - kind: monaco.languages.CompletionItemKind.Function, - insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, - insertText: `${name}($0)`, - range: new monaco.Range(1, 1, 1, 1), - detail: "(function)" - }) - }) - }) + const getSuggestions = async (): Promise => { + const suggestions = await Promise.all([ + ['CALL dbms.procedures() YIELD name as sug', '(function)'], + ['CALL db.propertyKeys() YIELD propertyKey as sug', '(property key)'], + ['CALL db.labels() YIELD label as sug', '(label)'], + ['CALL db.relationshipTypes() YIELD relationshipType as sug', '(relationship type)'] + ].map(([q, detail]) => fetchSuggestions(q, detail))) + + return [...suggestions.flatMap(arr => arr), ...FUNCTIONS.map(f => ({ + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + insertText: `${f}(\${0})`, + label: `${f}()`, + kind: monaco.languages.CompletionItemKind.Function, + range: new monaco.Range(1, 1, 1, 1), + detail: "(function)" + }))] } - const getSuggestions = async (monacoI?: Monaco) => { + const addSuggestions = async (monacoI: Monaco) => { + console.log("addSuggestions"); + const suggestions = SUGGESTIONS - if (!graph.Id || (!monacoInstance && !monacoI)) return - const m = monacoI || monacoInstance - const sug: Suggestions = getEmptySuggestions() - - sugProvider?.dispose() - - await Promise.all([ - addLabelsSuggestions(sug.labels), - addRelationshipTypesSuggestions(sug.relationshipTypes), - addPropertyKeysSuggestions(sug.propertyKeys), - addFunctionsSuggestions(sug.functions) - ]) + if (graph.Id) { + console.log("getSuggestions"); + suggestions.push(...(await getSuggestions())) + } - const namespaces = new Map() + const functions = suggestions.filter(({ detail }) => detail === "(function)") - sug.functions.forEach(({ label }) => { - const names = (label as string).split(".") - names.forEach((name, i) => { - if (i === names.length - 1) return - namespaces.set(name, name) - }) - }) + const namespaces = new Set(functions.filter(({ label }) => (label as string).includes(".")).map(({ label }) => { + const [namespace] = (label as string).split(".") + return namespace + })) - m!.languages.setMonarchTokensProvider('custom-language', { - tokenizer: { - root: [ - [new RegExp(`\\b(${Array.from(namespaces.keys()).join('|')})\\b`), "keyword"], - [new RegExp(`\\b(${KEYWORDS.join('|')})\\b`), "keyword"], - [ - new RegExp(`\\b(${sug.functions.map(({ label }) => { - const labels = (label as string).split(".") - return labels[labels.length - 1] - }).join('|')})\\b`), - "function" - ], - [/"([^"\\]|\\.)*"/, 'string'], - [/'([^'\\]|\\.)*'/, 'string'], - [/\d+/, 'number'], - [/:(\w+)/, 'type'], - [/\{/, { token: 'delimiter.curly', next: '@bracketCounting' }], - [/\[/, { token: 'delimiter.square', next: '@bracketCounting' }], - [/\(/, { token: 'delimiter.parenthesis', next: '@bracketCounting' }], - ], - bracketCounting: [ - [/\{/, 'delimiter.curly', '@bracketCounting'], - [/\}/, 'delimiter.curly', '@pop'], - [/\[/, 'delimiter.square', '@bracketCounting'], - [/\]/, 'delimiter.square', '@pop'], - [/\(/, 'delimiter.parenthesis', '@bracketCounting'], - [/\)/, 'delimiter.parenthesis', '@pop'], - { include: 'root' } - ], - }, - ignoreCase: true, - }) + if (sugProvider) { + sugProvider.dispose() + console.log("dispose"); + } - m!.languages.setLanguageConfiguration('custom-language', { + monacoI.languages.setLanguageConfiguration('custom-language', { brackets: [ ['{', '}'], ['[', ']'], @@ -432,110 +347,109 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre ] }); - m!.editor.setTheme('custom-theme'); - - addSuggestions(m!, [...sug.labels, ...sug.propertyKeys, ...sug.relationshipTypes], sug.functions) - - setSuggestions(sug) - } - - useEffect(() => { - if (!graph || !monacoInstance || graph.Id !== prevGraphName) return setPrevGraphName(graph.Id) - - const run = async () => { - const sug: Suggestions = getEmptySuggestions() - if (graph.Metadata.length > 0) { - await Promise.all(graph.Metadata.map(async (meta: string) => { - if (meta.includes("Labels")) await addLabelsSuggestions(sug.labels) - if (meta.includes("RelationshipTypes")) await addRelationshipTypesSuggestions(sug.relationshipTypes) - if (meta.includes("PropertyKeys")) await addPropertyKeysSuggestions(sug.propertyKeys) - })) - } - Object.entries(sug).forEach(([key, value]) => { - if (value.length === 0) { - sug[key as keyof Suggestions] = suggestions[key as keyof Suggestions] - } + if (graph.Id) { + monacoI.languages.setMonarchTokensProvider('custom-language', { + tokenizer: { + root: [ + [new RegExp(`\\b(${Array.from(namespaces.keys()).join('|')})\\b`), "keyword"], + [new RegExp(`\\b(${KEYWORDS.join('|')})\\b`), "keyword"], + [ + new RegExp(`\\b(${functions.map(({ label }) => { + if ((label as string).includes(".")) { + const labels = (label as string).split(".") + return labels[labels.length - 1] + } + return label + }).join('|')})\\b`), + "function" + ], + [/"([^"\\]|\\.)*"/, 'string'], + [/'([^'\\]|\\.)*'/, 'string'], + [/\d+/, 'number'], + [/:(\w+)/, 'type'], + [/\{/, { token: 'delimiter.curly', next: '@bracketCounting' }], + [/\[/, { token: 'delimiter.square', next: '@bracketCounting' }], + [/\(/, { token: 'delimiter.parenthesis', next: '@bracketCounting' }], + ], + bracketCounting: [ + [/\{/, 'delimiter.curly', '@bracketCounting'], + [/\}/, 'delimiter.curly', '@pop'], + [/\[/, 'delimiter.square', '@bracketCounting'], + [/\]/, 'delimiter.square', '@pop'], + [/\(/, 'delimiter.parenthesis', '@bracketCounting'], + [/\)/, 'delimiter.parenthesis', '@pop'], + { include: 'root' } + ], + }, + ignoreCase: true, + }) + } else { + monacoI.languages.setMonarchTokensProvider('custom-language', { + tokenizer: { + root: [ + [new RegExp(`\\b(${KEYWORDS.join('|')})\\b`), "keyword"], + [/"([^"\\]|\\.)*"/, 'string'], + [/'([^'\\]|\\.)*'/, 'string'], + [/\d+/, 'number'], + [/:(\w+)/, 'type'], + [/\{/, { token: 'delimiter.curly', next: '@bracketCounting' }], + [/\[/, { token: 'delimiter.square', next: '@bracketCounting' }], + [/\(/, { token: 'delimiter.parenthesis', next: '@bracketCounting' }], + ], + bracketCounting: [ + [/\{/, 'delimiter.curly', '@bracketCounting'], + [/\}/, 'delimiter.curly', '@pop'], + [/\[/, 'delimiter.square', '@bracketCounting'], + [/\]/, 'delimiter.square', '@pop'], + [/\(/, 'delimiter.parenthesis', '@bracketCounting'], + [/\)/, 'delimiter.parenthesis', '@pop'], + { include: 'root' } + ], + }, + ignoreCase: true, }) - - addSuggestions(monacoInstance, [...sug.labels, ...sug.propertyKeys, ...sug.relationshipTypes], sug.functions) - - setSuggestions(sug) } - run() - }, [graph]) + return monacoI.languages.registerCompletionItemProvider("custom-language", { + provideCompletionItems: (model, position) => { + const word = model.getWordUntilPosition(position) + const range = new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn) + console.log(suggestions); + return { + suggestions: suggestions.map(s => ({ ...s, range })) + } + }, + }) + } useEffect(() => { - const interval = setInterval(() => { - getSuggestions() + const timeout = setTimeout(async () => { + if (!monacoInstance) return + const provider = await addSuggestions(monacoInstance) + setSugProvider(provider) }, 5000) return () => { - clearInterval(interval) - sugProvider?.dispose() + clearTimeout(timeout) + if (sugProvider) { + sugProvider.dispose() + console.log("cleanup dispose"); + } + console.log("cleanup"); } }, [graph.Id]) - const handleEditorWillMount = (monacoI: Monaco) => { - monacoI.languages.setMonarchTokensProvider('custom-language', { - tokenizer: { - root: [ - [new RegExp(`\\b(${KEYWORDS.join('|')})\\b`), "keyword"], - [/"([^"\\]|\\.)*"/, 'string'], - [/'([^'\\]|\\.)*'/, 'string'], - [/\d+/, 'number'], - [/:(\w+)/, 'type'], - [/\{/, { token: 'delimiter.curly', next: '@bracketCounting' }], - [/\[/, { token: 'delimiter.square', next: '@bracketCounting' }], - [/\(/, { token: 'delimiter.parenthesis', next: '@bracketCounting' }], - ], - bracketCounting: [ - [/\{/, 'delimiter.curly', '@bracketCounting'], - [/\}/, 'delimiter.curly', '@pop'], - [/\[/, 'delimiter.square', '@bracketCounting'], - [/\]/, 'delimiter.square', '@pop'], - [/\(/, 'delimiter.parenthesis', '@bracketCounting'], - [/\)/, 'delimiter.parenthesis', '@pop'], - { include: 'root' } - ], - }, - ignoreCase: true, - }); + const handleEditorWillMount = async (monacoI: Monaco) => { - monacoI.languages.register({ id: 'custom-language' }); - - monacoI.languages.setLanguageConfiguration('custom-language', { - brackets: [ - ['{', '}'], - ['[', ']'], - ['(', ')'] - ], - autoClosingPairs: [ - { open: '{', close: '}' }, - { open: '[', close: ']' }, - { open: '(', close: ')' }, - { open: '"', close: '"', notIn: ['string'] }, - { open: "'", close: "'", notIn: ['string', 'comment'] } - ], - surroundingPairs: [ - { open: '{', close: '}' }, - { open: '[', close: ']' }, - { open: '(', close: ')' }, - { open: '"', close: '"' }, - { open: "'", close: "'" } - ] - }); + monacoI.languages.register({ id: "custom-language" }) setTheme(monacoI) - monacoI.editor.setTheme('custom-theme'); + const provider = await addSuggestions(monacoI) - if (graph.Id) { - getSuggestions(monacoI) - } else { - addSuggestions(monacoI) - } - }; + setSugProvider(provider) + setMonacoInstance(monacoI) + } const handleEditorDidMount = (e: monaco.editor.IStandaloneCodeEditor, monacoI: Monaco) => { const updatePlaceholderVisibility = () => { @@ -619,10 +533,6 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre }); } - useEffect(() => { - setLineNumber(query.split("\n").length) - }, [query]) - return (
{ @@ -637,7 +547,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre runQuery(query) }} > -
+
document.body.clientHeight / 100 * MAX_HEIGHT ? document.body.clientHeight / 100 * MAX_HEIGHT : lineNumber * LINE_HEIGHT} @@ -647,7 +557,14 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre lineNumbers: lineNumber > 1 ? "on" : "off", }} value={(blur ? query.replace(/\s+/g, ' ').trim() : query)} - onChange={(val) => historyRef.current.historyCounter ? setQuery(val || "") : setCurrentQuery(val || "")} + onChange={(val) => { + if (historyRef.current.historyCounter) { + setQuery(val || ""); + } else { + setCurrentQuery(val || ""); + } + setLineNumber(val?.split("\n").length || 1); + }} theme="custom-theme" beforeMount={handleEditorWillMount} onMount={handleEditorDidMount} From d4fc6be6e635faa6554639d2a2fc5e9045b57ef2 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 27 Jan 2025 16:28:37 +0200 Subject: [PATCH 25/65] commit --- app/components/EditorComponent.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/app/components/EditorComponent.tsx b/app/components/EditorComponent.tsx index 59b483ec..2900c792 100644 --- a/app/components/EditorComponent.tsx +++ b/app/components/EditorComponent.tsx @@ -450,8 +450,10 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre setSugProvider(provider) setMonacoInstance(monacoI) } - + const handleEditorDidMount = (e: monaco.editor.IStandaloneCodeEditor, monacoI: Monaco) => { + editorRef.current = e + const updatePlaceholderVisibility = () => { const hasContent = !!e.getValue(); if (placeholderRef.current) { @@ -463,25 +465,18 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre if (placeholderRef.current) { placeholderRef.current.style.display = 'none'; } + setBlur(false) }); e.onDidBlurEditorText(() => { updatePlaceholderVisibility(); + setBlur(true) }); updatePlaceholderVisibility(); setMonacoInstance(monacoI) - editorRef.current = e - - e.onDidBlurEditorText(() => { - setBlur(true) - }) - - e.onDidFocusEditorText(() => { - setBlur(false) - }) const isFirstLine = e.createContextKey('isFirstLine', true); From ad603534371a051864a379db241cee0b3bb2ed3e Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 27 Jan 2025 18:07:41 +0200 Subject: [PATCH 26/65] update playwright config for artifacts --- .github/workflows/playwright.yml | 7 +++++++ playwright.config.ts | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 83757c75..d90f9982 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -40,3 +40,10 @@ jobs: name: playwright-report path: playwright-report/ retention-days: 30 + - name: Upload failed test screenshots + if: always() + uses: actions/upload-artifact@v4 + with: + name: failed-test-screenshots + path: playwright-report/screenshots/ + retention-days: 30 diff --git a/playwright.config.ts b/playwright.config.ts index b705a6f5..4bc5ebd2 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -21,7 +21,8 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + reporter: [['html', { outputFolder: 'playwright-report' }]], + outputDir: 'playwright-report/artifacts', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ @@ -29,6 +30,7 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + screenshot: 'only-on-failure', }, /* Configure projects for major browsers */ From cd294475017a1d1ee8bf9086ebdc5bf4834ea013 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 27 Jan 2025 18:58:04 +0200 Subject: [PATCH 27/65] Update playwright.yml --- .github/workflows/playwright.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index d90f9982..e88f6b09 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -34,6 +34,10 @@ jobs: npm install npm run build NEXTAUTH_SECRET=SECRET npm start & npx playwright test --reporter=dot,list + - name: Ensure required directories exist + run: | + mkdir -p playwright-report + mkdir -p playwright-report/artifacts - uses: actions/upload-artifact@v4 if: always() with: @@ -45,5 +49,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: failed-test-screenshots - path: playwright-report/screenshots/ + path: playwright-report/artifacts/ retention-days: 30 From a7e3b990601f0a388ae6607e75f8b22f94e4da90 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 27 Jan 2025 19:27:00 +0200 Subject: [PATCH 28/65] updating tests --- e2e/logic/POM/settingsConfigPage.ts | 7 +++++++ e2e/tests/settingsConfig.spec.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/e2e/logic/POM/settingsConfigPage.ts b/e2e/logic/POM/settingsConfigPage.ts index 3b6eea73..2403ca2a 100644 --- a/e2e/logic/POM/settingsConfigPage.ts +++ b/e2e/logic/POM/settingsConfigPage.ts @@ -24,6 +24,10 @@ export default class SettingsConfigPage extends BasePage { return (role: string) => this.page.locator(`//tbody//tr[@data-id='${role}']/td[3]/div/div/button[1]`) } + private get toastCloseBtn(): Locator { + return this.page.locator("//li[@role='status']/button"); + } + async modifyRoleValue(role: string, input: string): Promise { await this.roleContentValue(role).hover(); await this.EditRoleButton(role).click(); @@ -33,4 +37,7 @@ export default class SettingsConfigPage extends BasePage { return value } + async clickOnToastCloseBtn(): Promise{ + await this.toastCloseBtn.click(); + } } \ No newline at end of file diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index 54226f85..31978769 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -225,6 +225,7 @@ test.describe('Settings Tests', () => { await new Promise(resolve => { setTimeout(resolve, 1000) }); const apiCall = new ApiCalls() let value = String((await apiCall.getSettingsRoleValue(roles.maxInfoQueries)).config[1]); + await settingsConfigPage.clickOnToastCloseBtn(); console.log(value); expect(value === "999").toBe(true); From c719d037725f77802e43131e5ce9be5079d20643 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 27 Jan 2025 19:45:07 +0200 Subject: [PATCH 29/65] increase timeout --- e2e/tests/settingsConfig.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index 31978769..c9972b1c 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -222,7 +222,7 @@ test.describe('Settings Tests', () => { test(`@admin Modify maxInfoQueries via UI validation via API: Input value: 999`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) await settingsConfigPage.modifyRoleValue(roles.maxInfoQueries, "999") - await new Promise(resolve => { setTimeout(resolve, 1000) }); + await new Promise(resolve => { setTimeout(resolve, 3000) }); const apiCall = new ApiCalls() let value = String((await apiCall.getSettingsRoleValue(roles.maxInfoQueries)).config[1]); await settingsConfigPage.clickOnToastCloseBtn(); From 91deab99578040d41dd1e4d63687dd32b70364d2 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Mon, 27 Jan 2025 20:00:50 +0200 Subject: [PATCH 30/65] Update settingsConfig.spec.ts --- e2e/tests/settingsConfig.spec.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index c9972b1c..fc9efabd 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -224,12 +224,16 @@ test.describe('Settings Tests', () => { await settingsConfigPage.modifyRoleValue(roles.maxInfoQueries, "999") await new Promise(resolve => { setTimeout(resolve, 3000) }); const apiCall = new ApiCalls() - let value = String((await apiCall.getSettingsRoleValue(roles.maxInfoQueries)).config[1]); - await settingsConfigPage.clickOnToastCloseBtn(); + let value; + for (let i = 0; i < 5; i++) { + value = String((await apiCall.getSettingsRoleValue(roles.maxInfoQueries)).config[1]); + if (value === "999") break; + await new Promise(resolve => setTimeout(resolve, 1500)); + } + console.log(value); - - expect(value === "999").toBe(true); - await apiCall.modifySettingsRole(roles.maxInfoQueries, "1000") + expect(value).toBe("999"); + await apiCall.modifySettingsRole(roles.maxInfoQueries, "1000"); }); }) \ No newline at end of file From eb0aad0a71e602c682ad7571eecf9f5097c5fd22 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:51:45 +0200 Subject: [PATCH 31/65] refresh ui before api call --- e2e/tests/settingsConfig.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index fc9efabd..48c51c86 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -222,6 +222,7 @@ test.describe('Settings Tests', () => { test(`@admin Modify maxInfoQueries via UI validation via API: Input value: 999`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) await settingsConfigPage.modifyRoleValue(roles.maxInfoQueries, "999") + await settingsConfigPage.refreshPage(); await new Promise(resolve => { setTimeout(resolve, 3000) }); const apiCall = new ApiCalls() let value; From 0244c82f16cbe1a846c68ae638c6189084fd6c50 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:59:09 +0200 Subject: [PATCH 32/65] add scroll after reload --- components/ui/table.tsx | 2 +- e2e/logic/POM/settingsConfigPage.ts | 8 ++++++++ e2e/tests/settingsConfig.spec.ts | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/components/ui/table.tsx b/components/ui/table.tsx index dbe1675d..6e98a5b1 100644 --- a/components/ui/table.tsx +++ b/components/ui/table.tsx @@ -11,7 +11,7 @@ const Table = React.forwardRef< HTMLTableElement, TableProps >(({ className, parentClassName, ...props }, ref) => ( -
+
{ await this.roleContentValue(role).hover(); await this.EditRoleButton(role).click(); @@ -40,4 +44,8 @@ export default class SettingsConfigPage extends BasePage { async clickOnToastCloseBtn(): Promise{ await this.toastCloseBtn.click(); } + + async scrollToBottomInTable(): Promise { + await this.tableContent.evaluate((el) => el.scrollTo(0, el.scrollHeight)); + } } \ No newline at end of file diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index 48c51c86..03786811 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -223,6 +223,7 @@ test.describe('Settings Tests', () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) await settingsConfigPage.modifyRoleValue(roles.maxInfoQueries, "999") await settingsConfigPage.refreshPage(); + await settingsConfigPage.scrollToBottomInTable(); await new Promise(resolve => { setTimeout(resolve, 3000) }); const apiCall = new ApiCalls() let value; From 338a4affbf67fcb0e4ffd91efd29b5b5ec6ddacb Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 28 Jan 2025 13:21:14 +0200 Subject: [PATCH 33/65] adding logging --- e2e/logic/api/apiCalls.ts | 1 + e2e/tests/settingsConfig.spec.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/e2e/logic/api/apiCalls.ts b/e2e/logic/api/apiCalls.ts index 26881060..1697766c 100644 --- a/e2e/logic/api/apiCalls.ts +++ b/e2e/logic/api/apiCalls.ts @@ -35,6 +35,7 @@ export default class ApiCalls { async getSettingsRoleValue(roleName: string, data?: any): Promise { const result = await getRequest(urls.api.settingsConfig + roleName, data) const jsonData = await result.json(); + console.log("api calls res:", jsonData, " role: ", roleName); return jsonData } diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index 03786811..8f753fdc 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -224,6 +224,9 @@ test.describe('Settings Tests', () => { await settingsConfigPage.modifyRoleValue(roles.maxInfoQueries, "999") await settingsConfigPage.refreshPage(); await settingsConfigPage.scrollToBottomInTable(); + const res = await settingsConfigPage.getRoleContentValue(roles.maxInfoQueries); + console.log("ui value: ", res); + await new Promise(resolve => { setTimeout(resolve, 3000) }); const apiCall = new ApiCalls() let value; @@ -233,7 +236,7 @@ test.describe('Settings Tests', () => { await new Promise(resolve => setTimeout(resolve, 1500)); } - console.log(value); + console.log("api value:", value); expect(value).toBe("999"); await apiCall.modifySettingsRole(roles.maxInfoQueries, "1000"); }); From 3f7c147d8271a0008c170349a7cfd07e59b98557 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 28 Jan 2025 13:39:26 +0200 Subject: [PATCH 34/65] commit --- app/components/CreateGraph.tsx | 22 ++++--- app/components/DialogComponent.tsx | 12 ++-- app/components/ExportGraph.tsx | 12 ++-- app/components/ForceGraph.tsx | 7 +-- app/components/FormComponent.tsx | 14 +++-- app/components/TableComponent.tsx | 96 +++++++++++++++++------------- app/components/ui/combobox.tsx | 20 ++++++- app/graph/Duplicate.tsx | 12 +++- app/graph/GraphDataPanel.tsx | 2 +- app/graph/Selector.tsx | 13 +++- app/graph/View.tsx | 65 +++++++++++++------- app/graph/labels.tsx | 4 +- app/layout.tsx | 7 ++- app/schema/SchemaCreateElement.tsx | 2 +- app/schema/SchemaDataPanel.tsx | 2 +- app/settings/users/Users.tsx | 83 ++++++++------------------ lib/utils.ts | 50 +++++++++++++++- 17 files changed, 257 insertions(+), 166 deletions(-) diff --git a/app/components/CreateGraph.tsx b/app/components/CreateGraph.tsx index 8cb14894..d592fff2 100644 --- a/app/components/CreateGraph.tsx +++ b/app/components/CreateGraph.tsx @@ -3,9 +3,10 @@ "use client" import { useState } from "react" -import { AlertCircle, PlusCircle } from "lucide-react" +import { InfoIcon, PlusCircle } from "lucide-react" import { prepareArg, securedFetch } from "@/lib/utils" import { useToast } from "@/components/ui/use-toast" +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import DialogComponent from "./DialogComponent" import Button from "./ui/Button" import CloseDialog from "./CloseDialog" @@ -54,6 +55,10 @@ export default function CreateGraph({ onSetGraphName(graphName) setGraphName("") setOpen(false) + toast({ + title: "Graph created successfully", + description: "The graph has been created successfully", + }) } return ( @@ -68,13 +73,14 @@ export default function CreateGraph({ handleCreateGraph(e) }}>
- + + + + + + {`${type} names can be edited later`} + +

Name your graph:

{ - description && - - {description} - + description ? + + {description} + + : + + } {children} diff --git a/app/components/ExportGraph.tsx b/app/components/ExportGraph.tsx index 5ca07104..f5d106fc 100644 --- a/app/components/ExportGraph.tsx +++ b/app/components/ExportGraph.tsx @@ -1,4 +1,4 @@ -import { useState } from "react" +import { ReactNode, useState } from "react" import { prepareArg, securedFetch } from "@/lib/utils" import { useToast } from "@/components/ui/use-toast" import DialogComponent from "./DialogComponent" @@ -8,9 +8,10 @@ import CloseDialog from "./CloseDialog" interface Props { selectedValues: string[] type: string + trigger: ReactNode } -export default function ExportGraph({ selectedValues, type }: Props) { +export default function ExportGraph({ selectedValues, type, trigger }: Props) { const [open, setOpen] = useState(false) const { toast } = useToast() @@ -49,12 +50,7 @@ export default function ExportGraph({ selectedValues, type }: Props) { value !== "").length === 0} - /> - } + trigger={trigger} title="Export your graph" description="Export a .dump file of your data" > diff --git a/app/components/ForceGraph.tsx b/app/components/ForceGraph.tsx index 10144fee..41a87d7a 100644 --- a/app/components/ForceGraph.tsx +++ b/app/components/ForceGraph.tsx @@ -6,7 +6,7 @@ import { Dispatch, RefObject, SetStateAction, useEffect, useRef, useState } from "react" import ForceGraph2D from "react-force-graph-2d" -import { securedFetch } from "@/lib/utils" +import { getComplementaryColor, securedFetch } from "@/lib/utils" import { useToast } from "@/components/ui/use-toast" import { Graph, GraphData, Link, Node } from "../api/graph/model" @@ -227,9 +227,6 @@ export default function ForceGraph({ if (!start.x || !start.y || !end.x || !end.y) return - ctx.strokeStyle = link.color; - ctx.globalAlpha = 0.5; - if (start.id === end.id) { const radius = NODE_SIZE * link.curve * 6.2; const angleOffset = -Math.PI / 4; // 45 degrees offset for text alignment @@ -255,7 +252,6 @@ export default function ForceGraph({ } // add label - ctx.globalAlpha = 1; ctx.fillStyle = 'black'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; @@ -273,6 +269,7 @@ export default function ForceGraph({ onEngineStop={() => { handleCooldown(0) }} + linkColor={link => getComplementaryColor(link.color)} linkCurvature="curve" nodeVisibility="visible" linkVisibility="visible" diff --git a/app/components/FormComponent.tsx b/app/components/FormComponent.tsx index fd7af685..3ad3702d 100644 --- a/app/components/FormComponent.tsx +++ b/app/components/FormComponent.tsx @@ -4,8 +4,9 @@ "use client" import { useState } from "react" -import { AlertCircle, EyeIcon, EyeOffIcon } from "lucide-react" +import { EyeIcon, EyeOffIcon, InfoIcon } from "lucide-react" import { cn } from "@/lib/utils" +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import Button from "./ui/Button" import Combobox from "./ui/combobox" import Input from "./ui/Input" @@ -74,9 +75,14 @@ export default function FormComponent({ handleSubmit, fields, error = undefined, { field.info && - + + + + + + {field.info} + + }
diff --git a/app/components/TableComponent.tsx b/app/components/TableComponent.tsx index 75162630..c4ed98a0 100644 --- a/app/components/TableComponent.tsx +++ b/app/components/TableComponent.tsx @@ -1,3 +1,4 @@ +/* eslint-disable import/no-cycle */ /* eslint-disable react/no-array-index-key */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable no-nested-ternary */ @@ -14,10 +15,12 @@ import { CheckCircle, Pencil, XCircle } from "lucide-react"; import Button from "./ui/Button"; import Input from "./ui/Input"; import { DataCell } from "../api/graph/model"; +import Combobox from "./ui/combobox"; type Cell = { value: DataCell, - onChange?: (value: string) => Promise + onChange?: (value: string) => Promise, + type?: string } export interface Row { @@ -29,10 +32,11 @@ interface Props { headers: string[], rows: Row[], children?: React.ReactNode, - setRows?: (rows: Row[]) => void + setRows?: (rows: Row[]) => void, + options?: string[] } -export default function TableComponent({ headers, rows, children, setRows }: Props) { +export default function TableComponent({ headers, rows, children, setRows, options }: Props) { const [search, setSearch] = useState("") const [isSearchable, setIsSearchable] = useState(false) @@ -45,6 +49,8 @@ export default function TableComponent({ headers, rows, children, setRows }: Pro setNewValue(value) } + console.log(rows) + return (
@@ -152,48 +158,56 @@ export default function TableComponent({ headers, rows, children, setRows }: Pro /> : cell.value && editable === `${i}-${j}` ? -
- ref?.focus()} - variant="primary" - className="grow" - value={newValue} - onChange={(e) => setNewValue(e.target.value)} - onKeyDown={async (e) => { - if (e.key === "Escape") { - e.preventDefault() - handleSetEditable("", "") - } - - if (e.key !== "Enter") return - - e.preventDefault() - const result = await cell.onChange!(newValue) - if (result) { - handleSetEditable("", "") - } + cell.type === "combobox" ? + { + cell.onChange!(value) + handleSetEditable("", "") }} /> -
- - + /> +
+ + +
-
:

{cell.value}

diff --git a/app/components/ui/combobox.tsx b/app/components/ui/combobox.tsx index 8ccf2e20..99334f27 100644 --- a/app/components/ui/combobox.tsx +++ b/app/components/ui/combobox.tsx @@ -1,3 +1,4 @@ +/* eslint-disable import/no-cycle */ /* eslint-disable @typescript-eslint/no-use-before-define */ /* eslint-disable react/require-default-props */ @@ -92,6 +93,10 @@ export default function Combobox({ isSelectGraph = false, disabled = false, inTa setOpenDelete(false) setOpenMenage(false) handleSetRows(options.filter(opt => !opts.includes(opt))) + toast({ + title: "Graph(s) deleted successfully", + description: "The graph(s) have been deleted successfully", + }) } return ( @@ -148,10 +153,11 @@ export default function Combobox({ isSelectGraph = false, disabled = false, inTa onOpenChange={setOpenDelete} title="Delete Graph" trigger={
- opt.checked).map(opt => opt.cells[0].value as string)} type={type!} /> + opt.checked).length === 0} + /> + } + selectedValues={rows.filter(opt => opt.checked).map(opt => opt.cells[0].value as string)} + type={type!} + /> diff --git a/app/graph/Duplicate.tsx b/app/graph/Duplicate.tsx index f39ae8dd..935fae4d 100644 --- a/app/graph/Duplicate.tsx +++ b/app/graph/Duplicate.tsx @@ -15,17 +15,23 @@ export default function Duplicate({ open, onOpenChange, selectedValue, onDuplica const [duplicateName, setDuplicateName] = useState(""); const { toast } = useToast() - + const handleDuplicate = async (e: FormEvent) => { e.preventDefault() - await securedFetch(`api/graph/${prepareArg(duplicateName)}/?sourceName=${prepareArg(selectedValue)}`, { + const result = await securedFetch(`api/graph/${prepareArg(duplicateName)}/?sourceName=${prepareArg(selectedValue)}`, { method: "POST" }, toast) - onOpenChange(false) + if (!result.ok) return + onDuplicate(duplicateName) + onOpenChange(false) + toast({ + title: "Graph duplicated successfully", + description: "The graph has been duplicated successfully", + }) } return ( diff --git a/app/graph/GraphDataPanel.tsx b/app/graph/GraphDataPanel.tsx index abe0c406..9b554838 100644 --- a/app/graph/GraphDataPanel.tsx +++ b/app/graph/GraphDataPanel.tsx @@ -212,7 +212,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement,

{label}

-

{attributes.length} Attributes

+

{attributes.length} Attributes

diff --git a/app/graph/Selector.tsx b/app/graph/Selector.tsx index 6f4f8138..dce26412 100644 --- a/app/graph/Selector.tsx +++ b/app/graph/Selector.tsx @@ -46,7 +46,7 @@ export default function Selector({ onChange, graphName, setGraphName, queries, r const type = pathname.includes("/schema") ? "Schema" : "Graph" const [isRotating, setIsRotating] = useState(false); const { toast } = useToast() - + useEffect(() => { if (!graphName) return setOptions(prev => { @@ -135,6 +135,12 @@ export default function Selector({ onChange, graphName, setGraphName, queries, r
+ } type={type} selectedValues={[selectedValue]} /> @@ -145,6 +151,7 @@ export default function Selector({ onChange, graphName, setGraphName, queries, r onDuplicate={(name) => { setOptions(prev => [...prev, name]) setSelectedValue(name) + setGraphName(name) }} selectedValue={selectedValue} /> @@ -156,9 +163,9 @@ export default function Selector({ onChange, graphName, setGraphName, queries, r selectedValue &&

Created on 2/2 24

- {nodesCount} Nodes + {nodesCount} Nodes

|

- {edgesCount} Edges + {edgesCount} Edges
} { diff --git a/app/graph/View.tsx b/app/graph/View.tsx index 9b19def3..481775c4 100644 --- a/app/graph/View.tsx +++ b/app/graph/View.tsx @@ -1,12 +1,11 @@ /* eslint-disable no-param-reassign */ -import { ChevronDown, ChevronUp, FileCheck2, PlusCircle, RotateCcw, Trash2 } from "lucide-react" +import { Check, ChevronDown, ChevronUp, FileCheck2, PlusCircle, RotateCcw, Trash2, X } from "lucide-react" import { useEffect, useState } from "react" import { cn } from "@/lib/utils" import { DEFAULT_COLORS, Graph } from "../api/graph/model" import Button from "../components/ui/Button" import DialogComponent from "../components/DialogComponent" -import Input from "../components/ui/Input" export default function View({ graph, setGraph, selectedValue }: { graph: Graph, @@ -22,8 +21,9 @@ export default function View({ graph, setGraph, selectedValue }: { setGraph(Graph.create(graph.Id, { data: graph.Data, metadata: graph.Metadata }, false, true, colors || colorsArr)) if (colors) { localStorage.removeItem(graph.Id) + } else { + localStorage.setItem(graph.Id, JSON.stringify(colorsArr)); } - localStorage.setItem(graph.Id, JSON.stringify(colorsArr)); } useEffect(() => { @@ -40,10 +40,10 @@ export default function View({ graph, setGraph, selectedValue }: { /> } className="w-[30%] h-[50%]" - title="Preferences" + title="Labels Legend" + description="Pick a color for each label" >
-

Legends

    { colorsArr.map((c, i) => ( @@ -86,9 +86,9 @@ export default function View({ graph, setGraph, selectedValue }: { c === newColor || c === editable ? <>
    - ref?.focus()} - className="w-24" value={editable === c ? editable : newColor} onChange={(e) => { setColorsArr(prev => { @@ -98,17 +98,16 @@ export default function View({ graph, setGraph, selectedValue }: { }); if (editable === c) { setEditable(e.target.value) - } else setNewColor(e.target.value); - }} - onBlur={() => { - setNewColor(""); - colorsArr.splice(i, 1); + } else { + setNewColor(e.target.value); + } }} onKeyDown={(e) => { if (e.key !== "Enter") return setNewColor(""); setEditable(""); }} + type="color" /> : <> @@ -120,15 +119,36 @@ export default function View({ graph, setGraph, selectedValue }: { } { - hover === c && !(c === newColor || c === editable) && - + (c === newColor || c === editable) ? +
    + + +
    + : hover === c && + } )) @@ -136,7 +156,7 @@ export default function View({ graph, setGraph, selectedValue }: {
)) diff --git a/app/layout.tsx b/app/layout.tsx index 141d0f53..1728c351 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,6 +4,7 @@ import "./globals.css"; import { Inter } from "next/font/google"; import { Metadata } from "next"; import { Toaster } from "@/components/ui/toaster"; +import { TooltipProvider } from "@/components/ui/tooltip"; import NextAuthProvider from "./providers"; import GTM from "./GTM"; @@ -26,7 +27,11 @@ export default function RootLayout({ - {children} + + + {children} + + diff --git a/app/schema/SchemaCreateElement.tsx b/app/schema/SchemaCreateElement.tsx index 13d9f354..2012f06a 100644 --- a/app/schema/SchemaCreateElement.tsx +++ b/app/schema/SchemaCreateElement.tsx @@ -194,7 +194,7 @@ export default function SchemaCreateElement({ onCreate, onExpand, selectedNodes, /> }
-

{attributes.length} Attributes

+

{attributes.length} Attributes

diff --git a/app/schema/SchemaDataPanel.tsx b/app/schema/SchemaDataPanel.tsx index 4daa9d4f..0ca7f8fa 100644 --- a/app/schema/SchemaDataPanel.tsx +++ b/app/schema/SchemaDataPanel.tsx @@ -143,7 +143,7 @@ export default function SchemaDataPanel({ obj, onExpand, onSetAttributes, onRemo

{label}

-

{attributes.length} Attributes

+

{attributes.length} Attributes

diff --git a/app/settings/users/Users.tsx b/app/settings/users/Users.tsx index dcedc96c..4b791771 100644 --- a/app/settings/users/Users.tsx +++ b/app/settings/users/Users.tsx @@ -2,10 +2,9 @@ "use client"; -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { CreateUser, User } from "@/app/api/user/model"; import { prepareArg, securedFetch } from "@/lib/utils"; -import Combobox from "@/app/components/ui/combobox"; import TableComponent, { Row } from "@/app/components/TableComponent"; import { useToast } from "@/components/ui/use-toast"; import { ToastAction } from "@/components/ui/toast"; @@ -25,16 +24,26 @@ export default function Users() { const [rows, setRows] = useState([]) const { toast } = useToast() - useEffect(() => { - setRows(users.map((user) => ({ - cells: [{ - value: user.username, - }, { - value: user.role, - }], - checked: false, - }))) - }, [users]) + const handleSetRole = useCallback(async (username: string, role: string, isUndo: boolean) => { + console.log(users); + + const oldRole = users.find(user => user.username === username)!.role + + const result = await securedFetch(`api/user/${prepareArg(username)}/?role=${role}`, { + method: 'PATCH' + }, toast) + + if (result.ok) { + setUsers(users.map(user => user.username === username ? { ...user, role } : user)) + setRows(rows.map(row => row.cells[0].value === username ? { ...row, cells: [{ ...row.cells[0], value: role }] } : row)) + + toast({ + title: "Success", + description: `${username} role updated successfully`, + action: isUndo ? handleSetRole(username, oldRole, false)}>Undo : undefined + }) + } + }, [users, toast, rows]) useEffect(() => { (async () => { @@ -53,51 +62,14 @@ export default function Users() { value: user.username, }, { value: user.role, - onChange: (value: string) => handleSetRole([user.username], [value], true) + onChange: user.username === "default" ? undefined : (value: string) => handleSetRole(user.username, value, true), + type: "combobox" }], checked: false, }))) } })() - - }, []) - - const handleSetRole = async (usernames: string[], role: string[], isUndo: boolean) => { - const oldRoles = usernames.map(username => users.find(user => user.username === username)!.role) - const updatedUsers = await Promise.all(users.map(async (user, i) => { - - if (!usernames.includes(user.username)) return user - - const result = await securedFetch(`api/user/${prepareArg(user.username)}/?role=${role}`, { - method: 'PATCH' - }, toast) - - if (result.ok) { - return { - ...user, - role: role.length === 1 ? role[0] : role[i] - } - } - - return user - })) - - setUsers(updatedUsers) - setRows(rows.map(row => { - if (!usernames.includes(row.cells[0].value as string)) return row - return { - ...row, - cells: [{ ...row.cells[0], value: role.length === 1 ? role[0] : role[usernames.indexOf(row.cells[0].value as string)] }], - checked: false - } - })) - toast({ - title: "Success", - description: `${usernames.join(", ")} role updated successfully`, - action: isUndo ? handleSetRole(usernames, oldRoles, false)}>Undo : undefined - }) - return true - } + }, [handleSetRole, toast]) const handleAddUser = async ({ username, password, role }: CreateUser) => { if (!role) { @@ -132,16 +104,11 @@ export default function Users() { headers={["Name", "Role"]} rows={rows} setRows={setRows} + options={ROLES} >
row.checked).map(row => users.find(user => user.username === row.cells[0].value)!)} setUsers={setUsers} /> - row.checked).length === 0} - type="Role" - options={ROLES} - setSelectedValue={(role) => { handleSetRole(rows.filter(row => row.checked).map(row => row.cells[0].value as string), [role], true) }} - />
diff --git a/lib/utils.ts b/lib/utils.ts index 059aa9d6..1e0c0f84 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -41,4 +41,52 @@ export function prepareArg(arg: string) { return encodeURIComponent(arg.trim()) } -export const defaultQuery = (q?: string) => q || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) return n,e,m LIMIT 100" \ No newline at end of file +export const defaultQuery = (q?: string) => q || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) return n,e,m LIMIT 100" + +export const rgbToHsl = (r: number, g: number, b: number): [number, number, number] => { + const newR = r / 255; + const newG = g / 255; + const newB = b / 255; + + const max = Math.max(newR, newG, newB); + const min = Math.min(newR, newG, newB); + let h = 0; + let s = 0; + const l = (max + min) / 2; + + if (max !== min) { + const d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case newR: h = (newG - newB) / d + (newG < newB ? 6 : 0); break; + case newG: h = (newB - newR) / d + 2; break; + case newB: h = (newR - newG) / d + 4; break; + default: + } + h *= 60; + } + + return [h, s * 100, l * 100]; +} + +export const getComplementaryColor = (color: string): string => { + // Create a temporary div to use canvas color parsing + const div = document.createElement('div'); + div.style.backgroundColor = color; + document.body.appendChild(div); + const rgbColor = window.getComputedStyle(div).backgroundColor; + document.body.removeChild(div); + + // Parse RGB values + const [r, g, b] = rgbColor.match(/\d+/g)!.map(Number); + + // Convert to HSL + const [h, s, l] = rgbToHsl(r, g, b); + + // Shift hue by 180 degrees for complementary color + const newHue = (h + 180) % 360; + + // Convert back to RGB + return `hsl(${newHue}, ${s}%, ${l}%)`; +} \ No newline at end of file From d24a326ed09641d11fde945fa9a3b114f30eafff Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Tue, 28 Jan 2025 21:06:26 +0200 Subject: [PATCH 35/65] Update playwright.yml --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index e88f6b09..adfffbb3 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -16,7 +16,7 @@ jobs: services: falkordb: - image: falkordb/falkordb:latest + image: falkordb/falkordb:v4.4.1 ports: - 6379:6379 From 3560c5567ab7efc2e0b0e73870a3e8b2f1940128 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 29 Jan 2025 17:08:01 +0200 Subject: [PATCH 36/65] commit --- app/api/graph/model.ts | 23 +++++++----- app/components/CreateGraph.tsx | 4 +-- app/components/TableComponent.tsx | 10 +++++- app/components/ui/Button.tsx | 45 ++++++++++++++++------- app/components/ui/combobox.tsx | 14 ++++++-- app/graph/GraphDataPanel.tsx | 1 + app/graph/GraphView.tsx | 60 ++++++++++++++++++++----------- app/graph/Selector.tsx | 1 + app/schema/SchemaView.tsx | 29 +++++++++------ 9 files changed, 128 insertions(+), 59 deletions(-) diff --git a/app/api/graph/model.ts b/app/api/graph/model.ts index ec94948b..e3f22157 100644 --- a/app/api/graph/model.ts +++ b/app/api/graph/model.ts @@ -127,14 +127,12 @@ export class Graph { private elements: GraphData; - private categoriesMap: Map; + private colorIndex: number = 0; - private categoriesColorIndex: number = 0; + private categoriesMap: Map; private labelsMap: Map; - private labelsColorIndex: number = 0; - private nodesMap: Map; private linksMap: Map; @@ -285,8 +283,15 @@ export class Graph { } return c }) + this.labels = this.labels.map(l => { + if (this.labelsMap.get(l.name)!.index! < l.index) { + l.index -= 1 + this.labelsMap.get(l.name)!.index = l.index + } + return l + }) this.categoriesMap.delete("") - this.categoriesColorIndex -= 1 + this.colorIndex -= 1 this.elements.nodes.forEach(n => { n.color = this.getCategoryColorValue(this.categoriesMap.get(n.category[0])?.index) }) @@ -484,8 +489,8 @@ export class Graph { let c = this.categoriesMap.get(category) if (!c) { - c = { name: category, index: this.categoriesColorIndex, show: true } - this.categoriesColorIndex += 1 + c = { name: category, index: this.colorIndex, show: true } + this.colorIndex += 1 this.categoriesMap.set(c.name, c) this.categories.push(c) } @@ -498,8 +503,8 @@ export class Graph { let l = this.labelsMap.get(category) if (!l) { - l = { name: category, index: this.labelsColorIndex, show: true } - this.labelsColorIndex += 1 + l = { name: category, index: this.colorIndex, show: true } + this.colorIndex += 1 this.labelsMap.set(l.name, l) this.labels.push(l) } diff --git a/app/components/CreateGraph.tsx b/app/components/CreateGraph.tsx index d592fff2..e3516d43 100644 --- a/app/components/CreateGraph.tsx +++ b/app/components/CreateGraph.tsx @@ -24,7 +24,7 @@ export default function CreateGraph({ trigger = ( @@ -81,7 +81,7 @@ export default function CreateGraph({ {`${type} names can be edited later`} -

Name your graph:

+

Name your {type}:

ref?.focus()} diff --git a/app/components/TableComponent.tsx b/app/components/TableComponent.tsx index c4ed98a0..6d704444 100644 --- a/app/components/TableComponent.tsx +++ b/app/components/TableComponent.tsx @@ -12,6 +12,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ import { cn } from "@/lib/utils"; import { useState } from "react"; import { CheckCircle, Pencil, XCircle } from "lucide-react"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import Button from "./ui/Button"; import Input from "./ui/Input"; import { DataCell } from "../api/graph/model"; @@ -209,7 +210,14 @@ export default function TableComponent({ headers, rows, children, setRows, optio :
-

{cell.value}

+ + +

{cell.value}

+
+ + {cell.value} + +
{ cell.onChange && hover === `${i}` && diff --git a/app/components/ui/Button.tsx b/app/components/ui/Button.tsx index e5e8ff90..69d58549 100644 --- a/app/components/ui/Button.tsx +++ b/app/components/ui/Button.tsx @@ -1,5 +1,6 @@ /* eslint-disable react/button-has-type */ /* eslint-disable react/jsx-props-no-spreading */ +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import { cn } from "@/lib/utils" import { forwardRef } from "react" @@ -42,19 +43,37 @@ const getClassName = (variant: Variant, disable: boolean | undefined, open: bool return className } -const Button = forwardRef(({ label, variant = "button", open, className, title, type = "button", disabled, children, ...props }, ref) => ( - -)) +const Button = forwardRef(({ label, variant = "button", open, className, title, type = "button", disabled, children, ...props }, ref) => + (title || label) ? ( + + + + + + {title || label} + + + ) : ( + + )) Button.displayName = "Button" diff --git a/app/components/ui/combobox.tsx b/app/components/ui/combobox.tsx index 99334f27..9cb37409 100644 --- a/app/components/ui/combobox.tsx +++ b/app/components/ui/combobox.tsx @@ -9,6 +9,7 @@ import { cn, prepareArg, securedFetch } from "@/lib/utils" import { useEffect, useState } from "react" import { Select, SelectContent, SelectGroup, SelectItem, SelectSeparator, SelectTrigger, SelectValue } from "@/components/ui/select" import { useToast } from "@/components/ui/use-toast" +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import Button from "./Button" import TableComponent, { Row } from "../TableComponent" import CloseDialog from "../CloseDialog" @@ -105,9 +106,16 @@ export default function Combobox({ isSelectGraph = false, disabled = false, inTa setOpen(o) if (onOpenChange) onOpenChange(o) }}> - - - + + + + + + + + {options.length === 0 ? "There is no graphs" : selectedValue || `Select ${type || "Graph"}`} + +
    diff --git a/app/graph/GraphDataPanel.tsx b/app/graph/GraphDataPanel.tsx index 9b554838..eb52ee80 100644 --- a/app/graph/GraphDataPanel.tsx +++ b/app/graph/GraphDataPanel.tsx @@ -262,6 +262,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, trigger={ diff --git a/app/graph/GraphView.tsx b/app/graph/GraphView.tsx index 3c0a779b..8b7e5231 100644 --- a/app/graph/GraphView.tsx +++ b/app/graph/GraphView.tsx @@ -13,6 +13,7 @@ import { Session } from "next-auth"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useToast } from "@/components/ui/use-toast"; import { Switch } from "@/components/ui/switch"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Category, Graph, GraphData, Link, Node } from "../api/graph/model"; import DataPanel from "./GraphDataPanel"; import Labels from "./labels"; @@ -21,7 +22,7 @@ import Button from "../components/ui/Button"; import TableView from "./TableView"; const ForceGraph = dynamic(() => import("../components/ForceGraph"), { ssr: false }); -const EditorComponent = dynamic(() => import("../components/EditorComponent"), {ssr: false}) +const EditorComponent = dynamic(() => import("../components/EditorComponent"), { ssr: false }) function GraphView({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, fetchCount, session }: { graph: Graph @@ -230,21 +231,30 @@ function GraphView({ graph, selectedElement, setSelectedElement, runQuery, histo setTabsValue("Graph")} - title="Graph"> - + > + setTabsValue("Table")} - title="Table" > -
+
+ @@ -277,16 +287,24 @@ function GraphView({ graph, selectedElement, setSelectedElement, runQuery, histo > {!maximize ? : } -
- {cooldownTicks === undefined ? : } - { - handleCooldown(cooldownTicks === undefined ? 0 : undefined) - }} - /> +
+ + +
+ {cooldownTicks === undefined ? : } + { + handleCooldown(cooldownTicks === undefined ? 0 : undefined) + }} + /> +
+
+ +

Animation Control

+
+
diff --git a/app/schema/SchemaView.tsx b/app/schema/SchemaView.tsx index 0bdb6aa7..2ec5957b 100644 --- a/app/schema/SchemaView.tsx +++ b/app/schema/SchemaView.tsx @@ -11,6 +11,7 @@ import { Session } from "next-auth" import dynamic from "next/dynamic" import { useToast } from "@/components/ui/use-toast" import { Switch } from "@/components/ui/switch" +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import Toolbar from "../graph/toolbar" import SchemaDataPanel from "./SchemaDataPanel" import Labels from "../graph/labels" @@ -371,16 +372,24 @@ export default function SchemaView({ schema, fetchCount, session }: Props) { : } -
- {cooldownTicks === undefined ? : } - { - handleCooldown(cooldownTicks === undefined ? 0 : undefined) - }} - /> +
+ + +
+ {cooldownTicks === undefined ? : } + { + handleCooldown(cooldownTicks === undefined ? 0 : undefined) + }} + /> +
+
+ +

Animation Control

+
+
Date: Thu, 30 Jan 2025 12:13:36 +0200 Subject: [PATCH 37/65] fix suggestion duplication --- app/components/EditorComponent.tsx | 282 ++++++++++++++--------------- app/graph/GraphView.tsx | 1 - 2 files changed, 139 insertions(+), 144 deletions(-) diff --git a/app/components/EditorComponent.tsx b/app/components/EditorComponent.tsx index 2900c792..3785fd1d 100644 --- a/app/components/EditorComponent.tsx +++ b/app/components/EditorComponent.tsx @@ -3,7 +3,7 @@ "use client"; -import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; +import { Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Editor, Monaco } from "@monaco-editor/react" import { useEffect, useRef, useState } from "react" import * as monaco from "monaco-editor"; @@ -11,6 +11,7 @@ import { Maximize2 } from "lucide-react"; import { prepareArg, securedFetch } from "@/lib/utils"; import { useToast } from "@/components/ui/use-toast"; import { Session } from "next-auth"; +import { VisuallyHidden } from "@radix-ui/react-visually-hidden"; import { Graph } from "../api/graph/model"; import Button from "./ui/Button"; @@ -175,13 +176,23 @@ const FUNCTIONS = [ "vec.cosineDistance", ] -const SUGGESTIONS: monaco.languages.CompletionItem[] = KEYWORDS.map(key => ({ - insertText: key, - label: key, - kind: monaco.languages.CompletionItemKind.Keyword, - range: new monaco.Range(1, 1, 1, 1), - detail: "(keyword)" -})) +const SUGGESTIONS: monaco.languages.CompletionItem[] = [ + ...KEYWORDS.map(key => ({ + insertText: key, + label: key, + kind: monaco.languages.CompletionItemKind.Keyword, + range: new monaco.Range(1, 1, 1, 1), + detail: "(keyword)" + })), + ...FUNCTIONS.map(f => ({ + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + insertText: `${f}(\${0})`, + label: `${f}()`, + kind: monaco.languages.CompletionItemKind.Function, + range: new monaco.Range(1, 1, 1, 1), + detail: "(function)" + })) +] const MAX_HEIGHT = 20 const LINE_HEIGHT = 38 @@ -192,10 +203,10 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre const [query, setQuery] = useState(currentQuery) const placeholderRef = useRef(null) - const [monacoInstance, setMonacoInstance] = useState() - const [sugProvider, setSugProvider] = useState() const [lineNumber, setLineNumber] = useState(1) + const graphIdRef = useRef(graph.Id) const [blur, setBlur] = useState(false) + const [sugDisposed, setSugDisposed] = useState() const { toast } = useToast() const submitQuery = useRef(null) const editorRef = useRef(null) @@ -206,6 +217,14 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre historyCounter: historyQueries.length }) + useEffect(() => { + graphIdRef.current = graph.Id + }, [graph.Id]) + + useEffect(() => () => { + sugDisposed?.dispose() + }, [sugDisposed]) + useEffect(() => { historyRef.current.historyQueries = historyQueries }, [historyQueries, currentQuery]) @@ -254,10 +273,12 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre 'editorSuggestWidget.hoverBackground': '#28283F', }, }); + + monacoI.editor.setTheme('custom-theme') } const fetchSuggestions = async (q: string, detail: string): Promise => { - const result = await securedFetch(`api/graph/${graph.Id}/?query=${prepareArg(q)}&role=${data?.user.role}`, { + const result = await securedFetch(`api/graph/${graphIdRef.current}/?query=${prepareArg(q)}&role=${data?.user.role}`, { method: 'GET', }, toast) @@ -286,44 +307,109 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre })) } - const getSuggestions = async (): Promise => { - const suggestions = await Promise.all([ - ['CALL dbms.procedures() YIELD name as sug', '(function)'], - ['CALL db.propertyKeys() YIELD propertyKey as sug', '(property key)'], - ['CALL db.labels() YIELD label as sug', '(label)'], - ['CALL db.relationshipTypes() YIELD relationshipType as sug', '(relationship type)'] - ].map(([q, detail]) => fetchSuggestions(q, detail))) - - return [...suggestions.flatMap(arr => arr), ...FUNCTIONS.map(f => ({ - insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, - insertText: `${f}(\${0})`, - label: `${f}()`, - kind: monaco.languages.CompletionItemKind.Function, - range: new monaco.Range(1, 1, 1, 1), - detail: "(function)" - }))] - } + const getSuggestions = async () => (await Promise.all([ + fetchSuggestions('CALL dbms.procedures() YIELD name as sug', '(function)'), + fetchSuggestions('CALL db.propertyKeys() YIELD propertyKey as sug', '(property key)'), + fetchSuggestions('CALL db.labels() YIELD label as sug', '(label)'), + fetchSuggestions('CALL db.relationshipTypes() YIELD relationshipType as sug', '(relationship type)') + ])).flat() const addSuggestions = async (monacoI: Monaco) => { - console.log("addSuggestions"); - const suggestions = SUGGESTIONS + const sug = [ + ...SUGGESTIONS, + ...(graphIdRef.current ? await getSuggestions() : []) + ]; + + const functions = sug.filter(({ detail }) => detail === "(function)") + + const namespaces = new Set( + functions + .filter(({ label }) => (label as string).includes(".")) + .map(({ label }) => { + const newNamespaces = (label as string).split(".") + newNamespaces.pop() + return newNamespaces + }).flat() + ) + + monacoI.languages.setMonarchTokensProvider('custom-language', { + tokenizer: { + root: graphIdRef.current ? [ + [new RegExp(`\\b(${Array.from(namespaces.keys()).join('|')})\\b`), "keyword"], + [new RegExp(`\\b(${KEYWORDS.join('|')})\\b`), "keyword"], + [ + new RegExp(`\\b(${functions.map(({ label }) => { + if ((label as string).includes(".")) { + const labels = (label as string).split(".") + return labels[labels.length - 1] + } + return label + }).join('|')})\\b`), + "function" + ], + [/"([^"\\]|\\.)*"/, 'string'], + [/'([^'\\]|\\.)*'/, 'string'], + [/\d+/, 'number'], + [/:(\w+)/, 'type'], + [/\{/, { token: 'delimiter.curly', next: '@bracketCounting' }], + [/\[/, { token: 'delimiter.square', next: '@bracketCounting' }], + [/\(/, { token: 'delimiter.parenthesis', next: '@bracketCounting' }], + ] : [ + [new RegExp(`\\b(${KEYWORDS.join('|')})\\b`), "keyword"], + [/"([^"\\]|\\.)*"/, 'string'], + [/'([^'\\]|\\.)*'/, 'string'], + [/\d+/, 'number'], + [/:(\w+)/, 'type'], + [/\{/, { token: 'delimiter.curly', next: '@bracketCounting' }], + [/\[/, { token: 'delimiter.square', next: '@bracketCounting' }], + [/\(/, { token: 'delimiter.parenthesis', next: '@bracketCounting' }], + ], + bracketCounting: [ + [/\{/, 'delimiter.curly', '@bracketCounting'], + [/\}/, 'delimiter.curly', '@pop'], + [/\[/, 'delimiter.square', '@bracketCounting'], + [/\]/, 'delimiter.square', '@pop'], + [/\(/, 'delimiter.parenthesis', '@bracketCounting'], + [/\)/, 'delimiter.parenthesis', '@pop'], + { include: 'root' } + ], + }, + ignoreCase: true, + }) - if (graph.Id) { - console.log("getSuggestions"); - suggestions.push(...(await getSuggestions())) - } + return sug + } - const functions = suggestions.filter(({ detail }) => detail === "(function)") + const handleEditorWillMount = async (monacoI: Monaco) => { - const namespaces = new Set(functions.filter(({ label }) => (label as string).includes(".")).map(({ label }) => { - const [namespace] = (label as string).split(".") - return namespace - })) + monacoI.languages.register({ id: "custom-language" }) - if (sugProvider) { - sugProvider.dispose() - console.log("dispose"); - } + monacoI.languages.setMonarchTokensProvider('custom-language', { + tokenizer: { + root: [ + [new RegExp(`\\b(${KEYWORDS.join('|')})\\b`), "keyword"], + [/"([^"\\]|\\.)*"/, 'string'], + [/'([^'\\]|\\.)*'/, 'string'], + [/\d+/, 'number'], + [/:(\w+)/, 'type'], + [/\{/, { token: 'delimiter.curly', next: '@bracketCounting' }], + [/\[/, { token: 'delimiter.square', next: '@bracketCounting' }], + [/\(/, { token: 'delimiter.parenthesis', next: '@bracketCounting' }], + ], + bracketCounting: [ + [/\{/, 'delimiter.curly', '@bracketCounting'], + [/\}/, 'delimiter.curly', '@pop'], + [/\[/, 'delimiter.square', '@bracketCounting'], + [/\]/, 'delimiter.square', '@pop'], + [/\(/, 'delimiter.parenthesis', '@bracketCounting'], + [/\)/, 'delimiter.parenthesis', '@pop'], + { include: 'root' } + ], + }, + ignoreCase: true, + }) + + setTheme(monacoI) monacoI.languages.setLanguageConfiguration('custom-language', { brackets: [ @@ -347,111 +433,20 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre ] }); - if (graph.Id) { - monacoI.languages.setMonarchTokensProvider('custom-language', { - tokenizer: { - root: [ - [new RegExp(`\\b(${Array.from(namespaces.keys()).join('|')})\\b`), "keyword"], - [new RegExp(`\\b(${KEYWORDS.join('|')})\\b`), "keyword"], - [ - new RegExp(`\\b(${functions.map(({ label }) => { - if ((label as string).includes(".")) { - const labels = (label as string).split(".") - return labels[labels.length - 1] - } - return label - }).join('|')})\\b`), - "function" - ], - [/"([^"\\]|\\.)*"/, 'string'], - [/'([^'\\]|\\.)*'/, 'string'], - [/\d+/, 'number'], - [/:(\w+)/, 'type'], - [/\{/, { token: 'delimiter.curly', next: '@bracketCounting' }], - [/\[/, { token: 'delimiter.square', next: '@bracketCounting' }], - [/\(/, { token: 'delimiter.parenthesis', next: '@bracketCounting' }], - ], - bracketCounting: [ - [/\{/, 'delimiter.curly', '@bracketCounting'], - [/\}/, 'delimiter.curly', '@pop'], - [/\[/, 'delimiter.square', '@bracketCounting'], - [/\]/, 'delimiter.square', '@pop'], - [/\(/, 'delimiter.parenthesis', '@bracketCounting'], - [/\)/, 'delimiter.parenthesis', '@pop'], - { include: 'root' } - ], - }, - ignoreCase: true, - }) - } else { - monacoI.languages.setMonarchTokensProvider('custom-language', { - tokenizer: { - root: [ - [new RegExp(`\\b(${KEYWORDS.join('|')})\\b`), "keyword"], - [/"([^"\\]|\\.)*"/, 'string'], - [/'([^'\\]|\\.)*'/, 'string'], - [/\d+/, 'number'], - [/:(\w+)/, 'type'], - [/\{/, { token: 'delimiter.curly', next: '@bracketCounting' }], - [/\[/, { token: 'delimiter.square', next: '@bracketCounting' }], - [/\(/, { token: 'delimiter.parenthesis', next: '@bracketCounting' }], - ], - bracketCounting: [ - [/\{/, 'delimiter.curly', '@bracketCounting'], - [/\}/, 'delimiter.curly', '@pop'], - [/\[/, 'delimiter.square', '@bracketCounting'], - [/\]/, 'delimiter.square', '@pop'], - [/\(/, 'delimiter.parenthesis', '@bracketCounting'], - [/\)/, 'delimiter.parenthesis', '@pop'], - { include: 'root' } - ], - }, - ignoreCase: true, - }) - } - - return monacoI.languages.registerCompletionItemProvider("custom-language", { - provideCompletionItems: (model, position) => { + const provider = monacoI.languages.registerCompletionItemProvider("custom-language", { + provideCompletionItems: async (model, position) => { const word = model.getWordUntilPosition(position) const range = new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn) - console.log(suggestions); return { - suggestions: suggestions.map(s => ({ ...s, range })) + suggestions: (await addSuggestions(monacoI)).map(s => ({ ...s, range })) } }, }) - } - - useEffect(() => { - const timeout = setTimeout(async () => { - if (!monacoInstance) return - const provider = await addSuggestions(monacoInstance) - setSugProvider(provider) - }, 5000) - - return () => { - clearTimeout(timeout) - if (sugProvider) { - sugProvider.dispose() - console.log("cleanup dispose"); - } - console.log("cleanup"); - } - }, [graph.Id]) - - const handleEditorWillMount = async (monacoI: Monaco) => { - monacoI.languages.register({ id: "custom-language" }) - - setTheme(monacoI) - - const provider = await addSuggestions(monacoI) - - setSugProvider(provider) - setMonacoInstance(monacoI) + setSugDisposed(provider) } - - const handleEditorDidMount = (e: monaco.editor.IStandaloneCodeEditor, monacoI: Monaco) => { + + const handleEditorDidMount = (e: monaco.editor.IStandaloneCodeEditor) => { editorRef.current = e const updatePlaceholderVisibility = () => { @@ -475,9 +470,6 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre updatePlaceholderVisibility(); - setMonacoInstance(monacoI) - - const isFirstLine = e.createContextKey('isFirstLine', true); // Update the context key value based on the cursor position @@ -586,6 +578,10 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre /> + + + + Date: Thu, 30 Jan 2025 12:31:42 +0200 Subject: [PATCH 38/65] commit --- app/components/DialogComponent.tsx | 2 +- app/globals.css | 30 +++------------------------- public/ColorLogo.svg | 23 --------------------- public/fonts/obviously_narrow.woff | Bin 73787 -> 0 bytes public/fonts/obviously_narrow.woff2 | Bin 56144 -> 0 bytes public/fonts/oswald.ttf | Bin 169108 -> 0 bytes 6 files changed, 4 insertions(+), 51 deletions(-) delete mode 100644 public/ColorLogo.svg delete mode 100644 public/fonts/obviously_narrow.woff delete mode 100644 public/fonts/obviously_narrow.woff2 delete mode 100644 public/fonts/oswald.ttf diff --git a/app/components/DialogComponent.tsx b/app/components/DialogComponent.tsx index 3ac83f6e..eb0c81f9 100644 --- a/app/components/DialogComponent.tsx +++ b/app/components/DialogComponent.tsx @@ -33,7 +33,7 @@ export default function DialogComponent({ - {title} + {title} { diff --git a/app/globals.css b/app/globals.css index 830ec076..c60e4061 100644 --- a/app/globals.css +++ b/app/globals.css @@ -31,15 +31,9 @@ @layer components { @font-face { - font-family: 'Fira Code'; - src: url('/fonts/fira_code.ttf') format('truetype'); - font-display: swap; - } - - @font-face { - font-family: 'Oswald'; - src: url('/fonts/oswald.ttf') format('truetype'); - font-display: swap; + font-family: 'FiraCode'; + src: url('/public/fonts/fira_code.ttf') format('truetype'); + font-display: block; } .tabs-trigger { @@ -51,11 +45,6 @@ -ms-overflow-style: none; } - .hide-scrollbar { - scrollbar-width: none; - -ms-overflow-style: none; - } - .Page { @apply h-full w-full flex flex-col bg-background; } @@ -80,14 +69,6 @@ align-items: center; justify-content: center; } - - .Secondary { - border-image: linear-gradient(90deg, #EC806C 0%, #B66EBD 43.41%, #7568F2 100%) 1; - } - - .monaco-placeholder { - @apply text-[#666] absolute select-none top-2 left-2; - } } input[type="number"]::-webkit-outer-spin-button, @@ -109,11 +90,6 @@ input[type="number"] { font-family: font-FiraCode, 'monospace'; } -.Title { - font-family: font-Oswald, 'monospace'; -} - - ::-webkit-scrollbar { width: 20px; height: 20px; diff --git a/public/ColorLogo.svg b/public/ColorLogo.svg deleted file mode 100644 index 3632ebf0..00000000 --- a/public/ColorLogo.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/fonts/obviously_narrow.woff b/public/fonts/obviously_narrow.woff deleted file mode 100644 index 7498e5b6e02c042884d956293e20e4600b77f09f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73787 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 z 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 diff --git a/public/fonts/obviously_narrow.woff2 b/public/fonts/obviously_narrow.woff2 deleted file mode 100644 index f2dac670c9baf50925c4c3edec4bcdcb6a583bf5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56144 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 diff --git a/public/fonts/oswald.ttf b/public/fonts/oswald.ttf deleted file mode 100644 index 938e912a94e4b319f76c7fa42e288b9ce20c51d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169108 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 From 2a65918f9a119606a746d563212cc0a4aa16370d Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Sun, 2 Feb 2025 11:13:50 +0200 Subject: [PATCH 39/65] update ci image and tests update falkordb image in ci remove unnecessary logging update settings config tests --- .github/workflows/playwright.yml | 2 +- e2e/logic/api/apiCalls.ts | 1 - e2e/tests/settingsConfig.spec.ts | 69 ++++++++++++++------------------ e2e/tests/settingsUsers.spec.ts | 2 +- 4 files changed, 31 insertions(+), 43 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index adfffbb3..e88f6b09 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -16,7 +16,7 @@ jobs: services: falkordb: - image: falkordb/falkordb:v4.4.1 + image: falkordb/falkordb:latest ports: - 6379:6379 diff --git a/e2e/logic/api/apiCalls.ts b/e2e/logic/api/apiCalls.ts index 1697766c..26881060 100644 --- a/e2e/logic/api/apiCalls.ts +++ b/e2e/logic/api/apiCalls.ts @@ -35,7 +35,6 @@ export default class ApiCalls { async getSettingsRoleValue(roleName: string, data?: any): Promise { const result = await getRequest(urls.api.settingsConfig + roleName, data) const jsonData = await result.json(); - console.log("api calls res:", jsonData, " role: ", roleName); return jsonData } diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index 8f753fdc..6eea15bb 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -138,7 +138,6 @@ test.describe('Settings Tests', () => { await apiCall.modifySettingsRole(roles.maxInfoQueries, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.maxInfoQueries) - console.log(value); expect(value === input).toBe(expected); if (index === Data.inputDataAcceptsZero.length - 1) { await apiCall.modifySettingsRole(roles.maxInfoQueries, "1000"); @@ -146,98 +145,88 @@ test.describe('Settings Tests', () => { }); }) - test(`@admin Modify maxQueuedQueries via UI validation via API: Input value: 24`, async () => { + test(`@admin Modify maxQueuedQueries via UI validation via API: Input value: ${Data.roleModificationData[0].input}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) - await settingsConfigPage.modifyRoleValue(roles.maxQueuedQueries, "24") + await settingsConfigPage.modifyRoleValue(roles.maxQueuedQueries, Data.roleModificationData[0].input) const apiCall = new ApiCalls() let value = String((await apiCall.getSettingsRoleValue(roles.maxQueuedQueries)).config[1]); - expect(value === "24").toBe(true); + expect(value === Data.roleModificationData[0].input).toBe(true); await apiCall.modifySettingsRole(roles.maxQueuedQueries, "25") }); - test(`@admin Modify TimeOut via UI validation via API: Input value: 1001`, async () => { + test(`@admin Modify TimeOut via UI validation via API: Input value: ${Data.roleModificationData[1].input}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) - await settingsConfigPage.modifyRoleValue(roles.TimeOut, "1001") + await settingsConfigPage.modifyRoleValue(roles.TimeOut, Data.roleModificationData[1].input) const apiCall = new ApiCalls() let value = String((await apiCall.getSettingsRoleValue(roles.TimeOut)).config[1]); - expect(value === "1001").toBe(true); + expect(value === Data.roleModificationData[1].input).toBe(true); await apiCall.modifySettingsRole(roles.TimeOut, "1000") }); - test(`@admin Modify maxTimeOut via UI validation via API: Input value: 1`, async () => { + test(`@admin Modify maxTimeOut via UI validation via API: Input value: ${Data.roleModificationData[2].input}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) - await settingsConfigPage.modifyRoleValue(roles.maxTimeOut, "1") + await settingsConfigPage.modifyRoleValue(roles.maxTimeOut, Data.roleModificationData[2].input) const apiCall = new ApiCalls() let value = String((await apiCall.getSettingsRoleValue(roles.maxTimeOut)).config[1]); - expect(value === "1").toBe(true); + expect(value === Data.roleModificationData[2].input).toBe(true); await apiCall.modifySettingsRole(roles.maxTimeOut, "0") }); - test(`@admin Modify defaultTimeOut via UI validation via API: Input value: 1`, async () => { + test(`@admin Modify defaultTimeOut via UI validation via API: Input value: ${Data.roleModificationData[3].input}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) - await settingsConfigPage.modifyRoleValue(roles.defaultTimeOut, "1") + await settingsConfigPage.modifyRoleValue(roles.defaultTimeOut, Data.roleModificationData[3].input) const apiCall = new ApiCalls() let value = String((await apiCall.getSettingsRoleValue(roles.defaultTimeOut)).config[1]); - expect(value === "1").toBe(true); + expect(value === Data.roleModificationData[3].input).toBe(true); await apiCall.modifySettingsRole(roles.defaultTimeOut, "0") }); - test(`@admin Modify resultSetSize via UI validation via API: Input value: 10001`, async () => { + test(`@admin Modify resultSetSize via UI validation via API: Input value: ${Data.roleModificationData[4].input}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) - await settingsConfigPage.modifyRoleValue(roles.resultSetSize, "10001") + await settingsConfigPage.modifyRoleValue(roles.resultSetSize, Data.roleModificationData[4].input) const apiCall = new ApiCalls() let value = String((await apiCall.getSettingsRoleValue(roles.resultSetSize)).config[1]); - expect(value === "10001").toBe(true); + expect(value === Data.roleModificationData[4].input).toBe(true); await apiCall.modifySettingsRole(roles.resultSetSize, "10000") }); - test(`@admin Modify queryMemCapacity via UI validation via API: Input value: 1`, async () => { + test(`@admin Modify queryMemCapacity via UI validation via API: Input value: ${Data.roleModificationData[5].input}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) - await settingsConfigPage.modifyRoleValue(roles.queryMemCapacity, "1") + await settingsConfigPage.modifyRoleValue(roles.queryMemCapacity, Data.roleModificationData[5].input) const apiCall = new ApiCalls() let value = String((await apiCall.getSettingsRoleValue(roles.queryMemCapacity)).config[1]); - expect(value === "1").toBe(true); + expect(value === Data.roleModificationData[5].input).toBe(true); await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") }); - test(`@admin Modify vKeyMaxEntityCount via UI validation via API: Input value: 100001`, async () => { + test(`@admin Modify vKeyMaxEntityCount via UI validation via API: Input value: ${Data.roleModificationData[6].input}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) - await settingsConfigPage.modifyRoleValue(roles.vKeyMaxEntityCount, "100001") + await settingsConfigPage.modifyRoleValue(roles.vKeyMaxEntityCount, Data.roleModificationData[6].input) const apiCall = new ApiCalls() let value = String((await apiCall.getSettingsRoleValue(roles.vKeyMaxEntityCount)).config[1]); - expect(value === "100001").toBe(true); + expect(value === Data.roleModificationData[6].input).toBe(true); await apiCall.modifySettingsRole(roles.vKeyMaxEntityCount, "100000") }); - test(`@admin Modify cmdInfo via UI validation via API: Input value: no`, async () => { + test(`@admin Modify cmdInfo via UI validation via API: Input value: ${Data.roleModificationData[7].input}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) - await settingsConfigPage.modifyRoleValue(roles.cmdInfo, "no") + await settingsConfigPage.modifyRoleValue(roles.cmdInfo, Data.roleModificationData[7].input) const apiCall = new ApiCalls() let value = String((await apiCall.getSettingsRoleValue(roles.cmdInfo)).config[1]); value = value === '1' ? 'yes' : value === '0' ? 'no' : value; - expect(value === "no").toBe(true); + expect(value === Data.roleModificationData[7].input).toBe(true); await apiCall.modifySettingsRole(roles.cmdInfo, "yes") }); - test(`@admin Modify maxInfoQueries via UI validation via API: Input value: 999`, async () => { + test(`@admin Modify maxInfoQueries via UI validation via API: Input value: ${Data.roleModificationData[8].input}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) - await settingsConfigPage.modifyRoleValue(roles.maxInfoQueries, "999") + await settingsConfigPage.modifyRoleValue(roles.maxInfoQueries, Data.roleModificationData[8].input) await settingsConfigPage.refreshPage(); await settingsConfigPage.scrollToBottomInTable(); - const res = await settingsConfigPage.getRoleContentValue(roles.maxInfoQueries); - console.log("ui value: ", res); - - await new Promise(resolve => { setTimeout(resolve, 3000) }); + await settingsConfigPage.getRoleContentValue(roles.maxInfoQueries); const apiCall = new ApiCalls() - let value; - for (let i = 0; i < 5; i++) { - value = String((await apiCall.getSettingsRoleValue(roles.maxInfoQueries)).config[1]); - if (value === "999") break; - await new Promise(resolve => setTimeout(resolve, 1500)); - } - - console.log("api value:", value); - expect(value).toBe("999"); + let value = String((await apiCall.getSettingsRoleValue(roles.maxInfoQueries)).config[1]); + expect(value).toBe(Data.roleModificationData[8].input); await apiCall.modifySettingsRole(roles.maxInfoQueries, "1000"); }); diff --git a/e2e/tests/settingsUsers.spec.ts b/e2e/tests/settingsUsers.spec.ts index 71953bb8..166ed73f 100644 --- a/e2e/tests/settingsUsers.spec.ts +++ b/e2e/tests/settingsUsers.spec.ts @@ -146,7 +146,7 @@ test.describe('Settings Tests', () => { const User = users.result.find(u => u.username === username); expect(User?.username === username).toBe(true) }) - // fail tests + test(`@admin API Test: without passing a username, Attempt to add a user and validate the user was not added`, async () => { const apiCall = new ApiCalls() const username = '' From c44c3379fb5fd98c14a74f7ea46f23f19d88f09f Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Sun, 2 Feb 2025 11:41:03 +0200 Subject: [PATCH 40/65] commit --- app/components/CreateGraph.tsx | 7 ++++--- app/schema/page.tsx | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/components/CreateGraph.tsx b/app/components/CreateGraph.tsx index 8cb14894..bcd2e807 100644 --- a/app/components/CreateGraph.tsx +++ b/app/components/CreateGraph.tsx @@ -36,7 +36,8 @@ export default function CreateGraph({ const handleCreateGraph = async (e: React.FormEvent) => { e.preventDefault() - if (!graphName) { + const name = graphName.trim() + if (!name) { toast({ title: "Error", description: "Graph name cannot be empty", @@ -45,13 +46,13 @@ export default function CreateGraph({ return } const q = 'RETURN 1' - const result = await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${prepareArg(q)}`, { + const result = await securedFetch(`api/graph/${prepareArg(name)}/?query=${prepareArg(q)}`, { method: "GET", }, toast) if (!result.ok) return - onSetGraphName(graphName) + onSetGraphName(name) setGraphName("") setOpen(false) } diff --git a/app/schema/page.tsx b/app/schema/page.tsx index 0b57ea4f..fd3a4400 100644 --- a/app/schema/page.tsx +++ b/app/schema/page.tsx @@ -56,7 +56,7 @@ export default function Page() { return (
-
+
Date: Sun, 2 Feb 2025 12:04:04 +0200 Subject: [PATCH 41/65] Add validation for MAX_INFO_QUERIES configuration limit --- app/settings/Configurations.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/settings/Configurations.tsx b/app/settings/Configurations.tsx index 689937c2..a3404ed4 100644 --- a/app/settings/Configurations.tsx +++ b/app/settings/Configurations.tsx @@ -138,6 +138,17 @@ export default function Configurations() { return false; } + if (name === "MAX_INFO_QUERIES") { + if (Number(value) > 1000) { + toast({ + title: "Error", + description: "Value must be less than 1000", + variant: "destructive" + }); + return false; + } + } + const result = await securedFetch( `api/graph/?config=${prepareArg(name)}&value=${prepareArg(value)}`, { method: 'POST' }, @@ -145,7 +156,7 @@ export default function Configurations() { ); if (!result.ok) return false; - + const configToUpdate = configs.find(row => row.cells[0].value === name); const oldValue = configToUpdate?.cells[2].value; From 55a82654f8b5ea499f94c5e78c495ce483b993a0 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Sun, 2 Feb 2025 14:43:45 +0200 Subject: [PATCH 42/65] commit --- app/components/Header.tsx | 8 +++-- app/settings/Configurations.tsx | 53 +++++++++------------------------ public/ColorLogo.svg | 23 ++++++++++++++ 3 files changed, 42 insertions(+), 42 deletions(-) create mode 100644 public/ColorLogo.svg diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 16849a84..75a78504 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -65,7 +65,7 @@ export default function Header({ onSetGraphName }: Props) { - + e.preventDefault()} className="gap-2 bg-foreground">

Help

@@ -108,8 +108,10 @@ export default function Header({ onSetGraphName }: Props) { - - + + + +

We Make AI Reliable

diff --git a/app/settings/Configurations.tsx b/app/settings/Configurations.tsx index 689937c2..da1e20ab 100644 --- a/app/settings/Configurations.tsx +++ b/app/settings/Configurations.tsx @@ -28,97 +28,72 @@ const disableRunTimeConfigs = new Set([ const Configs: Config[] = [ { name: "THREAD_COUNT", - description: `The number of threads in FalkorDB’s thread pool. - This is equivalent to the maximum number of queries that can be processed concurrently.`, + description: "The number of threads in FalkorDB's thread pool, representing the maximum number of queries that can be processed concurrently.", value: "" }, { name: "CACHE_SIZE", - description: `The max number of queries for FalkorDB to cache. - When a new query is encountered and the cache is full, meaning the cache has reached the size of CACHE_SIZE, it will evict the least recently used (LRU) entry.`, + description: "The maximum number of queries FalkorDB can cache. If the cache reaches its size limit (CACHE_SIZE) and a new query is encountered, the least recently used (LRU) entry is removed to make space.", value: "" }, { name: "OMP_THREAD_COUNT", - description: `The maximum number of threads that OpenMP may use for computation per query. - These threads are used for parallelizing GraphBLAS computations, so may be considered to control concurrency within the execution of individual queries.`, + description: "The maximum number of threads OpenMP can use per query for computation. These threads parallelize GraphBLAS operations, enabling concurrency within individual query executions.", value: "" }, { name: "NODE_CREATION_BUFFER", - description: `The node creation buffer is the number of new nodes that can be created without resizing matrices. - For example, when set to 16,384, the matrices will have extra space for 16,384 nodes upon creation. - Whenever the extra space is depleted, the matrices’ size will increase by 16,384. - Reducing this value will reduce memory consumption, but cause performance degradation due to the increased frequency of matrix resizes. - Conversely, increasing it might improve performance for write-heavy workloads but will increase memory consumption. - If the passed argument was not a power of 2, it will be rounded to the next-greatest power of 2 to improve memory alignment.`, + description: "Specifies the number of new nodes that can be created without resizing matrices. For example, if set to 16,384, matrices will initially have space for 16,384 nodes. When this space is exhausted, the matrix size increases by the same amount. Lower values reduce memory usage but may degrade performance due to frequent resizes, while higher values can improve performance for write-heavy tasks but increase memory consumption. Non-power-of-two values are rounded up to the nearest power of 2 for better memory alignment.", value: "" }, { name: "BOLT_PORT", - description: `The Bolt port configuration determines the port number on which FalkorDB handles the
bolt protocol`, + description: "Defines the port number on which FalkorDB processes Bolt protocol connections.", value: "" }, { name: "MAX_QUEUED_QUERIES", - description: `Setting the maximum number of queued queries allows the server to reject incoming queries with the error message Max pending queries exceeded. - This reduces the memory overhead of pending queries on an overloaded server and avoids congestion when the server processes its backlog of queries.`, + description: "Sets the maximum number of queries that can be queued. When this limit is reached, new queries are rejected with the error message \"Max pending queries exceeded.\" This reduces memory usage and prevents congestion during query backlogs.", value: "" }, { name: "TIMEOUT", - description: `(Deprecated in FalkorDB v2.10 It is recommended to use TIMEOUT_MAX and TIMEOUT_DEFAULT instead) - The TIMEOUT configuration parameter specifies the default maximal execution time for read queries, in milliseconds. - Write queries do not timeout. - When a read query execution time exceeds the maximal execution time, the query is aborted and the query reply is (error) Query timed out. - The TIMEOUT query parameter of the GRAPH.QUERY, GRAPH.RO_QUERY, and GRAPH.PROFILE commands can be used to override this value.`, + description: "(Deprecated in FalkorDB v2.10; use TIMEOUT_MAX and TIMEOUT_DEFAULT instead) Specifies the maximum execution time for read queries, in milliseconds. If a read query exceeds this limit, it is aborted with the error message \"Query timed out.\" Write queries are not subject to this timeout.", value: "" }, { name: "TIMEOUT_MAX", - description: `(Since RedisGraph v2.10) The TIMEOUT_MAX configuration parameter specifies the maximum execution time for both read and write queries, in milliseconds. - The TIMEOUT query parameter value of the GRAPH.QUERY, GRAPH.RO_QUERY, and GRAPH.PROFILE commands cannot exceed the TIMEOUT_MAX value (the command would abort with a (error) The query TIMEOUT parameter value cannot exceed the TIMEOUT_MAX configuration parameter value reply). - Similarly, the TIMEOUT_DEFAULT configuration parameter cannot exceed the TIMEOUT_MAX value. - When a query execution time exceeds the maximal execution time, the query is aborted and the query reply is (error) Query timed out. - For a write query - any change to the graph is undone (which may take additional time).`, + description: "(Since RedisGraph v2.10) Specifies the maximum execution time for both read and write queries, in milliseconds. The TIMEOUT parameter in GRAPH.QUERY, GRAPH.RO_QUERY, and GRAPH.PROFILE commands cannot exceed this value. Queries exceeding this time limit are aborted with the error \"Query timed out.\" For write queries, any graph changes are undone, which may take additional time.", value: "" }, { name: "TIMEOUT_DEFAULT", - description: `(Since RedisGraph v2.10) The TIMEOUT_DEFAULT configuration parameter specifies the default maximal execution time for both read and write queries, in milliseconds. - For a given query, this default maximal execution time can be overridden by the TIMEOUT query parameter of the GRAPH.QUERY, GRAPH.RO_QUERY, and GRAPH.PROFILE commands. - However, a query execution time cannot exceed TIMEOUT_MAX.`, + description: "(Since RedisGraph v2.10) Sets the default maximum execution time for both read and write queries, in milliseconds. This default can be overridden by the TIMEOUT parameter in GRAPH.QUERY, GRAPH.RO_QUERY, and GRAPH.PROFILE commands but cannot exceed TIMEOUT_MAX.", value: "" }, { name: "RESULTSET_SIZE", - description: `Result set size is a limit on the number of records that should be returned by any query. - This can be a valuable safeguard against incurring a heavy IO load while running queries with unknown results.`, + description: "Limits the number of records returned by any query. This helps prevent heavy I/O loads when running queries with unpredictable results.", value: "" }, { name: "QUERY_MEM_CAPACITY", - description: `Setting the memory capacity of a query allows the server to kill queries that are consuming too much memory and return with the error message Query's mem consumption exceeded capacity. - This helps to avoid scenarios when the server becomes unresponsive due to an unbounded query exhausting system resources. - The configuration argument is the maximum number of bytes that can be allocated by any single query.`, + description: "Specifies the maximum memory, in bytes, that a single query can use. Queries exceeding this limit are terminated with the error \"Query's memory consumption exceeded capacity,\" helping prevent server unresponsiveness.", value: "" }, { name: "VKEY_MAX_ENTITY_COUNT", - description: `To lower the time Redis is blocked when replicating large graphs, FalkorDB serializes the graph in a number of virtual keys. - One virtual key is created for every N graph entities, where N is the value defined by this configuration.`, + description: "Reduces the time Redis is blocked during large graph replication by serializing the graph into multiple virtual keys. One virtual key is created for every N graph entities, where N is defined by this configuration.", value: "" }, { name: "CMD_INFO", - description: `An on/off toggle for the GRAPH.INFO command. - Disabling this command may increase performance and lower the memory usage and these are the main reasons for it to be disabled - It’s valid values are ‘yes’ and ‘no’ (i.e., on and off).`, + description: "Enables or disables the GRAPH.INFO command. Disabling this command can improve performance and reduce memory usage. Acceptable values are \"yes\" (enabled) and \"no\" (disabled).", value: "" }, { name: "MAX_INFO_QUERIES", - description: `A limit for the number of previously executed queries stored in the telemetry stream.`, + description: "Sets a limit on the number of previously executed queries stored in the telemetry stream.", value: "" }, ] diff --git a/public/ColorLogo.svg b/public/ColorLogo.svg new file mode 100644 index 00000000..3632ebf0 --- /dev/null +++ b/public/ColorLogo.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + From b5bda9cd9c53a5198611d601a8c672456475c323 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 3 Feb 2025 13:34:39 +0200 Subject: [PATCH 43/65] commit --- app/api/graph/model.ts | 54 +++++++++++++------------------ app/components/ForceGraph.tsx | 10 ++---- app/components/Header.tsx | 4 +-- app/components/TableComponent.tsx | 2 -- app/globals.css | 23 +++---------- app/graph/GraphView.tsx | 4 ++- app/graph/Tutorial.tsx | 8 +++-- app/graph/View.tsx | 39 ++++++++++++---------- app/graph/labels.tsx | 4 +-- app/graph/page.tsx | 1 + app/settings/page.tsx | 4 +-- app/settings/users/Users.tsx | 2 -- lib/utils.ts | 53 +++++++++++++++++++++++------- 13 files changed, 107 insertions(+), 101 deletions(-) diff --git a/app/api/graph/model.ts b/app/api/graph/model.ts index e3f22157..eb741342 100644 --- a/app/api/graph/model.ts +++ b/app/api/graph/model.ts @@ -87,10 +87,10 @@ export type DataRow = { export type Data = DataRow[] export const DEFAULT_COLORS = [ - "#7466FF", - "#FF66B3", - "#FF804D", - "#80E6E6" + "hsl(246, 100%, 70%)", + "hsl(330, 100%, 70%)", + "hsl(20, 100%, 65%)", + "hsl(180, 66%, 70%)" ] export interface Query { @@ -152,7 +152,7 @@ export class Graph { this.labelsMap = labelsMap; this.nodesMap = nodesMap; this.linksMap = edgesMap; - this.COLORS_ORDER_VALUE = colors || DEFAULT_COLORS + this.COLORS_ORDER_VALUE = [...(colors || DEFAULT_COLORS)] } get Id(): string { @@ -272,30 +272,6 @@ export class Graph { Object.entries(cell.properties).forEach(([key, value]) => { currentNode.data[key] = isSchema ? getSchemaValue(value) : value; }); - - // remove empty category if there are no more empty nodes category - if (Array.from(this.nodesMap.values()).every(n => n.category.some(c => c !== ""))) { - this.categories = this.categories.filter(l => l.name !== "").map(c => { - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - if (this.categoriesMap.get("")?.index! < c.index) { - c.index -= 1 - this.categoriesMap.get(c.name)!.index = c.index - } - return c - }) - this.labels = this.labels.map(l => { - if (this.labelsMap.get(l.name)!.index! < l.index) { - l.index -= 1 - this.labelsMap.get(l.name)!.index = l.index - } - return l - }) - this.categoriesMap.delete("") - this.colorIndex -= 1 - this.elements.nodes.forEach(n => { - n.color = this.getCategoryColorValue(this.categoriesMap.get(n.category[0])?.index) - }) - } } return currentNode @@ -463,6 +439,13 @@ export class Graph { link.curve = curve * 0.1 }) + + // remove empty category if there are no more empty nodes category + if (Array.from(this.nodesMap.values()).every(n => n.category.some(c => c !== ""))) { + this.categories = this.categories.filter(c => c.name !== "") + this.categoriesMap.delete("") + } + return newElements } @@ -548,7 +531,14 @@ export class Graph { } } - public getCategoryColorValue(index = 0): string { - return this.COLORS_ORDER_VALUE[index % this.COLORS_ORDER_VALUE.length] + public getCategoryColorValue(index = 0) { + if (index < this.COLORS_ORDER_VALUE.length) { + return this.COLORS_ORDER_VALUE[index]; + } + + const newColor = `hsl(${(index - 4) * 20}, 100%, 70%)` + this.COLORS_ORDER_VALUE.push(newColor) + DEFAULT_COLORS.push(newColor) + return newColor } -} \ No newline at end of file +} diff --git a/app/components/ForceGraph.tsx b/app/components/ForceGraph.tsx index 11908efb..32791c45 100644 --- a/app/components/ForceGraph.tsx +++ b/app/components/ForceGraph.tsx @@ -6,7 +6,7 @@ import { Dispatch, RefObject, SetStateAction, useEffect, useRef, useState } from "react" import ForceGraph2D from "react-force-graph-2d" -import { lightenColor, securedFetch } from "@/lib/utils" +import { securedFetch } from "@/lib/utils" import { useToast } from "@/components/ui/use-toast" import { Graph, GraphData, Link, Node } from "../api/graph/model" @@ -302,12 +302,8 @@ export default function ForceGraph({ cooldownTime={2000} linkDirectionalArrowRelPos={1} linkDirectionalArrowLength={(link) => link.source.id === link.target.id ? 0 : 2} - linkDirectionalArrowColor={(link) => link.id === selectedElement?.id || link.id === hoverElement?.id - ? link.color - : lightenColor(link.color)} - linkColor={(link) => link.id === selectedElement?.id || link.id === hoverElement?.id - ? link.color - : lightenColor(link.color)} + linkDirectionalArrowColor={(link) => link.color} + linkColor={(link) => link.color} />
) diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 75a78504..2fa3b9e5 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -41,12 +41,12 @@ export default function Header({ onSetGraphName }: Props) {
diff --git a/app/components/TableComponent.tsx b/app/components/TableComponent.tsx index 6d704444..66030190 100644 --- a/app/components/TableComponent.tsx +++ b/app/components/TableComponent.tsx @@ -50,8 +50,6 @@ export default function TableComponent({ headers, rows, children, setRows, optio setNewValue(value) } - console.log(rows) - return (
diff --git a/app/globals.css b/app/globals.css index 36d229df..829b728c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -32,12 +32,12 @@ @font-face { font-family: 'FiraCode'; - src: url('/public/fonts/fira_code.ttf') format('truetype'); - font-display: block; + src: url('/fonts/fira_code.ttf') format('truetype'); + font-display: swap; } .tabs-trigger { - @apply !p-2 rounded-lg data-[state=active]:bg-foreground data-[state=active]:!text-white !text-white; + @apply !p-2 rounded-lg data-[state=active]:bg-foreground data-[state=active]:!text-white text-gray-500; } .hide-scrollbar { @@ -67,23 +67,8 @@ } } -input[type="number"]::-webkit-outer-spin-button, -input[type="number"]::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -input[type="number"] { - -moz-appearance: textfield; - -} - -.monaco-editor .suggest-widget { - margin-top: 0.5rem; -} - * { - font-family: font-FiraCode, 'monospace'; + font-family: 'FiraCode', 'Arial', monospace !important; } ::-webkit-scrollbar { diff --git a/app/graph/GraphView.tsx b/app/graph/GraphView.tsx index fa87af21..2de91473 100644 --- a/app/graph/GraphView.tsx +++ b/app/graph/GraphView.tsx @@ -24,13 +24,14 @@ import TableView from "./TableView"; const ForceGraph = dynamic(() => import("../components/ForceGraph"), { ssr: false }); const EditorComponent = dynamic(() => import("../components/EditorComponent"), { ssr: false }) -function GraphView({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, fetchCount, session }: { +function GraphView({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, setHistoryQueries, fetchCount, session }: { graph: Graph selectedElement: Node | Link | undefined setSelectedElement: Dispatch> runQuery: (query: string) => Promise historyQuery: string historyQueries: string[] + setHistoryQueries: (queries: string[]) => void fetchCount: () => void session: Session | null }) { @@ -224,6 +225,7 @@ function GraphView({ graph, selectedElement, setSelectedElement, runQuery, histo maximize={maximize} currentQuery={query} historyQueries={historyQueries} + setHistoryQueries={setHistoryQueries} runQuery={handleRunQuery} setCurrentQuery={setQuery} data={session} diff --git a/app/graph/Tutorial.tsx b/app/graph/Tutorial.tsx index f907fd9c..0fb0bc4d 100644 --- a/app/graph/Tutorial.tsx +++ b/app/graph/Tutorial.tsx @@ -33,7 +33,7 @@ export default function Tutorial({ onSetGraphName }: Props) { - +
@@ -42,8 +42,10 @@ export default function Tutorial({ onSetGraphName }: Props) {

Our Browser allows you to visualize, manipulate and explore your data.

- -

Easily interact with your data in our adaptive force canvas

+ +

Interact with your data with a force-directed layout, + with features including zoom, pan, + node-drag and interactive node/link hover and click events

Configure or export your graph with ease from the control center

diff --git a/app/graph/View.tsx b/app/graph/View.tsx index 481775c4..39fe7a66 100644 --- a/app/graph/View.tsx +++ b/app/graph/View.tsx @@ -1,8 +1,8 @@ /* eslint-disable no-param-reassign */ -import { Check, ChevronDown, ChevronUp, FileCheck2, PlusCircle, RotateCcw, Trash2, X } from "lucide-react" +import { Check, ChevronDown, ChevronUp, FileCheck2, Pencil, PlusCircle, RotateCcw, Trash2, X } from "lucide-react" import { useEffect, useState } from "react" -import { cn } from "@/lib/utils" +import { cn, rgbToHSL } from "@/lib/utils" import { DEFAULT_COLORS, Graph } from "../api/graph/model" import Button from "../components/ui/Button" import DialogComponent from "../components/DialogComponent" @@ -91,15 +91,16 @@ export default function View({ graph, setGraph, selectedValue }: { ref={ref => ref?.focus()} value={editable === c ? editable : newColor} onChange={(e) => { + const newHslColor = rgbToHSL(e.target.value); setColorsArr(prev => { const newArr = [...prev]; - newArr[i] = e.target.value; + newArr[i] = newHslColor; return newArr; }); if (editable === c) { - setEditable(e.target.value) + setEditable(newHslColor) } else { - setNewColor(e.target.value); + setNewColor(newHslColor); } }} onKeyDown={(e) => { @@ -112,10 +113,7 @@ export default function View({ graph, setGraph, selectedValue }: { : <>
-
: hover === c && - +
+ + +
} )) diff --git a/app/graph/labels.tsx b/app/graph/labels.tsx index e2d245b6..23e97a79 100644 --- a/app/graph/labels.tsx +++ b/app/graph/labels.tsx @@ -1,5 +1,5 @@ import { useRef, useState } from "react"; -import { cn, lightenColor } from "@/lib/utils"; +import { cn } from "@/lib/utils"; import { ChevronDown, ChevronUp } from "lucide-react"; import { Category, Graph } from "../api/graph/model"; import Button from "../components/ui/Button"; @@ -57,7 +57,7 @@ export default function Labels({ graph, categories, onClick, label, className = setReload(prev => !prev) }} > -
+
)) diff --git a/app/graph/page.tsx b/app/graph/page.tsx index 7e89fdb7..ad47ac94 100644 --- a/app/graph/page.tsx +++ b/app/graph/page.tsx @@ -129,6 +129,7 @@ export default function Page() { runQuery={runQuery} historyQuery={historyQuery} historyQueries={queries.map(({ text }) => text)} + setHistoryQueries={(queriesArr) => setQueries(queries.map((query, i) => ({ text: queriesArr[i], metadata: query.metadata } as Query)))} fetchCount={fetchCount} session={session} /> diff --git a/app/settings/page.tsx b/app/settings/page.tsx index 7cd2c7f9..622b3100 100644 --- a/app/settings/page.tsx +++ b/app/settings/page.tsx @@ -35,12 +35,12 @@ export default function Settings() {

Settings

diff --git a/app/components/TableComponent.tsx b/app/components/TableComponent.tsx index 66030190..6d704444 100644 --- a/app/components/TableComponent.tsx +++ b/app/components/TableComponent.tsx @@ -50,6 +50,8 @@ export default function TableComponent({ headers, rows, children, setRows, optio setNewValue(value) } + console.log(rows) + return (
diff --git a/app/globals.css b/app/globals.css index 829b728c..36d229df 100644 --- a/app/globals.css +++ b/app/globals.css @@ -32,12 +32,12 @@ @font-face { font-family: 'FiraCode'; - src: url('/fonts/fira_code.ttf') format('truetype'); - font-display: swap; + src: url('/public/fonts/fira_code.ttf') format('truetype'); + font-display: block; } .tabs-trigger { - @apply !p-2 rounded-lg data-[state=active]:bg-foreground data-[state=active]:!text-white text-gray-500; + @apply !p-2 rounded-lg data-[state=active]:bg-foreground data-[state=active]:!text-white !text-white; } .hide-scrollbar { @@ -67,8 +67,23 @@ } } +input[type="number"]::-webkit-outer-spin-button, +input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type="number"] { + -moz-appearance: textfield; + +} + +.monaco-editor .suggest-widget { + margin-top: 0.5rem; +} + * { - font-family: 'FiraCode', 'Arial', monospace !important; + font-family: font-FiraCode, 'monospace'; } ::-webkit-scrollbar { diff --git a/app/graph/GraphView.tsx b/app/graph/GraphView.tsx index 2de91473..fa87af21 100644 --- a/app/graph/GraphView.tsx +++ b/app/graph/GraphView.tsx @@ -24,14 +24,13 @@ import TableView from "./TableView"; const ForceGraph = dynamic(() => import("../components/ForceGraph"), { ssr: false }); const EditorComponent = dynamic(() => import("../components/EditorComponent"), { ssr: false }) -function GraphView({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, setHistoryQueries, fetchCount, session }: { +function GraphView({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, fetchCount, session }: { graph: Graph selectedElement: Node | Link | undefined setSelectedElement: Dispatch> runQuery: (query: string) => Promise historyQuery: string historyQueries: string[] - setHistoryQueries: (queries: string[]) => void fetchCount: () => void session: Session | null }) { @@ -225,7 +224,6 @@ function GraphView({ graph, selectedElement, setSelectedElement, runQuery, histo maximize={maximize} currentQuery={query} historyQueries={historyQueries} - setHistoryQueries={setHistoryQueries} runQuery={handleRunQuery} setCurrentQuery={setQuery} data={session} diff --git a/app/graph/Tutorial.tsx b/app/graph/Tutorial.tsx index 0fb0bc4d..f907fd9c 100644 --- a/app/graph/Tutorial.tsx +++ b/app/graph/Tutorial.tsx @@ -33,7 +33,7 @@ export default function Tutorial({ onSetGraphName }: Props) { - +
@@ -42,10 +42,8 @@ export default function Tutorial({ onSetGraphName }: Props) {

Our Browser allows you to visualize, manipulate and explore your data.

- -

Interact with your data with a force-directed layout, - with features including zoom, pan, - node-drag and interactive node/link hover and click events

+ +

Easily interact with your data in our adaptive force canvas

Configure or export your graph with ease from the control center

diff --git a/app/graph/View.tsx b/app/graph/View.tsx index 39fe7a66..481775c4 100644 --- a/app/graph/View.tsx +++ b/app/graph/View.tsx @@ -1,8 +1,8 @@ /* eslint-disable no-param-reassign */ -import { Check, ChevronDown, ChevronUp, FileCheck2, Pencil, PlusCircle, RotateCcw, Trash2, X } from "lucide-react" +import { Check, ChevronDown, ChevronUp, FileCheck2, PlusCircle, RotateCcw, Trash2, X } from "lucide-react" import { useEffect, useState } from "react" -import { cn, rgbToHSL } from "@/lib/utils" +import { cn } from "@/lib/utils" import { DEFAULT_COLORS, Graph } from "../api/graph/model" import Button from "../components/ui/Button" import DialogComponent from "../components/DialogComponent" @@ -91,16 +91,15 @@ export default function View({ graph, setGraph, selectedValue }: { ref={ref => ref?.focus()} value={editable === c ? editable : newColor} onChange={(e) => { - const newHslColor = rgbToHSL(e.target.value); setColorsArr(prev => { const newArr = [...prev]; - newArr[i] = newHslColor; + newArr[i] = e.target.value; return newArr; }); if (editable === c) { - setEditable(newHslColor) + setEditable(e.target.value) } else { - setNewColor(newHslColor); + setNewColor(e.target.value); } }} onKeyDown={(e) => { @@ -113,7 +112,10 @@ export default function View({ graph, setGraph, selectedValue }: { : <>
-

{c}

+
: hover === c && -
- - -
+ } )) diff --git a/app/graph/labels.tsx b/app/graph/labels.tsx index 23e97a79..e2d245b6 100644 --- a/app/graph/labels.tsx +++ b/app/graph/labels.tsx @@ -1,5 +1,5 @@ import { useRef, useState } from "react"; -import { cn } from "@/lib/utils"; +import { cn, lightenColor } from "@/lib/utils"; import { ChevronDown, ChevronUp } from "lucide-react"; import { Category, Graph } from "../api/graph/model"; import Button from "../components/ui/Button"; @@ -57,7 +57,7 @@ export default function Labels({ graph, categories, onClick, label, className = setReload(prev => !prev) }} > -
+
)) diff --git a/app/graph/page.tsx b/app/graph/page.tsx index ad47ac94..7e89fdb7 100644 --- a/app/graph/page.tsx +++ b/app/graph/page.tsx @@ -129,7 +129,6 @@ export default function Page() { runQuery={runQuery} historyQuery={historyQuery} historyQueries={queries.map(({ text }) => text)} - setHistoryQueries={(queriesArr) => setQueries(queries.map((query, i) => ({ text: queriesArr[i], metadata: query.metadata } as Query)))} fetchCount={fetchCount} session={session} /> diff --git a/app/settings/page.tsx b/app/settings/page.tsx index 622b3100..7cd2c7f9 100644 --- a/app/settings/page.tsx +++ b/app/settings/page.tsx @@ -35,12 +35,12 @@ export default function Settings() {

Settings

) diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 75a78504..2fa3b9e5 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -41,12 +41,12 @@ export default function Header({ onSetGraphName }: Props) {
diff --git a/app/components/TableComponent.tsx b/app/components/TableComponent.tsx index 6d704444..66030190 100644 --- a/app/components/TableComponent.tsx +++ b/app/components/TableComponent.tsx @@ -50,8 +50,6 @@ export default function TableComponent({ headers, rows, children, setRows, optio setNewValue(value) } - console.log(rows) - return (
diff --git a/app/globals.css b/app/globals.css index 36d229df..829b728c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -32,12 +32,12 @@ @font-face { font-family: 'FiraCode'; - src: url('/public/fonts/fira_code.ttf') format('truetype'); - font-display: block; + src: url('/fonts/fira_code.ttf') format('truetype'); + font-display: swap; } .tabs-trigger { - @apply !p-2 rounded-lg data-[state=active]:bg-foreground data-[state=active]:!text-white !text-white; + @apply !p-2 rounded-lg data-[state=active]:bg-foreground data-[state=active]:!text-white text-gray-500; } .hide-scrollbar { @@ -67,23 +67,8 @@ } } -input[type="number"]::-webkit-outer-spin-button, -input[type="number"]::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -input[type="number"] { - -moz-appearance: textfield; - -} - -.monaco-editor .suggest-widget { - margin-top: 0.5rem; -} - * { - font-family: font-FiraCode, 'monospace'; + font-family: 'FiraCode', 'Arial', monospace !important; } ::-webkit-scrollbar { diff --git a/app/graph/GraphView.tsx b/app/graph/GraphView.tsx index fa87af21..2de91473 100644 --- a/app/graph/GraphView.tsx +++ b/app/graph/GraphView.tsx @@ -24,13 +24,14 @@ import TableView from "./TableView"; const ForceGraph = dynamic(() => import("../components/ForceGraph"), { ssr: false }); const EditorComponent = dynamic(() => import("../components/EditorComponent"), { ssr: false }) -function GraphView({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, fetchCount, session }: { +function GraphView({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, setHistoryQueries, fetchCount, session }: { graph: Graph selectedElement: Node | Link | undefined setSelectedElement: Dispatch> runQuery: (query: string) => Promise historyQuery: string historyQueries: string[] + setHistoryQueries: (queries: string[]) => void fetchCount: () => void session: Session | null }) { @@ -224,6 +225,7 @@ function GraphView({ graph, selectedElement, setSelectedElement, runQuery, histo maximize={maximize} currentQuery={query} historyQueries={historyQueries} + setHistoryQueries={setHistoryQueries} runQuery={handleRunQuery} setCurrentQuery={setQuery} data={session} diff --git a/app/graph/Tutorial.tsx b/app/graph/Tutorial.tsx index f907fd9c..0fb0bc4d 100644 --- a/app/graph/Tutorial.tsx +++ b/app/graph/Tutorial.tsx @@ -33,7 +33,7 @@ export default function Tutorial({ onSetGraphName }: Props) { - +
@@ -42,8 +42,10 @@ export default function Tutorial({ onSetGraphName }: Props) {

Our Browser allows you to visualize, manipulate and explore your data.

- -

Easily interact with your data in our adaptive force canvas

+ +

Interact with your data with a force-directed layout, + with features including zoom, pan, + node-drag and interactive node/link hover and click events

Configure or export your graph with ease from the control center

diff --git a/app/graph/View.tsx b/app/graph/View.tsx index 481775c4..39fe7a66 100644 --- a/app/graph/View.tsx +++ b/app/graph/View.tsx @@ -1,8 +1,8 @@ /* eslint-disable no-param-reassign */ -import { Check, ChevronDown, ChevronUp, FileCheck2, PlusCircle, RotateCcw, Trash2, X } from "lucide-react" +import { Check, ChevronDown, ChevronUp, FileCheck2, Pencil, PlusCircle, RotateCcw, Trash2, X } from "lucide-react" import { useEffect, useState } from "react" -import { cn } from "@/lib/utils" +import { cn, rgbToHSL } from "@/lib/utils" import { DEFAULT_COLORS, Graph } from "../api/graph/model" import Button from "../components/ui/Button" import DialogComponent from "../components/DialogComponent" @@ -91,15 +91,16 @@ export default function View({ graph, setGraph, selectedValue }: { ref={ref => ref?.focus()} value={editable === c ? editable : newColor} onChange={(e) => { + const newHslColor = rgbToHSL(e.target.value); setColorsArr(prev => { const newArr = [...prev]; - newArr[i] = e.target.value; + newArr[i] = newHslColor; return newArr; }); if (editable === c) { - setEditable(e.target.value) + setEditable(newHslColor) } else { - setNewColor(e.target.value); + setNewColor(newHslColor); } }} onKeyDown={(e) => { @@ -112,10 +113,7 @@ export default function View({ graph, setGraph, selectedValue }: { : <>
-
: hover === c && - +
+ + +
} )) diff --git a/app/graph/labels.tsx b/app/graph/labels.tsx index e2d245b6..23e97a79 100644 --- a/app/graph/labels.tsx +++ b/app/graph/labels.tsx @@ -1,5 +1,5 @@ import { useRef, useState } from "react"; -import { cn, lightenColor } from "@/lib/utils"; +import { cn } from "@/lib/utils"; import { ChevronDown, ChevronUp } from "lucide-react"; import { Category, Graph } from "../api/graph/model"; import Button from "../components/ui/Button"; @@ -57,7 +57,7 @@ export default function Labels({ graph, categories, onClick, label, className = setReload(prev => !prev) }} > -
+
)) diff --git a/app/graph/page.tsx b/app/graph/page.tsx index 7e89fdb7..ad47ac94 100644 --- a/app/graph/page.tsx +++ b/app/graph/page.tsx @@ -129,6 +129,7 @@ export default function Page() { runQuery={runQuery} historyQuery={historyQuery} historyQueries={queries.map(({ text }) => text)} + setHistoryQueries={(queriesArr) => setQueries(queries.map((query, i) => ({ text: queriesArr[i], metadata: query.metadata } as Query)))} fetchCount={fetchCount} session={session} /> diff --git a/app/settings/page.tsx b/app/settings/page.tsx index 7cd2c7f9..622b3100 100644 --- a/app/settings/page.tsx +++ b/app/settings/page.tsx @@ -35,12 +35,12 @@ export default function Settings() {

Settings

) diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 2fa3b9e5..75a78504 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -41,12 +41,12 @@ export default function Header({ onSetGraphName }: Props) {
diff --git a/app/components/TableComponent.tsx b/app/components/TableComponent.tsx index 66030190..6d704444 100644 --- a/app/components/TableComponent.tsx +++ b/app/components/TableComponent.tsx @@ -50,6 +50,8 @@ export default function TableComponent({ headers, rows, children, setRows, optio setNewValue(value) } + console.log(rows) + return (
diff --git a/app/globals.css b/app/globals.css index 829b728c..36d229df 100644 --- a/app/globals.css +++ b/app/globals.css @@ -32,12 +32,12 @@ @font-face { font-family: 'FiraCode'; - src: url('/fonts/fira_code.ttf') format('truetype'); - font-display: swap; + src: url('/public/fonts/fira_code.ttf') format('truetype'); + font-display: block; } .tabs-trigger { - @apply !p-2 rounded-lg data-[state=active]:bg-foreground data-[state=active]:!text-white text-gray-500; + @apply !p-2 rounded-lg data-[state=active]:bg-foreground data-[state=active]:!text-white !text-white; } .hide-scrollbar { @@ -67,8 +67,23 @@ } } +input[type="number"]::-webkit-outer-spin-button, +input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type="number"] { + -moz-appearance: textfield; + +} + +.monaco-editor .suggest-widget { + margin-top: 0.5rem; +} + * { - font-family: 'FiraCode', 'Arial', monospace !important; + font-family: font-FiraCode, 'monospace'; } ::-webkit-scrollbar { diff --git a/app/graph/GraphView.tsx b/app/graph/GraphView.tsx index 2de91473..fa87af21 100644 --- a/app/graph/GraphView.tsx +++ b/app/graph/GraphView.tsx @@ -24,14 +24,13 @@ import TableView from "./TableView"; const ForceGraph = dynamic(() => import("../components/ForceGraph"), { ssr: false }); const EditorComponent = dynamic(() => import("../components/EditorComponent"), { ssr: false }) -function GraphView({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, setHistoryQueries, fetchCount, session }: { +function GraphView({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, fetchCount, session }: { graph: Graph selectedElement: Node | Link | undefined setSelectedElement: Dispatch> runQuery: (query: string) => Promise historyQuery: string historyQueries: string[] - setHistoryQueries: (queries: string[]) => void fetchCount: () => void session: Session | null }) { @@ -225,7 +224,6 @@ function GraphView({ graph, selectedElement, setSelectedElement, runQuery, histo maximize={maximize} currentQuery={query} historyQueries={historyQueries} - setHistoryQueries={setHistoryQueries} runQuery={handleRunQuery} setCurrentQuery={setQuery} data={session} diff --git a/app/graph/Tutorial.tsx b/app/graph/Tutorial.tsx index 0fb0bc4d..f907fd9c 100644 --- a/app/graph/Tutorial.tsx +++ b/app/graph/Tutorial.tsx @@ -33,7 +33,7 @@ export default function Tutorial({ onSetGraphName }: Props) { - +
@@ -42,10 +42,8 @@ export default function Tutorial({ onSetGraphName }: Props) {

Our Browser allows you to visualize, manipulate and explore your data.

- -

Interact with your data with a force-directed layout, - with features including zoom, pan, - node-drag and interactive node/link hover and click events

+ +

Easily interact with your data in our adaptive force canvas

Configure or export your graph with ease from the control center

diff --git a/app/graph/View.tsx b/app/graph/View.tsx index 39fe7a66..481775c4 100644 --- a/app/graph/View.tsx +++ b/app/graph/View.tsx @@ -1,8 +1,8 @@ /* eslint-disable no-param-reassign */ -import { Check, ChevronDown, ChevronUp, FileCheck2, Pencil, PlusCircle, RotateCcw, Trash2, X } from "lucide-react" +import { Check, ChevronDown, ChevronUp, FileCheck2, PlusCircle, RotateCcw, Trash2, X } from "lucide-react" import { useEffect, useState } from "react" -import { cn, rgbToHSL } from "@/lib/utils" +import { cn } from "@/lib/utils" import { DEFAULT_COLORS, Graph } from "../api/graph/model" import Button from "../components/ui/Button" import DialogComponent from "../components/DialogComponent" @@ -91,16 +91,15 @@ export default function View({ graph, setGraph, selectedValue }: { ref={ref => ref?.focus()} value={editable === c ? editable : newColor} onChange={(e) => { - const newHslColor = rgbToHSL(e.target.value); setColorsArr(prev => { const newArr = [...prev]; - newArr[i] = newHslColor; + newArr[i] = e.target.value; return newArr; }); if (editable === c) { - setEditable(newHslColor) + setEditable(e.target.value) } else { - setNewColor(newHslColor); + setNewColor(e.target.value); } }} onKeyDown={(e) => { @@ -113,7 +112,10 @@ export default function View({ graph, setGraph, selectedValue }: { : <>
-

{c}

+
: hover === c && -
- - -
+ } )) diff --git a/app/graph/labels.tsx b/app/graph/labels.tsx index 23e97a79..e2d245b6 100644 --- a/app/graph/labels.tsx +++ b/app/graph/labels.tsx @@ -1,5 +1,5 @@ import { useRef, useState } from "react"; -import { cn } from "@/lib/utils"; +import { cn, lightenColor } from "@/lib/utils"; import { ChevronDown, ChevronUp } from "lucide-react"; import { Category, Graph } from "../api/graph/model"; import Button from "../components/ui/Button"; @@ -57,7 +57,7 @@ export default function Labels({ graph, categories, onClick, label, className = setReload(prev => !prev) }} > -
+
)) diff --git a/app/graph/page.tsx b/app/graph/page.tsx index ad47ac94..7e89fdb7 100644 --- a/app/graph/page.tsx +++ b/app/graph/page.tsx @@ -129,7 +129,6 @@ export default function Page() { runQuery={runQuery} historyQuery={historyQuery} historyQueries={queries.map(({ text }) => text)} - setHistoryQueries={(queriesArr) => setQueries(queries.map((query, i) => ({ text: queriesArr[i], metadata: query.metadata } as Query)))} fetchCount={fetchCount} session={session} /> diff --git a/app/settings/page.tsx b/app/settings/page.tsx index 622b3100..7cd2c7f9 100644 --- a/app/settings/page.tsx +++ b/app/settings/page.tsx @@ -35,12 +35,12 @@ export default function Settings() {

Settings

) diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 75a78504..2fa3b9e5 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -41,12 +41,12 @@ export default function Header({ onSetGraphName }: Props) {
diff --git a/app/components/TableComponent.tsx b/app/components/TableComponent.tsx index 6d704444..66030190 100644 --- a/app/components/TableComponent.tsx +++ b/app/components/TableComponent.tsx @@ -50,8 +50,6 @@ export default function TableComponent({ headers, rows, children, setRows, optio setNewValue(value) } - console.log(rows) - return (
diff --git a/app/globals.css b/app/globals.css index 36d229df..829b728c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -32,12 +32,12 @@ @font-face { font-family: 'FiraCode'; - src: url('/public/fonts/fira_code.ttf') format('truetype'); - font-display: block; + src: url('/fonts/fira_code.ttf') format('truetype'); + font-display: swap; } .tabs-trigger { - @apply !p-2 rounded-lg data-[state=active]:bg-foreground data-[state=active]:!text-white !text-white; + @apply !p-2 rounded-lg data-[state=active]:bg-foreground data-[state=active]:!text-white text-gray-500; } .hide-scrollbar { @@ -67,23 +67,8 @@ } } -input[type="number"]::-webkit-outer-spin-button, -input[type="number"]::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -input[type="number"] { - -moz-appearance: textfield; - -} - -.monaco-editor .suggest-widget { - margin-top: 0.5rem; -} - * { - font-family: font-FiraCode, 'monospace'; + font-family: 'FiraCode', 'Arial', monospace !important; } ::-webkit-scrollbar { diff --git a/app/graph/Tutorial.tsx b/app/graph/Tutorial.tsx index f907fd9c..0fb0bc4d 100644 --- a/app/graph/Tutorial.tsx +++ b/app/graph/Tutorial.tsx @@ -33,7 +33,7 @@ export default function Tutorial({ onSetGraphName }: Props) { - +
@@ -42,8 +42,10 @@ export default function Tutorial({ onSetGraphName }: Props) {

Our Browser allows you to visualize, manipulate and explore your data.

- -

Easily interact with your data in our adaptive force canvas

+ +

Interact with your data with a force-directed layout, + with features including zoom, pan, + node-drag and interactive node/link hover and click events

Configure or export your graph with ease from the control center

diff --git a/app/graph/View.tsx b/app/graph/View.tsx index 481775c4..39fe7a66 100644 --- a/app/graph/View.tsx +++ b/app/graph/View.tsx @@ -1,8 +1,8 @@ /* eslint-disable no-param-reassign */ -import { Check, ChevronDown, ChevronUp, FileCheck2, PlusCircle, RotateCcw, Trash2, X } from "lucide-react" +import { Check, ChevronDown, ChevronUp, FileCheck2, Pencil, PlusCircle, RotateCcw, Trash2, X } from "lucide-react" import { useEffect, useState } from "react" -import { cn } from "@/lib/utils" +import { cn, rgbToHSL } from "@/lib/utils" import { DEFAULT_COLORS, Graph } from "../api/graph/model" import Button from "../components/ui/Button" import DialogComponent from "../components/DialogComponent" @@ -91,15 +91,16 @@ export default function View({ graph, setGraph, selectedValue }: { ref={ref => ref?.focus()} value={editable === c ? editable : newColor} onChange={(e) => { + const newHslColor = rgbToHSL(e.target.value); setColorsArr(prev => { const newArr = [...prev]; - newArr[i] = e.target.value; + newArr[i] = newHslColor; return newArr; }); if (editable === c) { - setEditable(e.target.value) + setEditable(newHslColor) } else { - setNewColor(e.target.value); + setNewColor(newHslColor); } }} onKeyDown={(e) => { @@ -112,10 +113,7 @@ export default function View({ graph, setGraph, selectedValue }: { : <>
-
: hover === c && - +
+ + +
} )) diff --git a/app/graph/labels.tsx b/app/graph/labels.tsx index e2d245b6..23e97a79 100644 --- a/app/graph/labels.tsx +++ b/app/graph/labels.tsx @@ -1,5 +1,5 @@ import { useRef, useState } from "react"; -import { cn, lightenColor } from "@/lib/utils"; +import { cn } from "@/lib/utils"; import { ChevronDown, ChevronUp } from "lucide-react"; import { Category, Graph } from "../api/graph/model"; import Button from "../components/ui/Button"; @@ -57,7 +57,7 @@ export default function Labels({ graph, categories, onClick, label, className = setReload(prev => !prev) }} > -
+
)) diff --git a/app/settings/page.tsx b/app/settings/page.tsx index 7cd2c7f9..622b3100 100644 --- a/app/settings/page.tsx +++ b/app/settings/page.tsx @@ -35,12 +35,12 @@ export default function Settings() {

Settings

-
- ) - case "graph": { - - const q = "MATCH (n) WITH Count(n) as nodes MATCH ()-[e]->() return nodes, Count(e) as edges" - - securedFetch(`api/graph/${prepareArg(graphName)}/?query=${prepareArg(q)}`, { - method: "GET", - }, toast).then((response) => response.json()).then((json) => { - const data = json.result.data[0] - setNodesCount(data.nodes) - setEdgesCount(data.edges) - }).catch(() => { - toast({ - title: "Error", - description: "Failed to get graph metadata", - variant: "destructive" - }) - }) - - return ( -
-
- Created on 2/2 24 - {nodesCount} Nodes -

|

- {edgesCount} Edges -
- {/* */} -
- -
-
- ) - } - default: - return ( -
{ await handleCreateSchema(e) }} className="grow flex flex-col gap-8"> -
-
-
-

Graph Name

- setGraphName(e.target.value)} - required - /> -
-
-

OpenAI Key

- setOpenaiKey(e.target.value)} - required - /> -
-
-
-

Files

-

URLs

-

Amazon S3/GCP

-
- -
-
- -
- - ) - } - } - - return ( -
-
-
-

Create New Graph

-
-
-
-

Add Data

- -

Schema

- -

Knowledge Graph

-
-
- {getCurrentTab()} -
-
-
- ) -} \ No newline at end of file diff --git a/app/graph/Duplicate.tsx b/app/graph/Duplicate.tsx index 935fae4d..9ae27049 100644 --- a/app/graph/Duplicate.tsx +++ b/app/graph/Duplicate.tsx @@ -1,6 +1,7 @@ import { FormEvent, useState } from "react"; import { prepareArg, securedFetch } from "@/lib/utils"; import { useToast } from "@/components/ui/use-toast"; +import { useSession } from "next-auth/react"; import DialogComponent from "../components/DialogComponent"; import Button from "../components/ui/Button"; import Input from "../components/ui/Input"; @@ -15,14 +16,15 @@ export default function Duplicate({ open, onOpenChange, selectedValue, onDuplica const [duplicateName, setDuplicateName] = useState(""); const { toast } = useToast() - + const { data: session } = useSession() + const handleDuplicate = async (e: FormEvent) => { e.preventDefault() const result = await securedFetch(`api/graph/${prepareArg(duplicateName)}/?sourceName=${prepareArg(selectedValue)}`, { method: "POST" - }, toast) + }, session?.user?.role, toast) if (!result.ok) return diff --git a/app/graph/GraphDataPanel.tsx b/app/graph/GraphDataPanel.tsx index 2976a9d1..b3c6c271 100644 --- a/app/graph/GraphDataPanel.tsx +++ b/app/graph/GraphDataPanel.tsx @@ -7,9 +7,9 @@ import { prepareArg, securedFetch } from "@/lib/utils"; import { Dispatch, SetStateAction, useEffect, useState } from "react"; import { Check, ChevronRight, Pencil, Plus, Trash2, X } from "lucide-react"; -import { Session } from "next-auth"; import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { useToast } from "@/components/ui/use-toast"; +import { useSession } from "next-auth/react"; import Button from "../components/ui/Button"; import { Graph, Link, Node } from "../api/graph/model"; import Input from "../components/ui/Input"; @@ -24,10 +24,9 @@ interface Props { onExpand: () => void; graph: Graph; onDeleteElement: () => Promise; - data: Session | null; } -export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, graph, data }: Props) { +export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, graph }: Props) { const [attributes, setAttributes] = useState([]); const [editable, setEditable] = useState(""); @@ -39,7 +38,8 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, const [label, setLabel] = useState([""]); const type = !("source" in obj) const { toast } = useToast() - + const { data: session } = useSession() + const handleSetEditable = (key: string, val: string) => { if (key !== "") { setIsAddValue(false) @@ -67,7 +67,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, const q = `MATCH ${type ? "(e)" : "()-[e]-()"} WHERE id(e) = ${id} SET e.${key} = '${val}'` const success = (await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(q)}`, { method: "GET" - }, toast)).ok + }, session?.user?.role, toast)).ok if (success) { graph.getElements().forEach(e => { @@ -138,7 +138,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, const q = `MATCH ${type ? "(e)" : "()-[e]-()"} WHERE id(e) = ${id} SET e.${key} = NULL` const success = (await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(q)}`, { method: "GET" - }, toast)).ok + }, session?.user?.role, toast)).ok if (success) { const value = obj.data[key] @@ -238,7 +238,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement,
{ - editable === key && data?.user.role !== "Read-Only" ? + editable === key && session?.user?.role !== "Read-Only" ? <>