Skip to content

Commit ab3c6d5

Browse files
authored
Add ability to see full error message (#858)
* Add radix dialog component * Add error details button * Ensure text wraps properly * Update changelog
1 parent 7ab506f commit ab3c6d5

File tree

6 files changed

+253
-10
lines changed

6 files changed

+253
-10
lines changed

Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
- **Added** ability to restore the graph from the previous session
88
([#826](https://github.com/aws/graph-explorer/pull/826),
99
[#840](https://github.com/aws/graph-explorer/pull/840))
10+
- **Added** ability to see full error details from errors in the UI
11+
([#858](https://github.com/aws/graph-explorer/pull/858))
1012
- **Added** query editor for Gremlin connections
1113
([#848](https://github.com/aws/graph-explorer/pull/848),
1214
[#853](https://github.com/aws/graph-explorer/pull/853),

packages/graph-explorer/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@mantine/emotion": "^7.17.1",
2424
"@mantine/hooks": "^7.17.1",
2525
"@radix-ui/react-checkbox": "^1.1.4",
26+
"@radix-ui/react-dialog": "^1.1.6",
2627
"@radix-ui/react-form": "^0.1.2",
2728
"@radix-ui/react-label": "^2.1.2",
2829
"@radix-ui/react-popover": "^1.1.6",
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import * as React from "react";
2+
import * as DialogPrimitive from "@radix-ui/react-dialog";
3+
import { cn } from "@/utils";
4+
import { XIcon } from "lucide-react";
5+
6+
const Dialog = DialogPrimitive.Root;
7+
8+
const DialogTrigger = DialogPrimitive.Trigger;
9+
10+
const DialogPortal = DialogPrimitive.Portal;
11+
12+
const DialogClose = DialogPrimitive.Close;
13+
14+
const DialogOverlay = React.forwardRef<
15+
React.ElementRef<typeof DialogPrimitive.Overlay>,
16+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
17+
>(({ className, ...props }, ref) => (
18+
<DialogPrimitive.Overlay
19+
ref={ref}
20+
className={cn(
21+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
22+
className
23+
)}
24+
{...props}
25+
/>
26+
));
27+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
28+
29+
const DialogContent = React.forwardRef<
30+
React.ElementRef<typeof DialogPrimitive.Content>,
31+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
32+
>(({ className, children, ...props }, ref) => (
33+
<DialogPortal>
34+
<DialogOverlay />
35+
<div className="fixed inset-0 z-50 flex h-full w-full items-center justify-center p-20">
36+
<DialogPrimitive.Content
37+
ref={ref}
38+
className={cn(
39+
"bg-background-default data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 relative grid max-h-full max-w-lg overflow-y-auto duration-200 sm:rounded-lg",
40+
className
41+
)}
42+
{...props}
43+
>
44+
{children}
45+
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-4 top-4 rounded-sm p-2 opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none">
46+
<XIcon className="h-5 w-5" />
47+
<span className="sr-only">Close</span>
48+
</DialogPrimitive.Close>
49+
</DialogPrimitive.Content>
50+
</div>
51+
</DialogPortal>
52+
));
53+
DialogContent.displayName = DialogPrimitive.Content.displayName;
54+
55+
const DialogHeader = ({
56+
className,
57+
...props
58+
}: React.HTMLAttributes<HTMLDivElement>) => (
59+
<div
60+
className={cn(
61+
"flex flex-col space-y-1.5 p-6 pb-3 text-center sm:text-left",
62+
className
63+
)}
64+
{...props}
65+
/>
66+
);
67+
DialogHeader.displayName = "DialogHeader";
68+
69+
const DialogBody = ({
70+
className,
71+
...props
72+
}: React.HTMLAttributes<HTMLDivElement>) => (
73+
<div
74+
className={cn(
75+
"flex flex-col gap-5 p-6 text-center sm:text-left",
76+
className
77+
)}
78+
{...props}
79+
/>
80+
);
81+
DialogBody.displayName = "DialogBody";
82+
83+
const DialogFooter = ({
84+
className,
85+
...props
86+
}: React.HTMLAttributes<HTMLDivElement>) => (
87+
<div
88+
className={cn(
89+
"flex flex-col-reverse bg-gray-100 p-6 sm:flex-row sm:justify-end sm:space-x-2",
90+
className
91+
)}
92+
{...props}
93+
/>
94+
);
95+
DialogFooter.displayName = "DialogFooter";
96+
97+
const DialogTitle = React.forwardRef<
98+
React.ElementRef<typeof DialogPrimitive.Title>,
99+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
100+
>(({ className, ...props }, ref) => (
101+
<DialogPrimitive.Title
102+
ref={ref}
103+
className={cn("text-lg font-bold leading-none tracking-tight", className)}
104+
{...props}
105+
/>
106+
));
107+
DialogTitle.displayName = DialogPrimitive.Title.displayName;
108+
109+
const DialogDescription = React.forwardRef<
110+
React.ElementRef<typeof DialogPrimitive.Description>,
111+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
112+
>(({ className, ...props }, ref) => (
113+
<DialogPrimitive.Description
114+
ref={ref}
115+
className={cn("text-muted-foreground text-sm", className)}
116+
{...props}
117+
/>
118+
));
119+
DialogDescription.displayName = DialogPrimitive.Description.displayName;
120+
121+
export {
122+
Dialog,
123+
DialogPortal,
124+
DialogOverlay,
125+
DialogTrigger,
126+
DialogClose,
127+
DialogContent,
128+
DialogBody,
129+
DialogHeader,
130+
DialogFooter,
131+
DialogTitle,
132+
DialogDescription,
133+
};
Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,27 @@
11
import { createDisplayError } from "@/utils/createDisplayError";
2-
import { PanelEmptyState } from "./PanelEmptyState";
32
import { GraphIcon } from "./icons";
3+
import { Button } from "./Button";
4+
import {
5+
EmptyState,
6+
EmptyStateIcon,
7+
EmptyStateContent,
8+
EmptyStateTitle,
9+
EmptyStateDescription,
10+
EmptyStateActions,
11+
} from "./EmptyState";
12+
import { InfoIcon, RotateCcwIcon } from "lucide-react";
13+
import {
14+
Dialog,
15+
DialogBody,
16+
DialogClose,
17+
DialogContent,
18+
DialogFooter,
19+
DialogHeader,
20+
DialogTitle,
21+
DialogTrigger,
22+
} from "./Dialog";
23+
import { Label } from "./Label";
24+
import { FormItem } from "./Form";
425

526
export default function PanelError({
627
error,
@@ -13,14 +34,61 @@ export default function PanelError({
1334
}) {
1435
const displayError = createDisplayError(error);
1536
return (
16-
<PanelEmptyState
17-
variant="error"
18-
icon={<GraphIcon />}
19-
title={displayError.title}
20-
subtitle={displayError.message}
21-
onAction={onRetry}
22-
actionLabel={onRetry ? "Retry" : undefined}
23-
className={className}
24-
/>
37+
<EmptyState className={className}>
38+
<EmptyStateIcon variant="error">
39+
<GraphIcon />
40+
</EmptyStateIcon>
41+
<EmptyStateContent>
42+
<EmptyStateTitle>{displayError.title}</EmptyStateTitle>
43+
<EmptyStateDescription>{displayError.message}</EmptyStateDescription>
44+
45+
<EmptyStateActions>
46+
<ErrorDetailsButton error={error} />
47+
{onRetry ? (
48+
<Button onPress={onRetry}>
49+
<RotateCcwIcon />
50+
Retry
51+
</Button>
52+
) : null}
53+
</EmptyStateActions>
54+
</EmptyStateContent>
55+
</EmptyState>
56+
);
57+
}
58+
59+
function ErrorDetailsButton({ error }: { error: Error }) {
60+
return (
61+
<Dialog>
62+
<DialogTrigger asChild>
63+
<Button>
64+
<InfoIcon />
65+
Error Details
66+
</Button>
67+
</DialogTrigger>
68+
<DialogContent>
69+
<DialogHeader>
70+
<DialogTitle>Error Details</DialogTitle>
71+
</DialogHeader>
72+
<DialogBody>
73+
<FormItem>
74+
<Label>Error name</Label>
75+
<div className="text-pretty text-base leading-snug [word-break:break-word]">
76+
{error.name}
77+
</div>
78+
</FormItem>
79+
<FormItem>
80+
<Label>Error message</Label>
81+
<div className="text-pretty text-base leading-snug [word-break:break-word]">
82+
{error.message}
83+
</div>
84+
</FormItem>
85+
</DialogBody>
86+
<DialogFooter>
87+
<DialogClose asChild>
88+
<Button variant="filled">Close</Button>
89+
</DialogClose>
90+
</DialogFooter>
91+
</DialogContent>
92+
</Dialog>
2593
);
2694
}

packages/graph-explorer/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export { PanelEmptyState } from "./PanelEmptyState";
1414
export * from "./PanelEmptyState";
1515
export { default as PanelError } from "./PanelError";
1616

17+
export * from "./Dialog";
1718
export { default as Divider } from "./Divider";
1819

1920
export * from "./EdgeRow";

pnpm-lock.yaml

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)