Skip to content

Commit 096efbc

Browse files
authored
fix: do not show errors for component items in left panel in case of network failure (#1473)
## Description Closes #1455 Enhanced error handling and loading states for component items in the Flow Sidebar. Added proper error fallbacks for the `ComponentIcon` and `ComponentItemFromUrl` components, and implemented a skeleton loading state for component items. Also improved the `UpgradeAvailableAlertBox` with an error fallback. ## Type of Change - [x] Bug fix ## Checklist - [x] I have tested this does not break current pipelines / runs functionality - [ ] I have tested the changes on staging ## Instructions 1. Halt backend locally 2. Try to open component folders in left panel 3. Ensure components are still available and visually appealing. [Screen Recording 2025-12-04 at 2.59.45 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.com/user-attachments/thumbnails/e063ce93-c799-4fe3-a7f4-01a09887fb94.mov" />](https://app.graphite.com/user-attachments/video/e063ce93-c799-4fe3-a7f4-01a09887fb94.mov) ## Additional Comments Key changes include: - Added a type guard function `hasProps` to safely check component icon properties - Created a `ComponentItemSkeleton` for better loading states - Added error fallbacks to prevent crashes when components fail to load - Fixed layout issues by adding `wrap="nowrap"` to prevent component names from breaking - Improved the suspense wrapper implementation for better error handling
1 parent f3813f9 commit 096efbc

File tree

2 files changed

+107
-63
lines changed

2 files changed

+107
-63
lines changed

src/components/shared/ReactFlow/FlowSidebar/components/ComponentItem.tsx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { withSuspenseWrapper } from "@/components/shared/SuspenseWrapper";
99
import { Icon } from "@/components/ui/icon";
1010
import { InlineStack } from "@/components/ui/layout";
1111
import { SidebarMenuItem } from "@/components/ui/sidebar";
12+
import { Skeleton } from "@/components/ui/skeleton";
13+
import { Spinner } from "@/components/ui/spinner";
1214
import { useHydrateComponentReference } from "@/hooks/useHydrateComponentReference";
1315
import { cn } from "@/lib/utils";
1416
import { useComponentLibrary } from "@/providers/ComponentLibraryProvider";
@@ -34,6 +36,16 @@ const ComponentIconSkeleton = ({
3436
return <Icon name="File" className="shrink-0 text-gray-400" {...iconProps} />;
3537
};
3638

39+
function hasProps(props: any): props is ComponentIconProps {
40+
return (
41+
props !== undefined &&
42+
props !== null &&
43+
typeof props === "object" &&
44+
"name" in props &&
45+
"className" in props
46+
);
47+
}
48+
3749
const ComponentIcon = withSuspenseWrapper(
3850
({ component, className, ...iconProps }: ComponentIconProps) => {
3951
const { data: outdatedComponents } = useOutdatedComponents([component]);
@@ -46,6 +58,15 @@ const ComponentIcon = withSuspenseWrapper(
4658
return <Icon name="BookAlert" className="text-orange-500" />;
4759
},
4860
ComponentIconSkeleton,
61+
/**
62+
* Error fallback to show just the icon
63+
*/
64+
({ originalProps }) =>
65+
hasProps(originalProps) ? (
66+
<Icon name={originalProps.name} className={originalProps.className} />
67+
) : (
68+
<Icon name="File" className="shrink-0 text-gray-400" />
69+
),
4970
);
5071

5172
const ComponentMarkup = ({
@@ -174,7 +195,12 @@ const ComponentMarkup = ({
174195
data-testid="component-item"
175196
data-component-name={displayName}
176197
>
177-
<InlineStack gap="2" blockAlign="center" className="w-full">
198+
<InlineStack
199+
gap="2"
200+
blockAlign="center"
201+
className="w-full"
202+
wrap="nowrap"
203+
>
178204
{isRemoteComponentLibrarySearchEnabled ? (
179205
<ComponentIcon
180206
name={iconName}
@@ -218,6 +244,17 @@ const ComponentMarkup = ({
218244
);
219245
};
220246

247+
const ComponentItemSkeleton = () => {
248+
return (
249+
<SidebarMenuItem className="pl-2 py-1.5">
250+
<InlineStack blockAlign="center" gap="2">
251+
<Spinner size={10} />
252+
<Skeleton size="sm" color="default" />
253+
</InlineStack>
254+
</SidebarMenuItem>
255+
);
256+
};
257+
221258
const ComponentItemFromUrl = withSuspenseWrapper(
222259
({ componentRef }: { componentRef: ComponentReference }) => {
223260
const hydratedComponent = useHydrateComponentReference(componentRef);
@@ -226,6 +263,8 @@ const ComponentItemFromUrl = withSuspenseWrapper(
226263

227264
return <ComponentMarkup component={hydratedComponent} />;
228265
},
266+
ComponentItemSkeleton,
267+
() => null,
229268
);
230269

231270
interface IONodeSidebarItemProps {

src/components/shared/ReactFlow/FlowSidebar/components/UpgradeAvailableAlertBox.tsx

Lines changed: 67 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -26,82 +26,87 @@ const UpgradeAvailableAlertBoxSkeleton = () => {
2626
*
2727
* @returns The UpgradeAvailableAlertBox component
2828
*/
29-
export const UpgradeAvailableAlertBox = withSuspenseWrapper(() => {
30-
const notify = useToastNotification();
31-
const [dismissed, setDismissed] = useDissmissedStorage();
29+
export const UpgradeAvailableAlertBox = withSuspenseWrapper(
30+
() => {
31+
const notify = useToastNotification();
32+
const [dismissed, setDismissed] = useDissmissedStorage();
3233

33-
const { usedComponentsFolder } = useComponentLibrary();
34+
const { usedComponentsFolder } = useComponentLibrary();
3435

35-
const { data: outdatedComponents } = useOutdatedComponents(
36-
usedComponentsFolder.components ?? [],
37-
);
36+
const { data: outdatedComponents } = useOutdatedComponents(
37+
usedComponentsFolder.components ?? [],
38+
);
3839

39-
const { notifyNode, getNodeIdsByDigest, fitNodeIntoView } = useNodesOverlay();
40+
const { notifyNode, getNodeIdsByDigest, fitNodeIntoView } =
41+
useNodesOverlay();
4042

41-
const dismissCallback = useCallback(() => {
42-
setDismissed();
43-
notify("Upgrade alert dismissed for next 24 hours", "success");
44-
}, [setDismissed, notify]);
43+
const dismissCallback = useCallback(() => {
44+
setDismissed();
45+
notify("Upgrade alert dismissed for next 24 hours", "success");
46+
}, [setDismissed, notify]);
4547

46-
const upgradeAllComponentsCallback = useCallback(async () => {
47-
if (outdatedComponents.length === 0) {
48-
return;
49-
}
48+
const upgradeAllComponentsCallback = useCallback(async () => {
49+
if (outdatedComponents.length === 0) {
50+
return;
51+
}
5052

51-
const nodeIds = outdatedComponents.flatMap(([outdated, _]) =>
52-
getNodeIdsByDigest(outdated.digest),
53-
);
53+
const nodeIds = outdatedComponents.flatMap(([outdated, _]) =>
54+
getNodeIdsByDigest(outdated.digest),
55+
);
5456

55-
if (nodeIds.length === 0) {
56-
return;
57-
}
57+
if (nodeIds.length === 0) {
58+
return;
59+
}
5860

59-
const nodeId = nodeIds.pop();
61+
const nodeId = nodeIds.pop();
6062

61-
if (!nodeId) {
62-
return;
63-
}
63+
if (!nodeId) {
64+
return;
65+
}
6466

65-
await fitNodeIntoView(nodeId);
67+
await fitNodeIntoView(nodeId);
6668

67-
notifyNode(nodeId, {
68-
type: "update-overlay",
69-
data: {
70-
replaceWith: new Map(
71-
outdatedComponents.map(([outdated, mrc]) => [outdated.digest, mrc]),
72-
),
73-
ids: nodeIds,
74-
},
75-
});
76-
}, [getNodeIdsByDigest, fitNodeIntoView, notifyNode, outdatedComponents]);
69+
notifyNode(nodeId, {
70+
type: "update-overlay",
71+
data: {
72+
replaceWith: new Map(
73+
outdatedComponents.map(([outdated, mrc]) => [outdated.digest, mrc]),
74+
),
75+
ids: nodeIds,
76+
},
77+
});
78+
}, [getNodeIdsByDigest, fitNodeIntoView, notifyNode, outdatedComponents]);
7779

78-
const showOutdatedComponentsAlert =
79-
outdatedComponents.length > 0 && !dismissed;
80+
const showOutdatedComponentsAlert =
81+
outdatedComponents.length > 0 && !dismissed;
8082

81-
if (!showOutdatedComponentsAlert) {
82-
return null;
83-
}
83+
if (!showOutdatedComponentsAlert) {
84+
return null;
85+
}
8486

85-
return (
86-
<BlockStack className="px-2">
87-
<InfoBox title="Upgrades available" key="outdated-components">
88-
<BlockStack gap="2">
89-
<Paragraph size="xs">
90-
You have outdated components used in your Pipeline.
91-
</Paragraph>
92-
<InlineStack align="space-between" className="w-full">
93-
<Button size="xs" variant="secondary" onClick={dismissCallback}>
94-
Dismiss
95-
</Button>
96-
<Button size="xs" onClick={upgradeAllComponentsCallback}>
97-
Review
98-
</Button>
99-
</InlineStack>
100-
</BlockStack>
101-
</InfoBox>
102-
</BlockStack>
103-
);
104-
}, UpgradeAvailableAlertBoxSkeleton);
87+
return (
88+
<BlockStack className="px-2">
89+
<InfoBox title="Upgrades available" key="outdated-components">
90+
<BlockStack gap="2">
91+
<Paragraph size="xs">
92+
You have outdated components used in your Pipeline.
93+
</Paragraph>
94+
<InlineStack align="space-between" className="w-full">
95+
<Button size="xs" variant="secondary" onClick={dismissCallback}>
96+
Dismiss
97+
</Button>
98+
<Button size="xs" onClick={upgradeAllComponentsCallback}>
99+
Review
100+
</Button>
101+
</InlineStack>
102+
</BlockStack>
103+
</InfoBox>
104+
</BlockStack>
105+
);
106+
},
107+
UpgradeAvailableAlertBoxSkeleton,
108+
() => null,
109+
);
105110

106111
interface DismissedStorage {
107112
upgradeAvailableAlertDismissed: Date | undefined;

0 commit comments

Comments
 (0)