Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export and import graph #781

Merged
merged 10 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

## Upcoming

- **Added** ability to save the rendered graph to a file, allowing for reloading
the graph later or sharing the graph with other users who have the same
connection ([#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),
[#775](https://github.com/aws/graph-explorer/pull/775),
[#781](https://github.com/aws/graph-explorer/pull/781))
- **Updated** UI labels to refer to node & edge "labels" instead of "types"
([#766](https://github.com/aws/graph-explorer/pull/766))
- **Improved** neighbor count retrieval to be more efficient
Expand All @@ -15,16 +27,6 @@
([#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),
[#775](https://github.com/aws/graph-explorer/pull/775))
- **Updated** styling across the app
([#777](https://github.com/aws/graph-explorer/pull/777),
[#743](https://github.com/aws/graph-explorer/pull/743),
Expand Down
1 change: 1 addition & 0 deletions packages/graph-explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@react-stately/list": "^3.11.2",
"@tanstack/react-query": "^5.64.2",
"@tanstack/react-query-devtools": "^5.64.2",
"@types/wicg-file-system-access": "^2023.10.5",
"clsx": "^2.1.1",
"color": "^4.2.3",
"crypto-js": "^4.2.0",
Expand Down
62 changes: 62 additions & 0 deletions packages/graph-explorer/src/components/FileButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import Button from "./Button";

export interface FileButtonProps
extends React.ComponentPropsWithoutRef<typeof Button> {
asChild?: boolean;
onChange?: (files: FileList | null) => void;
accept?: string;
multiple?: boolean;
}

/**
* A wrapper around whatever button you want to open a file dialog. It will
* automatically open the file dialog when clicked.
*
* @example
* <FileButton onChange={files => console.log(files)} asChild>
* <Button>Open File</Button>
* </FileButton>
*/
export const FileButton = React.forwardRef<HTMLButtonElement, FileButtonProps>(
(
{ asChild, onChange, accept, multiple, isDisabled, children, ...props },
ref
) => {
const inputRef = React.useRef<HTMLInputElement | null>(null);

const handleClick = () => {
!isDisabled && inputRef.current?.click();
};

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
onChange?.(files);
// Reset the input value to allow selecting the same file again
event.target.value = "";
};

const Component = asChild ? (Slot as any) : Button;

return (
<>
<Component ref={ref} type="button" onClick={handleClick} {...props}>
{children}
</Component>
<input
ref={inputRef}
type="file"
accept={accept}
multiple={multiple}
onChange={handleChange}
disabled={isDisabled}
className="hidden"
aria-hidden
/>
</>
);
}
);

FileButton.displayName = "FileButton";
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ function useUpdateLayout({
node.unlock();
});
}

previousNodesRef.current = new Set(nodesInGraph.map(node => node.id()));
previousLayoutRef.current = layout;
}

// Ensure the previousNodesRef is updated on every run
previousNodesRef.current = new Set(nodesInGraph.map(node => node.id()));
}, [
cy,
layout,
Expand Down
2 changes: 2 additions & 0 deletions packages/graph-explorer/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export { default as PanelError } from "./PanelError";

export { default as Divider } from "./Divider";

export * from "./FileButton";

export { default as Graph } from "./Graph";
export * from "./Graph";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useCallback } from "react";
import { useRecoilValue } from "recoil";
import { SaveIcon } from "lucide-react";
import { nodesAtom, edgesAtom, useExplorer, useConfiguration } from "@/core";
import { saveFile, toJsonFileData } from "@/utils/fileData";
import { PanelHeaderActionButton } from "@/components";
import { createDefaultFileName, createExportedGraph } from "./exportedGraph";

export function ExportGraphButton() {
const exportGraph = useExportGraph();

return (
<PanelHeaderActionButton
icon={<SaveIcon />}
label="Save graph to file"
onActionClick={() => exportGraph()}
/>
);
}

export function useExportGraph() {
const vertexIds = useRecoilValue(nodesAtom).keys().toArray();
const edgeIds = useRecoilValue(edgesAtom).keys().toArray();
const connection = useExplorer().connection;
kmcginnes marked this conversation as resolved.
Show resolved Hide resolved
const config = useConfiguration();

const exportGraph = useCallback(async () => {
const fileName = createDefaultFileName(
config?.displayLabel ?? "Connection"
);
const exportData = createExportedGraph(vertexIds, edgeIds, connection);
const fileToSave = toJsonFileData(exportData);
await saveFile(fileToSave, fileName);
}, [config?.displayLabel, connection, vertexIds, edgeIds]);

return exportGraph;
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import useGraphStyles from "./useGraphStyles";
import useNodeBadges from "./useNodeBadges";
import { SelectedElements } from "@/components/Graph/Graph.model";
import { useAutoOpenDetailsSidebar } from "./useAutoOpenDetailsSidebar";
import { ImportGraphButton } from "./ImportGraphButton";
import { ExportGraphButton } from "./ExportGraphButton";
import {
BadgeInfoIcon,
CircleSlash2,
Expand Down Expand Up @@ -204,6 +206,8 @@ export default function GraphViewer({
icon={<ImageDownIcon />}
onActionClick={onSaveScreenshot}
/>
<ExportGraphButton />
<ImportGraphButton />
<PanelHeaderDivider />
<PanelHeaderActionButton
label="Zoom in"
Expand Down
Loading