Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { toast } from "sonner";

import { fetchEntityDetails, notifyOnIncompleteRestoration } from "@/connector";
import { useAddToGraph } from "@/hooks";
import { formatEntityCounts, logger } from "@/utils";
import { useEntityCountFormatterCallback } from "@/hooks/useEntityCountFormatter";
import { logger } from "@/utils";
import { createDisplayError } from "@/utils/createDisplayError";

import type { GraphSessionStorageModel } from "./storage";
Expand All @@ -14,6 +15,7 @@ import type { GraphSessionStorageModel } from "./storage";
export function useRestoreGraphSession() {
const queryClient = useQueryClient();
const addToGraph = useAddToGraph();
const formatEntityCounts = useEntityCountFormatterCallback();

const mutation = useMutation({
mutationFn: async (graph: GraphSessionStorageModel) => {
Expand Down
89 changes: 89 additions & 0 deletions packages/graph-explorer/src/hooks/useEntityCountFormatter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { vi } from "vitest";

import { renderHookWithJotai } from "@/utils/testing";

import {
useEntityCountFormatterCallback,
useFormattedEntityCounts,
} from "./useEntityCountFormatter";

// Mock useTranslations
vi.mock("./useTranslations", () => ({
default: () => (key: string) => {
const translations: Record<string, string> = {
node: "node",
nodes: "nodes",
edge: "edge",
edges: "edges",
};
return translations[key] || key;
},
}));

describe("useFormattedEntityCounts", () => {
it("should format single node and single edge", () => {
const { result } = renderHookWithJotai(() =>
useFormattedEntityCounts(1, 1),
);
expect(result.current).toBe("1 node and 1 edge");
});

it("should format multiple nodes and multiple edges", () => {
const { result } = renderHookWithJotai(() =>
useFormattedEntityCounts(5, 3),
);
expect(result.current).toBe("5 nodes and 3 edges");
});

it("should format only nodes when edge count is zero", () => {
const { result } = renderHookWithJotai(() =>
useFormattedEntityCounts(2, 0),
);
expect(result.current).toBe("2 nodes");
});

it("should format only edges when node count is zero", () => {
const { result } = renderHookWithJotai(() =>
useFormattedEntityCounts(0, 4),
);
expect(result.current).toBe("4 edges");
});

it("should return empty string when both counts are zero", () => {
const { result } = renderHookWithJotai(() =>
useFormattedEntityCounts(0, 0),
);
expect(result.current).toBe("");
});

it("should format large numbers with locale formatting", () => {
const { result } = renderHookWithJotai(() =>
useFormattedEntityCounts(1000, 2500),
);
expect(result.current).toBe("1,000 nodes and 2,500 edges");
});
});

describe("useEntityCountFormatterCallback", () => {
it("should return a function that formats counts", () => {
const { result } = renderHookWithJotai(() =>
useEntityCountFormatterCallback(),
);

expect(typeof result.current).toBe("function");
expect(result.current(1, 1)).toBe("1 node and 1 edge");
});

it("should handle different count combinations", () => {
const { result } = renderHookWithJotai(() =>
useEntityCountFormatterCallback(),
);

const format = result.current;

expect(format(0, 0)).toBe("");
expect(format(1, 0)).toBe("1 node");
expect(format(0, 1)).toBe("1 edge");
expect(format(10, 5)).toBe("10 nodes and 5 edges");
});
});
29 changes: 29 additions & 0 deletions packages/graph-explorer/src/hooks/useEntityCountFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import useTranslations from "./useTranslations";

export function useFormattedEntityCounts(
vertexCount: number,
edgeCount: number,
) {
const format = useEntityCountFormatterCallback();
return format(vertexCount, edgeCount);
}

export function useEntityCountFormatterCallback() {
const t = useTranslations();

const format = (vertexCount: number, edgeCount: number) => {
const nodeMessage =
vertexCount === 0
? null
: `${vertexCount.toLocaleString()} ${vertexCount === 1 ? t("node") : t("nodes")}`.toLocaleLowerCase();
const edgeMessage =
edgeCount === 0
? null
: `${edgeCount.toLocaleString()} ${edgeCount === 1 ? t("edge") : t("edges")}`.toLocaleLowerCase();

return [nodeMessage, edgeMessage]
.filter(message => message != null)
.join(" and ");
};
return format;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
useAvailablePreviousSession,
useRestoreGraphSession,
} from "@/core";
import { cn, formatEntityCounts } from "@/utils";
import { useFormattedEntityCounts } from "@/hooks/useEntityCountFormatter";
import { cn } from "@/utils";

export function GraphViewerEmptyState(props: ComponentPropsWithRef<"div">) {
const availablePrevSession = useAvailablePreviousSession();
Expand All @@ -35,7 +36,7 @@ function RestorePreviousSessionEmptyState({
} & ComponentPropsWithRef<"div">) {
const restore = useRestoreGraphSession();

const entityCounts = formatEntityCounts(
const entityCounts = useFormattedEntityCounts(
prevSession.vertices.size,
prevSession.edges.size,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { FileButton, PanelHeaderActionButton, Spinner } from "@/components";
import { fetchEntityDetails, notifyOnIncompleteRestoration } from "@/connector";
import { configurationAtom, type ConnectionWithId, useExplorer } from "@/core";
import { useAddToGraph } from "@/hooks";
import { useEntityCountFormatterCallback } from "@/hooks/useEntityCountFormatter";
import { getTranslation } from "@/hooks/useTranslations";
import { formatEntityCounts, logger } from "@/utils";
import { logger } from "@/utils";
import { fromFileToJson } from "@/utils/fileData";

import {
Expand Down Expand Up @@ -40,6 +41,7 @@ function useImportGraphMutation() {
const queryClient = useQueryClient();
const explorer = useExplorer();
const addToGraph = useAddToGraph();
const formatEntityCounts = useEntityCountFormatterCallback();
const allConfigs = useAtomValue(configurationAtom);
const allConnections = allConfigs
.values()
Expand Down