Skip to content

Commit da2c213

Browse files
author
Hrithik-Gavankar
committed
fix(NGUI-176): improve error handling and fix test cases
1 parent d562278 commit da2c213

12 files changed

+216
-132
lines changed

src/components/DynamicComponents.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ const DynamicComponent = ({ config, customProps = {} }: IProps) => {
9090
const Component = componentsMap[config?.component];
9191

9292
if (!Component) {
93-
// Throw an error for missing components so ErrorBoundary can catch it
94-
throw new Error(`Component "${config?.component}" is not available in the React package. Available components: ${Object.keys(componentsMap).join(', ')}`);
93+
// Return null for unknown components instead of throwing an error
94+
console.warn(`Component "${config?.component}" is not available in the React package. Available components: ${Object.keys(componentsMap).join(', ')}`);
95+
return null;
9596
}
9697

9798
const newProps = parseProps(config?.props || config);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from "react";
2+
3+
interface ErrorPlaceholderProps {
4+
hasError: boolean;
5+
errorMessage?: string;
6+
noContentMessage?: string;
7+
className?: string;
8+
}
9+
10+
const ErrorPlaceholder: React.FC<ErrorPlaceholderProps> = ({
11+
hasError,
12+
errorMessage = "Content failed to load",
13+
noContentMessage = "No content available",
14+
className = "",
15+
}) => {
16+
const message = hasError ? errorMessage : noContentMessage;
17+
const errorClass = hasError ? "error-state" : "";
18+
19+
return (
20+
<div className={`error-placeholder ${errorClass} ${className}`}>
21+
{message}
22+
</div>
23+
);
24+
};
25+
26+
export default ErrorPlaceholder;

src/components/ImageComponent.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Card, CardBody, CardHeader, CardTitle } from "@patternfly/react-core";
22
import React, { useState } from "react";
33

4+
import ErrorPlaceholder from "./ErrorPlaceholder";
5+
46
interface ImageComponentProps {
57
component: "image";
68
id: string;
@@ -15,25 +17,31 @@ const ImageComponent: React.FC<ImageComponentProps> = ({
1517
title,
1618
className,
1719
}) => {
18-
const [imageError, setImageError] = useState(false);
20+
const [hasImageError, setHasImageError] = useState(false);
21+
22+
const handleImageError = () => {
23+
setHasImageError(true);
24+
};
1925

2026
return (
2127
<Card id={id} className={className}>
2228
<CardHeader>
2329
<CardTitle>{title}</CardTitle>
2430
</CardHeader>
2531
<CardBody>
26-
{image && !imageError ? (
32+
{image && !hasImageError ? (
2733
<img
2834
src={image}
2935
alt={title}
3036
className="image-component-img"
31-
onError={() => setImageError(true)}
37+
onError={handleImageError}
3238
/>
3339
) : (
34-
<div className="image-component-placeholder">
35-
{imageError ? "Image failed to load" : "No image provided"}
36-
</div>
40+
<ErrorPlaceholder
41+
hasError={hasImageError}
42+
errorMessage="Image failed to load"
43+
noContentMessage="No image provided"
44+
/>
3745
)}
3846
</CardBody>
3947
</Card>

src/components/OneCardWrapper.tsx

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
FlexItem,
1111
Title,
1212
} from "@patternfly/react-core";
13-
import React from "react";
13+
import React, { useState } from "react";
14+
15+
import ErrorPlaceholder from "./ErrorPlaceholder";
1416

1517
interface DataField {
1618
name: string;
@@ -35,6 +37,29 @@ const OneCardWrapper: React.FC<OneCardProps> = ({
3537
imageSize = "md",
3638
className,
3739
}) => {
40+
const [hasImageError, setHasImageError] = useState(false);
41+
42+
const handleImageError = () => {
43+
setHasImageError(true);
44+
};
45+
46+
// Check for missing or invalid data
47+
const hasNoFields = !fields || fields.length === 0;
48+
const hasNoTitle = !title || title.trim() === "";
49+
50+
// If no title and no fields, show error
51+
if (hasNoTitle && hasNoFields) {
52+
return (
53+
<Card id={id} className={`onecard-component-container ${className || ''}`}>
54+
<CardBody>
55+
<ErrorPlaceholder
56+
hasError={false}
57+
noContentMessage="No content available"
58+
/>
59+
</CardBody>
60+
</Card>
61+
);
62+
}
3863

3964
return (
4065
<Card
@@ -44,15 +69,24 @@ const OneCardWrapper: React.FC<OneCardProps> = ({
4469
<CardBody>
4570
<Flex spaceItems={{ default: "spaceItemsLg" }} alignItems={{ default: "alignItemsFlexStart" }}>
4671
{/* Left Column - Image */}
47-
{image && (
72+
{image && !hasImageError ? (
4873
<FlexItem className={`onecard-component-image-container size-${imageSize}`}>
4974
<img
5075
src={image}
5176
alt={title}
5277
className="onecard-component-img"
78+
onError={handleImageError}
79+
/>
80+
</FlexItem>
81+
) : image && hasImageError ? (
82+
<FlexItem className={`onecard-component-image-container size-${imageSize}`}>
83+
<ErrorPlaceholder
84+
hasError={true}
85+
errorMessage="Image failed to load"
86+
noContentMessage=""
5387
/>
5488
</FlexItem>
55-
)}
89+
) : null}
5690

5791
{/* Right Column - Title + Fields */}
5892
<FlexItem grow={{ default: "grow" }}>
@@ -61,18 +95,25 @@ const OneCardWrapper: React.FC<OneCardProps> = ({
6195
</Title>
6296
<Divider component="div" className="onecard-component-divider" />
6397
<div>
64-
<DescriptionList isAutoFit>
65-
{fields?.map((field, idx) => (
66-
<DescriptionListGroup key={idx}>
67-
<DescriptionListTerm>{field.name}</DescriptionListTerm>
68-
<DescriptionListDescription>
69-
{field.data.map((item) =>
70-
item === null ? "N/A" : String(item)
71-
).join(", ")}
72-
</DescriptionListDescription>
73-
</DescriptionListGroup>
74-
))}
75-
</DescriptionList>
98+
{hasNoFields ? (
99+
<ErrorPlaceholder
100+
hasError={false}
101+
noContentMessage="No data fields available"
102+
/>
103+
) : (
104+
<DescriptionList isAutoFit>
105+
{fields?.map((field, idx) => (
106+
<DescriptionListGroup key={idx}>
107+
<DescriptionListTerm>{field.name}</DescriptionListTerm>
108+
<DescriptionListDescription>
109+
{field.data.map((item) =>
110+
item === null ? "N/A" : String(item)
111+
).join(", ")}
112+
</DescriptionListDescription>
113+
</DescriptionListGroup>
114+
))}
115+
</DescriptionList>
116+
)}
76117
</div>
77118
</FlexItem>
78119
</Flex>

src/components/TableWrapper.tsx

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
Caption,
1010
} from "@patternfly/react-table";
1111

12+
import ErrorPlaceholder from "./ErrorPlaceholder";
13+
1214
interface FieldData {
1315
name: string;
1416
data_path: string;
@@ -25,9 +27,14 @@ interface TableWrapperProps {
2527

2628
const TableWrapper = (props: TableWrapperProps) => {
2729
const { title, id, fields, className } = props;
30+
31+
// Check for missing or invalid data
32+
const hasNoFields = !fields || fields.length === 0;
33+
const hasNoTitle = !title || title.trim() === "";
34+
2835
// Transform fields data into table format
2936
const transformFieldsToTableData = () => {
30-
if (!fields || fields.length === 0) return { columns: [], rows: [] };
37+
if (hasNoFields) return { columns: [], rows: [] };
3138

3239
// Find the maximum number of data items across all fields
3340
const maxDataLength = Math.max(...fields.map((field) => field.data.length));
@@ -59,29 +66,51 @@ const TableWrapper = (props: TableWrapperProps) => {
5966
};
6067

6168
const { columns, rows } = transformFieldsToTableData();
69+
const hasNoData = rows.length === 0;
70+
71+
// If no title and no fields, show error
72+
if (hasNoTitle && hasNoFields) {
73+
return (
74+
<Card id={id} className={className}>
75+
<CardBody>
76+
<ErrorPlaceholder
77+
hasError={false}
78+
noContentMessage="No content available"
79+
/>
80+
</CardBody>
81+
</Card>
82+
);
83+
}
6284

6385
return (
6486
<Card id={id} className={className}>
6587
<CardBody>
66-
<Table variant="compact" borders>
67-
<Caption>{title}</Caption>
68-
<Thead>
69-
<Tr>
70-
{columns.map((col, index) => (
71-
<Th key={index}>{col.label}</Th>
72-
))}
73-
</Tr>
74-
</Thead>
75-
<Tbody>
76-
{rows.map((row, rowIndex) => (
77-
<Tr key={rowIndex} data-testid={`row-${rowIndex}`}>
78-
{columns.map((col, colIndex) => (
79-
<Td key={colIndex}>{row[col.key]}</Td>
88+
{hasNoData ? (
89+
<ErrorPlaceholder
90+
hasError={false}
91+
noContentMessage="No data available"
92+
/>
93+
) : (
94+
<Table variant="compact" borders>
95+
<Caption>{title}</Caption>
96+
<Thead>
97+
<Tr>
98+
{columns.map((col, index) => (
99+
<Th key={index}>{col.label}</Th>
80100
))}
81101
</Tr>
82-
))}
83-
</Tbody>
84-
</Table>
102+
</Thead>
103+
<Tbody>
104+
{rows.map((row, rowIndex) => (
105+
<Tr key={rowIndex} data-testid={`row-${rowIndex}`}>
106+
{columns.map((col, colIndex) => (
107+
<Td key={colIndex}>{row[col.key]}</Td>
108+
))}
109+
</Tr>
110+
))}
111+
</Tbody>
112+
</Table>
113+
)}
85114
</CardBody>
86115
</Card>
87116
);

src/components/VideoPlayerWrapper.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {
44
CardTitle,
55
Title,
66
} from "@patternfly/react-core";
7-
import React from "react";
7+
import React, { useState } from "react";
8+
9+
import ErrorPlaceholder from "./ErrorPlaceholder";
810

911
interface VideoPlayerProps {
1012
id?: string;
@@ -27,6 +29,12 @@ import {
2729
controls = true,
2830
aspectRatio = '16:9',
2931
}) => {
32+
const [hasVideoError, setHasVideoError] = useState(false);
33+
34+
const handleVideoError = () => {
35+
setHasVideoError(true);
36+
};
37+
3038
// Get aspect ratio class
3139
const getAspectRatioClass = () => {
3240
switch (aspectRatio) {
@@ -72,7 +80,7 @@ import {
7280
};
7381

7482
const renderVideoContent = () => {
75-
if (video) {
83+
if (video && !hasVideoError) {
7684
if (isYouTubeUrl(video)) {
7785
// YouTube video embedding
7886
return (
@@ -84,6 +92,7 @@ import {
8492
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
8593
referrerPolicy="strict-origin-when-cross-origin"
8694
allowFullScreen
95+
onError={handleVideoError}
8796
/>
8897
</div>
8998
);
@@ -96,27 +105,31 @@ import {
96105
autoPlay={autoPlay}
97106
title={title}
98107
className="video-player-video"
108+
onError={handleVideoError}
99109
>
100110
Your browser does not support the video tag.
101111
</video>
102112
);
103113
}
104-
} else if (video_img) {
114+
} else if (video_img && !hasVideoError) {
105115
// Show poster image when no video URL is provided
106116
return (
107117
<img
108118
src={video_img}
109119
alt={title}
110120
className="video-player-poster"
121+
onError={handleVideoError}
111122
/>
112123
);
113124
}
114125

115-
// Fallback when neither video nor poster image is available
126+
// Show error placeholder when there's an error or no content
116127
return (
117-
<div>
118-
No video content available
119-
</div>
128+
<ErrorPlaceholder
129+
hasError={hasVideoError}
130+
errorMessage="Video failed to load"
131+
noContentMessage="No video content available"
132+
/>
120133
);
121134
};
122135

0 commit comments

Comments
 (0)