diff --git a/Changelog.md b/Changelog.md index 1d840ffe8..27c6dc7f2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -17,6 +17,15 @@ ([#743](https://github.com/aws/graph-explorer/pull/743)) - **Improved** pagination controls by using a single shared component ([#742](https://github.com/aws/graph-explorer/pull/742)) +- **Updated** graph foundations to accommodate loading a graph from a set of IDs + ([#756](https://github.com/aws/graph-explorer/pull/756), + [#758](https://github.com/aws/graph-explorer/pull/758), + [#761](https://github.com/aws/graph-explorer/pull/761), + [#762](https://github.com/aws/graph-explorer/pull/762), + [#767](https://github.com/aws/graph-explorer/pull/767), + [#768](https://github.com/aws/graph-explorer/pull/768), + [#769](https://github.com/aws/graph-explorer/pull/769), + [#770](https://github.com/aws/graph-explorer/pull/770)) - **Updated** dependencies ([#764](https://github.com/aws/graph-explorer/pull/764)) diff --git a/packages/graph-explorer/src/connector/queries.ts b/packages/graph-explorer/src/connector/queries.ts index 3bf8b3b09..07f45683b 100644 --- a/packages/graph-explorer/src/connector/queries.ts +++ b/packages/graph-explorer/src/connector/queries.ts @@ -54,11 +54,10 @@ export type NeighborCountsQueryResponse = { */ export function neighborsCountQuery( vertexId: VertexId, - limit: number | undefined, explorer: Explorer | null ) { return queryOptions({ - queryKey: ["neighborsCount", vertexId, limit, explorer], + queryKey: ["neighborsCount", vertexId, explorer], enabled: Boolean(explorer), queryFn: async (): Promise => { if (!explorer) { @@ -69,6 +68,8 @@ export function neighborsCountQuery( }; } + const limit = explorer.connection.nodeExpansionLimit; + const result = await explorer.fetchNeighborsCount({ vertexId, limit, diff --git a/packages/graph-explorer/src/core/StateProvider/neighbors.ts b/packages/graph-explorer/src/core/StateProvider/neighbors.ts index 0149e80f4..bf5fcee15 100644 --- a/packages/graph-explorer/src/core/StateProvider/neighbors.ts +++ b/packages/graph-explorer/src/core/StateProvider/neighbors.ts @@ -1,4 +1,4 @@ -import { VertexId } from "@/core"; +import { useDisplayVerticesInCanvas, VertexId } from "@/core"; import { selectorFamily, useRecoilCallback, useRecoilValue } from "recoil"; import { edgesAtom } from "./edges"; import { nodesAtom } from "./nodes"; @@ -9,7 +9,7 @@ import { import { useEffect, useMemo } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { neighborsCountQuery } from "@/connector"; -import { activeConnectionSelector, explorerSelector } from "../connector"; +import { explorerSelector } from "../connector"; import { useNotification } from "@/components/NotificationProvider"; export type NeighborCounts = { @@ -44,9 +44,8 @@ export function useNeighborsCallback() { fetchedNeighborsSelector(vertexId) ); const explorer = await snapshot.getPromise(explorerSelector); - const connection = await snapshot.getPromise(activeConnectionSelector); const response = await queryClient.ensureQueryData( - neighborsCountQuery(vertexId, connection?.nodeExpansionLimit, explorer) + neighborsCountQuery(vertexId, explorer) ); const neighbors = calculateNeighbors( @@ -97,11 +96,14 @@ export function useNeighborByType(vertexId: VertexId, type: string) { return neighbors; } -export function useAllNeighbors(vertices: VertexId[]) { +export function useAllNeighbors() { + const vertices = useDisplayVerticesInCanvas(); + const vertexIds = useMemo(() => vertices.keys().toArray(), [vertices]); + const fetchedNeighbors = useRecoilValue( - allFetchedNeighborsSelector(vertices) + allFetchedNeighborsSelector(vertexIds) ); - const query = useAllNeighborCountsQuery(vertices); + const query = useAllNeighborCountsQuery(vertexIds); const { enqueueNotification, clearNotification } = useNotification(); diff --git a/packages/graph-explorer/src/core/connector.ts b/packages/graph-explorer/src/core/connector.ts index 8c62193ad..5266c8c71 100644 --- a/packages/graph-explorer/src/core/connector.ts +++ b/packages/graph-explorer/src/core/connector.ts @@ -19,7 +19,7 @@ import { Explorer } from "@/connector/useGEFetchTypes"; * Active connection where the value will only change when one of the * properties we care about are changed. */ -export const activeConnectionSelector = equalSelector({ +const activeConnectionSelector = equalSelector({ key: "activeConnection", get: ({ get }) => { const config = get(mergedConfigurationSelector); @@ -75,6 +75,14 @@ export const explorerSelector = selector({ }, }); +export function useExplorer() { + const explorer = useRecoilValue(explorerSelector); + if (!explorer) { + throw new Error("No explorer found"); + } + return explorer; +} + /** CAUTION: This atom is only for testing purposes. */ export const explorerForTestingAtom = atom({ key: "explorerForTesting", diff --git a/packages/graph-explorer/src/hooks/useAddToGraph.test.ts b/packages/graph-explorer/src/hooks/useAddToGraph.test.ts index 6333a2149..1e556721a 100644 --- a/packages/graph-explorer/src/hooks/useAddToGraph.test.ts +++ b/packages/graph-explorer/src/hooks/useAddToGraph.test.ts @@ -13,14 +13,14 @@ import { nodesAtom, toNodeMap } from "@/core/StateProvider/nodes"; test("should add one node", async () => { const vertex = createRandomVertex(); const { result } = renderHookWithRecoilRoot(() => { - const callback = useAddToGraph(vertex); + const callback = useAddToGraph(); const [entities] = useEntities(); return { callback, entities }; }); await act(async () => { - result.current.callback(); + result.current.callback({ vertices: [vertex] }); }); const actual = result.current.entities.nodes.get(vertex.id); @@ -34,7 +34,7 @@ test("should add one edge", async () => { const { result } = renderHookWithRecoilRoot( () => { - const callback = useAddToGraph(edge); + const callback = useAddToGraph(); const [entities] = useEntities(); return { callback, entities }; @@ -45,7 +45,7 @@ test("should add one edge", async () => { ); await act(async () => { - result.current.callback(); + result.current.callback({ edges: [edge] }); }); const actual = result.current.entities.edges.get(edge.id); @@ -55,17 +55,17 @@ test("should add one edge", async () => { test("should add multiple nodes and edges", async () => { const randomEntities = createRandomEntities(); const { result } = renderHookWithRecoilRoot(() => { - const callback = useAddToGraph( - ...randomEntities.nodes.values(), - ...randomEntities.edges.values() - ); + const callback = useAddToGraph(); const [entities] = useEntities(); return { callback, entities }; }); await act(async () => { - result.current.callback(); + result.current.callback({ + vertices: [...randomEntities.nodes.values()], + edges: [...randomEntities.edges.values()], + }); }); const actualNodes = result.current.entities.nodes.values().toArray(); diff --git a/packages/graph-explorer/src/hooks/useAddToGraph.ts b/packages/graph-explorer/src/hooks/useAddToGraph.ts index 2934fc340..80aa642fe 100644 --- a/packages/graph-explorer/src/hooks/useAddToGraph.ts +++ b/packages/graph-explorer/src/hooks/useAddToGraph.ts @@ -6,20 +6,32 @@ import { useCallback } from "react"; import { useSetRecoilState } from "recoil"; /** Returns a callback that adds an array of nodes and edges to the graph. */ -export function useAddToGraph(...entitiesToAdd: (Vertex | Edge)[]) { +export function useAddToGraph() { const setEntities = useSetRecoilState(entitiesSelector); - return useCallback(() => { - const nodes = entitiesToAdd.filter(e => e.entityType === "vertex"); - const edges = entitiesToAdd.filter(e => e.entityType === "edge"); + return useCallback( + (entities: { vertices?: Vertex[]; edges?: Edge[] }) => { + const vertices = entities.vertices ?? []; + const edges = entities.edges ?? []; - if (nodes.length === 0 && edges.length === 0) { - return; - } + if (vertices.length === 0 && edges.length === 0) { + return; + } - setEntities({ - nodes: toNodeMap(nodes), - edges: toEdgeMap(edges), - }); - }, [entitiesToAdd, setEntities]); + setEntities({ + nodes: toNodeMap(vertices), + edges: toEdgeMap(edges), + }); + }, + [setEntities] + ); +} + +/** Returns a callback the given vertex to the graph. */ +export function useAddVertexToGraph(vertex: Vertex) { + const callback = useAddToGraph(); + return useCallback( + () => callback({ vertices: [vertex] }), + [callback, vertex] + ); } diff --git a/packages/graph-explorer/src/hooks/useEdgeDetailsQuery.ts b/packages/graph-explorer/src/hooks/useEdgeDetailsQuery.ts index 50d7625cf..35970bc8d 100644 --- a/packages/graph-explorer/src/hooks/useEdgeDetailsQuery.ts +++ b/packages/graph-explorer/src/hooks/useEdgeDetailsQuery.ts @@ -1,10 +1,9 @@ import { edgeDetailsQuery } from "@/connector"; -import { EdgeId, explorerSelector } from "@/core"; +import { EdgeId, useExplorer } from "@/core"; import { useQuery } from "@tanstack/react-query"; -import { useRecoilValue } from "recoil"; export function useEdgeDetailsQuery(edgeId: EdgeId) { - const explorer = useRecoilValue(explorerSelector); + const explorer = useExplorer(); const query = useQuery(edgeDetailsQuery({ edgeId }, explorer)); return query; } diff --git a/packages/graph-explorer/src/hooks/useExpandNode.tsx b/packages/graph-explorer/src/hooks/useExpandNode.tsx index 8e5a9c6b8..724249aed 100644 --- a/packages/graph-explorer/src/hooks/useExpandNode.tsx +++ b/packages/graph-explorer/src/hooks/useExpandNode.tsx @@ -6,19 +6,13 @@ import { type NeighborsRequest, type NeighborsResponse, } from "@/connector"; -import { - activeConnectionSelector, - explorerSelector, - loggerSelector, -} from "@/core/connector"; -import useEntities from "./useEntities"; +import { loggerSelector, useExplorer } from "@/core/connector"; import { useRecoilValue } from "recoil"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { Vertex } from "@/core"; import { createDisplayError } from "@/utils/createDisplayError"; -import { toNodeMap } from "@/core/StateProvider/nodes"; -import { toEdgeMap } from "@/core/StateProvider/edges"; import { useNeighborsCallback } from "@/core"; +import { useAddToGraph } from "./useAddToGraph"; export type ExpandNodeFilters = Omit< NeighborsRequest, @@ -36,8 +30,8 @@ export type ExpandNodeRequest = { */ export default function useExpandNode() { const queryClient = useQueryClient(); - const explorer = useRecoilValue(explorerSelector); - const [_, setEntities] = useEntities(); + const explorer = useExplorer(); + const addToGraph = useAddToGraph(); const { enqueueNotification, clearNotification } = useNotification(); const remoteLogger = useRecoilValue(loggerSelector); @@ -45,6 +39,21 @@ export default function useExpandNode() { mutationFn: async ( expandNodeRequest: ExpandNodeRequest ): Promise => { + // Calculate the expansion limit based on the connection limit and the request limit + const limit = (() => { + if (!explorer.connection.nodeExpansionLimit) { + return expandNodeRequest.filters?.limit; + } + if (!expandNodeRequest.filters?.limit) { + return explorer.connection.nodeExpansionLimit; + } + // If both exists then use the smaller of the two + return Math.min( + explorer.connection.nodeExpansionLimit, + expandNodeRequest.filters.limit + ); + })(); + // Perform the query when a request exists const request: NeighborsRequest | null = expandNodeRequest && { vertexId: expandNodeRequest.vertex.id, @@ -52,6 +61,7 @@ export default function useExpandNode() { expandNodeRequest.vertex.types?.join("::") ?? expandNodeRequest.vertex.type, ...expandNodeRequest.filters, + limit, }; if (!explorer || !request) { @@ -70,10 +80,7 @@ export default function useExpandNode() { updateEdgeDetailsCache(explorer, queryClient, data.edges); // Update nodes and edges in the graph - setEntities({ - nodes: toNodeMap(data.vertices), - edges: toEdgeMap(data.edges), - }); + addToGraph(data); }, onError: error => { remoteLogger.error(`Failed to expand node: ${error.message}`); @@ -92,10 +99,8 @@ export default function useExpandNode() { if (!isPending) { return; } - // const displayName = getDisplayNames(expandNodeRequest.vertex); const notificationId = enqueueNotification({ title: "Expanding Node", - // message: `Expanding the node ${displayName.name}`, message: "Expanding neighbors for the given node.", stackable: true, }); @@ -104,7 +109,6 @@ export default function useExpandNode() { }, [clearNotification, enqueueNotification, isPending]); // Build the expand node callback - const connection = useRecoilValue(activeConnectionSelector); const neighborCallback = useNeighborsCallback(); const expandNode = useCallback( async (vertex: Vertex, filters?: ExpandNodeFilters) => { @@ -118,10 +122,7 @@ export default function useExpandNode() { } const request: ExpandNodeRequest = { vertex, - filters: { - ...filters, - limit: filters?.limit || connection?.nodeExpansionLimit, - }, + filters, }; // Only allow expansion if we are not busy with another expansion @@ -140,13 +141,7 @@ export default function useExpandNode() { mutate(request); }, - [ - connection?.nodeExpansionLimit, - enqueueNotification, - isPending, - mutate, - neighborCallback, - ] + [enqueueNotification, isPending, mutate, neighborCallback] ); return { diff --git a/packages/graph-explorer/src/hooks/useRemoveFromGraph.test.ts b/packages/graph-explorer/src/hooks/useRemoveFromGraph.test.ts index 83e4bb8f3..5dc9dea2c 100644 --- a/packages/graph-explorer/src/hooks/useRemoveFromGraph.test.ts +++ b/packages/graph-explorer/src/hooks/useRemoveFromGraph.test.ts @@ -8,13 +8,16 @@ import useEntities from "./useEntities"; import { act } from "react"; import { nodesAtom, toNodeMap } from "@/core/StateProvider/nodes"; import { + useClearGraph, useRemoveEdgeFromGraph, useRemoveNodeFromGraph, } from "./useRemoveFromGraph"; import { edgesAtom, toEdgeMap } from "@/core/StateProvider/edges"; +import { createArray } from "@shared/utils/testing"; test("should remove one node", async () => { const vertex = createRandomVertex(); + const noise = createArray(10, createRandomVertex); const { result } = renderHookWithRecoilRoot( () => { const callback = useRemoveNodeFromGraph(vertex.id); @@ -23,7 +26,7 @@ test("should remove one node", async () => { return { callback, entities }; }, snapshot => { - snapshot.set(nodesAtom, toNodeMap([vertex])); + snapshot.set(nodesAtom, toNodeMap([vertex, ...noise])); } ); @@ -32,23 +35,25 @@ test("should remove one node", async () => { }); expect(result.current.entities.nodes.has(vertex.id)).toBeFalsy(); + expect(result.current.entities.nodes.size).toBe(noise.length); }); test("should remove one edge", async () => { const node1 = createRandomVertex(); const node2 = createRandomVertex(); - const edge = createRandomEdge(node1, node2); + const edge1 = createRandomEdge(node1, node2); + const edge2 = createRandomEdge(node2, node1); const { result } = renderHookWithRecoilRoot( () => { - const callback = useRemoveEdgeFromGraph(edge.id); + const callback = useRemoveEdgeFromGraph(edge1.id); const [entities] = useEntities(); return { callback, entities }; }, snapshot => { snapshot.set(nodesAtom, toNodeMap([node1, node2])); - snapshot.set(edgesAtom, toEdgeMap([edge])); + snapshot.set(edgesAtom, toEdgeMap([edge1, edge2])); } ); @@ -56,5 +61,34 @@ test("should remove one edge", async () => { result.current.callback(); }); - expect(result.current.entities.edges.has(edge.id)).toBeFalsy(); + expect(result.current.entities.edges.has(edge1.id)).toBeFalsy(); + expect(result.current.entities.edges.has(edge2.id)).toBeTruthy(); + expect(result.current.entities.nodes.size).toBe(2); +}); + +test("should remove all nodes and edges", async () => { + const node1 = createRandomVertex(); + const node2 = createRandomVertex(); + const edge1 = createRandomEdge(node1, node2); + const edge2 = createRandomEdge(node2, node1); + + const { result } = renderHookWithRecoilRoot( + () => { + const callback = useClearGraph(); + const [entities] = useEntities(); + + return { callback, entities }; + }, + snapshot => { + snapshot.set(nodesAtom, toNodeMap([node1, node2])); + snapshot.set(edgesAtom, toEdgeMap([edge1, edge2])); + } + ); + + await act(async () => { + result.current.callback(); + }); + + expect(result.current.entities.nodes.size).toBe(0); + expect(result.current.entities.edges.size).toBe(0); }); diff --git a/packages/graph-explorer/src/hooks/useRemoveFromGraph.ts b/packages/graph-explorer/src/hooks/useRemoveFromGraph.ts index 41a0d97f5..611c22d8a 100644 --- a/packages/graph-explorer/src/hooks/useRemoveFromGraph.ts +++ b/packages/graph-explorer/src/hooks/useRemoveFromGraph.ts @@ -1,30 +1,57 @@ import { EdgeId, VertexId } from "@/core"; -import useEntities from "./useEntities"; +import entitiesSelector from "@/core/StateProvider/entitiesSelector"; +import { useSetRecoilState } from "recoil"; +import { useCallback } from "react"; -export function useRemoveNodeFromGraph(nodeId: VertexId) { - const [, setEntities] = useEntities(); +export function useRemoveFromGraph() { + const setEntities = useSetRecoilState(entitiesSelector); + + return useCallback( + (entities: { vertices?: VertexId[]; edges?: EdgeId[] }) => { + const vertices = new Set(entities.vertices ?? []); + const edges = new Set(entities.edges ?? []); + + // Ensure there is something to remove + if (vertices.size === 0 && edges.size === 0) { + return; + } - return () => { - setEntities(prev => { - return { - ...prev, - nodes: new Map(prev.nodes.entries().filter(([id]) => id !== nodeId)), + setEntities(prev => ({ + nodes: new Map( + prev.nodes.entries().filter(([id]) => !vertices.has(id)) + ), + edges: new Map(prev.edges.entries().filter(([id]) => !edges.has(id))), forceSet: true, - }; - }); - }; + })); + }, + [setEntities] + ); +} + +export function useRemoveNodeFromGraph(nodeId: VertexId) { + const removeFromGraph = useRemoveFromGraph(); + + return useCallback(() => { + removeFromGraph({ vertices: [nodeId] }); + }, [nodeId, removeFromGraph]); } export function useRemoveEdgeFromGraph(edgeId: EdgeId) { - const [, setEntities] = useEntities(); + const removeFromGraph = useRemoveFromGraph(); - return () => { - setEntities(prev => { - return { - ...prev, - edges: new Map(prev.edges.entries().filter(([id]) => id !== edgeId)), - forceSet: true, - }; + return useCallback(() => { + removeFromGraph({ edges: [edgeId] }); + }, [edgeId, removeFromGraph]); +} + +export function useClearGraph() { + const setEntities = useSetRecoilState(entitiesSelector); + + return useCallback(() => { + setEntities({ + nodes: new Map(), + edges: new Map(), + forceSet: true, }); - }; + }, [setEntities]); } diff --git a/packages/graph-explorer/src/hooks/useSchemaSync.ts b/packages/graph-explorer/src/hooks/useSchemaSync.ts index ea9a7479a..e88a3e66d 100644 --- a/packages/graph-explorer/src/hooks/useSchemaSync.ts +++ b/packages/graph-explorer/src/hooks/useSchemaSync.ts @@ -1,7 +1,7 @@ import { useCallback, useRef } from "react"; import { useNotification } from "@/components/NotificationProvider"; import { useConfiguration } from "@/core/ConfigurationProvider"; -import { explorerSelector, loggerSelector } from "@/core/connector"; +import { loggerSelector, useExplorer } from "@/core/connector"; import usePrefixesUpdater from "./usePrefixesUpdater"; import useUpdateSchema from "./useUpdateSchema"; import { createDisplayError } from "@/utils/createDisplayError"; @@ -9,7 +9,7 @@ import { useRecoilValue } from "recoil"; const useSchemaSync = (onSyncChange?: (isSyncing: boolean) => void) => { const config = useConfiguration(); - const explorer = useRecoilValue(explorerSelector); + const explorer = useExplorer(); const remoteLogger = useRecoilValue(loggerSelector); const updatePrefixes = usePrefixesUpdater(); diff --git a/packages/graph-explorer/src/hooks/useUpdateNodeCounts.ts b/packages/graph-explorer/src/hooks/useUpdateNodeCounts.ts index b33563a88..92ab4f7a6 100644 --- a/packages/graph-explorer/src/hooks/useUpdateNodeCounts.ts +++ b/packages/graph-explorer/src/hooks/useUpdateNodeCounts.ts @@ -1,25 +1,18 @@ import { useQueries, useQuery } from "@tanstack/react-query"; -import { useRecoilValue } from "recoil"; import { neighborsCountQuery } from "@/connector"; -import { activeConnectionSelector, explorerSelector } from "@/core/connector"; +import { useExplorer } from "@/core/connector"; import { VertexId } from "@/core"; export function useUpdateNodeCountsQuery(vertexId: VertexId) { - const connection = useRecoilValue(activeConnectionSelector); - const explorer = useRecoilValue(explorerSelector); - return useQuery( - neighborsCountQuery(vertexId, connection?.nodeExpansionLimit, explorer) - ); + const explorer = useExplorer(); + return useQuery(neighborsCountQuery(vertexId, explorer)); } export function useAllNeighborCountsQuery(vertexIds: VertexId[]) { - const connection = useRecoilValue(activeConnectionSelector); - const explorer = useRecoilValue(explorerSelector); + const explorer = useExplorer(); return useQueries({ - queries: vertexIds.map(vertex => - neighborsCountQuery(vertex, connection?.nodeExpansionLimit, explorer) - ), + queries: vertexIds.map(vertex => neighborsCountQuery(vertex, explorer)), combine: results => ({ data: results.map(result => result.data), pending: results.some(result => result.isPending), diff --git a/packages/graph-explorer/src/hooks/useUpdateVertexTypeCounts.ts b/packages/graph-explorer/src/hooks/useUpdateVertexTypeCounts.ts index 50750c5de..77df077ab 100644 --- a/packages/graph-explorer/src/hooks/useUpdateVertexTypeCounts.ts +++ b/packages/graph-explorer/src/hooks/useUpdateVertexTypeCounts.ts @@ -1,13 +1,11 @@ import { useEffect } from "react"; import { useQuery } from "@tanstack/react-query"; -import { explorerSelector } from "@/core/connector"; +import { useExplorer } from "@/core"; import useUpdateSchema from "./useUpdateSchema"; -import { useRecoilValue } from "recoil"; import { nodeCountByNodeTypeQuery } from "@/connector"; export default function useUpdateVertexTypeCounts(vertexType: string) { - const explorer = useRecoilValue(explorerSelector); - + const explorer = useExplorer(); const query = useQuery(nodeCountByNodeTypeQuery(vertexType, explorer)); // Sync the result over to the schema in Recoil state diff --git a/packages/graph-explorer/src/hooks/useVertexDetailsQuery.ts b/packages/graph-explorer/src/hooks/useVertexDetailsQuery.ts index 8fec0c5e2..5c4be84ce 100644 --- a/packages/graph-explorer/src/hooks/useVertexDetailsQuery.ts +++ b/packages/graph-explorer/src/hooks/useVertexDetailsQuery.ts @@ -1,10 +1,9 @@ import { vertexDetailsQuery } from "@/connector"; -import { explorerSelector, VertexId } from "@/core"; +import { useExplorer, VertexId } from "@/core"; import { useQuery } from "@tanstack/react-query"; -import { useRecoilValue } from "recoil"; export function useVertexDetailsQuery(vertexId: VertexId) { - const explorer = useRecoilValue(explorerSelector); + const explorer = useExplorer(); const query = useQuery(vertexDetailsQuery({ vertexId }, explorer)); return query; } diff --git a/packages/graph-explorer/src/modules/EntitiesTabular/components/NodesTabular.tsx b/packages/graph-explorer/src/modules/EntitiesTabular/components/NodesTabular.tsx index 94c207521..4b0079ca2 100644 --- a/packages/graph-explorer/src/modules/EntitiesTabular/components/NodesTabular.tsx +++ b/packages/graph-explorer/src/modules/EntitiesTabular/components/NodesTabular.tsx @@ -30,12 +30,7 @@ const NodesTabular = forwardRef, any>( (_props, ref) => { const t = useTranslations(); const displayNodes = useDisplayVerticesInCanvas(); - const neighborCounts = useAllNeighbors( - displayNodes - .values() - .map(v => v.id) - .toArray() - ); + const neighborCounts = useAllNeighbors(); const setNodesOut = useSetRecoilState(nodesOutOfFocusIdsAtom); const [hiddenNodesIds, setHiddenNodesIds] = useRecoilState(nodesFilteredIdsAtom); diff --git a/packages/graph-explorer/src/modules/GraphViewer/GraphViewer.tsx b/packages/graph-explorer/src/modules/GraphViewer/GraphViewer.tsx index 18c525d5e..d66ecd4e7 100644 --- a/packages/graph-explorer/src/modules/GraphViewer/GraphViewer.tsx +++ b/packages/graph-explorer/src/modules/GraphViewer/GraphViewer.tsx @@ -32,7 +32,7 @@ import { nodesOutOfFocusIdsAtom, nodesSelectedIdsAtom, } from "@/core/StateProvider/nodes"; -import { useEntities, useExpandNode } from "@/hooks"; +import { useClearGraph, useEntities, useExpandNode } from "@/hooks"; import ContextMenu from "./internalComponents/ContextMenu"; import useContextMenu from "./useContextMenu"; import useGraphGlobalActions from "./useGraphGlobalActions"; @@ -93,7 +93,7 @@ export default function GraphViewer({ onEdgeCustomize, }: GraphViewerProps) { const graphRef = useRef(null); - const [entities, setEntities] = useEntities(); + const [entities] = useEntities(); const [nodesSelectedIds, setNodesSelectedIds] = useRecoilState(nodesSelectedIdsAtom); @@ -146,6 +146,7 @@ export default function GraphViewer({ async (_, vertex) => { const neighborCount = await neighborCallback(vertex.id); const offset = neighborCount ? neighborCount.fetched : undefined; + expandNode(vertex, { limit: 10, offset, @@ -155,13 +156,7 @@ export default function GraphViewer({ ); const [layout, setLayout] = useState("F_COSE"); - const onClearCanvas = useCallback(() => { - setEntities({ - nodes: new Map(), - edges: new Map(), - forceSet: true, - }); - }, [setEntities]); + const onClearGraph = useClearGraph(); const nodes = useMemo( () => @@ -226,7 +221,7 @@ export default function GraphViewer({ label="Clear canvas" icon={} color="error" - onActionClick={onClearCanvas} + onActionClick={onClearGraph} /> { const t = useTranslations(); - const [_, setEntities] = useEntities(); const displayNodes = useDisplayVerticesInCanvas(); const displayEdges = useDisplayEdgesInCanvas(); const [nodesSelectedIds, setNodesSelectedIds] = @@ -135,41 +134,20 @@ const ContextMenu = ({ onClose?.(); }, [onClose, onZoomOut]); + const removeFromGraph = useRemoveFromGraph(); const handleRemoveFromCanvas = useCallback( (nodesIds: VertexId[], edgesIds: EdgeId[]) => () => { - setEntities(prev => { - // const newNodes = new Map(prev.nodes); - // nodesIds.forEach(id => newNodes.delete(id)); - return { - // nodes: newNodes, - nodes: toNodeMap( - prev.nodes - .values() - .filter(n => !nodesIds.includes(n.id)) - .toArray() - ), - edges: toEdgeMap( - prev.edges - .values() - .filter(e => !edgesIds.includes(e.id)) - .toArray() - ), - forceSet: true, - }; - }); + removeFromGraph({ vertices: nodesIds, edges: edgesIds }); onClose?.(); }, - [onClose, setEntities] + [onClose, removeFromGraph] ); + const clearGraph = useClearGraph(); const handleRemoveAllFromCanvas = useCallback(() => { - setEntities({ - nodes: new Map(), - edges: new Map(), - forceSet: true, - }); + clearGraph(); onClose?.(); - }, [onClose, setEntities]); + }, [onClose, clearGraph]); const noSelectionOrNotAffected = affectedNodesIds?.length === 0 && diff --git a/packages/graph-explorer/src/modules/GraphViewer/useNodeBadges.ts b/packages/graph-explorer/src/modules/GraphViewer/useNodeBadges.ts index c051ac90a..a524b49e3 100644 --- a/packages/graph-explorer/src/modules/GraphViewer/useNodeBadges.ts +++ b/packages/graph-explorer/src/modules/GraphViewer/useNodeBadges.ts @@ -6,12 +6,7 @@ import { useAllNeighbors } from "@/core"; const useNodeBadges = () => { const displayNodes = useDisplayVerticesInCanvas(); - const neighborCounts = useAllNeighbors( - displayNodes - .values() - .map(v => v.id) - .toArray() - ); + const neighborCounts = useAllNeighbors(); return useCallback( (outOfFocusIds: Set): BadgeRenderer => diff --git a/packages/graph-explorer/src/modules/SearchSidebar/NodeSearchResult.tsx b/packages/graph-explorer/src/modules/SearchSidebar/NodeSearchResult.tsx index c7798afa9..d1c9be56b 100644 --- a/packages/graph-explorer/src/modules/SearchSidebar/NodeSearchResult.tsx +++ b/packages/graph-explorer/src/modules/SearchSidebar/NodeSearchResult.tsx @@ -7,7 +7,7 @@ import { VertexRow, } from "@/components"; import { - useAddToGraph, + useAddVertexToGraph, useHasVertexBeenAddedToGraph, useRemoveNodeFromGraph, } from "@/hooks"; @@ -25,7 +25,7 @@ export function NodeSearchResult({ node }: { node: Vertex }) { const [expanded, setExpanded] = useState(false); const displayNode = useDisplayVertexFromVertex(node); - const addToGraph = useAddToGraph(node); + const addToGraph = useAddVertexToGraph(node); const removeFromGraph = useRemoveNodeFromGraph(node.id); const hasBeenAdded = useHasVertexBeenAddedToGraph(node.id); diff --git a/packages/graph-explorer/src/modules/SearchSidebar/SearchResultsList.tsx b/packages/graph-explorer/src/modules/SearchSidebar/SearchResultsList.tsx index fee070f34..0908974c9 100644 --- a/packages/graph-explorer/src/modules/SearchSidebar/SearchResultsList.tsx +++ b/packages/graph-explorer/src/modules/SearchSidebar/SearchResultsList.tsx @@ -60,7 +60,7 @@ export function SearchResultsList({ } function LoadedResults({ vertices, edges, scalars }: MappedQueryResults) { - const sendToGraph = useAddToGraph(...vertices, ...edges); + const sendToGraph = useAddToGraph(); const canSendToGraph = vertices.length > 0 || edges.length > 0; const counts = [ @@ -89,7 +89,7 @@ function LoadedResults({ vertices, edges, scalars }: MappedQueryResults) {