Skip to content

Commit 5f79e44

Browse files
fix: [DI-29172] - Dependent API error handling in Edit Alert (#13379)
* fix: [DI-29172] - Dependent API error handling in Edit Alert * add changeset * fix assertion * revert assertion to visibility * replace queryByText with getByText
1 parent e5d1630 commit 5f79e44

25 files changed

+162
-34
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Fixed
3+
---
4+
5+
Error handling for dependent API failures in the ACLP - Edit Alert feature ([#13379](https://github.com/linode/manager/pull/13379))

packages/manager/src/features/CloudPulse/Alerts/AlertRegions/AlertRegions.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ interface AlertRegionsProps {
4343
* The service type for which the regions are being selected.
4444
*/
4545
serviceType: CloudPulseServiceType | null;
46+
/**
47+
* Callback to set error flag on API failure
48+
*/
49+
setError?: (hasError: boolean) => void;
4650
/**
4751
* The selected regions.
4852
*/
@@ -57,12 +61,17 @@ export const AlertRegions = React.memo((props: AlertRegionsProps) => {
5761
errorText,
5862
mode,
5963
scrollElement,
64+
setError,
6065
} = props;
6166
const [searchText, setSearchText] = React.useState<string>('');
62-
const { data: regions, isLoading: isRegionsLoading } = useRegionsQuery();
67+
const {
68+
data: regions,
69+
isLoading: isRegionsLoading,
70+
isError: isRegionsError,
71+
} = useRegionsQuery();
6372
const [selectedRegions, setSelectedRegions] = React.useState<string[]>(value);
6473
const [showSelected, setShowSelected] = React.useState<boolean>(false);
65-
const { data: resources, isLoading: isResourcesLoading } = useResourcesQuery(
74+
const { data: resources, isLoading: isResourcesLoading, isError } = useResourcesQuery(
6675
Boolean(serviceType && regions?.length),
6776
serviceType === null ? undefined : serviceType,
6877
{},
@@ -71,6 +80,13 @@ export const AlertRegions = React.memo((props: AlertRegionsProps) => {
7180
getFilterFn(serviceType)
7281
);
7382

83+
React.useEffect(() => {
84+
const hasError = isError || isRegionsError;
85+
if (setError) {
86+
setError(hasError);
87+
}
88+
}, [setError, isError, isRegionsError]);
89+
7490
const titleRef = React.useRef<HTMLDivElement>(null); // Reference to the component title, used for scrolling to the title when the table's page size or page number changes.
7591

7692
const handleSelectionChange = React.useCallback(

packages/manager/src/features/CloudPulse/Alerts/AlertsResources/AlertsResources.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,16 @@ describe('AlertResources component tests', () => {
7979
});
8080
it('should render circle progress if api calls are in fetching state', () => {
8181
queryMocks.useResourcesQuery.mockReturnValue({
82-
data: linodes,
82+
data: undefined,
8383
isError: false,
8484
isLoading: true,
8585
});
86-
const { getByTestId, queryByText } = renderWithTheme(
86+
const { getByTestId, getByText } = renderWithTheme(
8787
<AlertResources {...alertResourcesProp} />
8888
);
8989
expect(getByTestId('circle-progress')).toBeInTheDocument();
90-
expect(queryByText(searchPlaceholder)).not.toBeInTheDocument();
91-
expect(queryByText(regionPlaceholder)).not.toBeInTheDocument();
90+
expect(getByText(searchPlaceholder)).not.toBeVisible();
91+
expect(getByText(regionPlaceholder)).not.toBeVisible();
9292
});
9393

9494
it('should render error state if api call fails', () => {

packages/manager/src/features/CloudPulse/Alerts/AlertsResources/AlertsResources.tsx

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ export interface AlertResourcesProp {
104104
* The service type associated with the alerts like DBaaS, Linode etc.,
105105
*/
106106
serviceType?: CloudPulseServiceType;
107+
/**
108+
* Callback to set the error on API Failure
109+
*/
110+
setError?: (hasError: boolean) => void;
107111
}
108112

109113
export const AlertResources = React.memo((props: AlertResourcesProp) => {
@@ -120,6 +124,7 @@ export const AlertResources = React.memo((props: AlertResourcesProp) => {
120124
maxSelectionCount,
121125
scrollElement,
122126
serviceType,
127+
setError,
123128
} = props;
124129
const [searchText, setSearchText] = React.useState<string>();
125130
const [filteredRegions, setFilteredRegions] = React.useState<string[]>();
@@ -174,8 +179,8 @@ export const AlertResources = React.memo((props: AlertResourcesProp) => {
174179
const filteredTypes =
175180
alertClass === 'shared'
176181
? Object.keys(databaseTypeClassMap).filter(
177-
(type) => type !== 'dedicated'
178-
)
182+
(type) => type !== 'dedicated'
183+
)
179184
: [alertClass];
180185

181186
// Apply type filter only for DBaaS user alerts with a valid alertClass based on above filtered types
@@ -209,6 +214,13 @@ export const AlertResources = React.memo((props: AlertResourcesProp) => {
209214
filterFn
210215
);
211216

217+
React.useEffect(() => {
218+
const hasError = isResourcesError || isRegionsError;
219+
if (setError) {
220+
setError(hasError);
221+
}
222+
}, [setError, isResourcesError, isRegionsError]);
223+
212224
const regionFilteredResources = React.useMemo(() => {
213225
if (
214226
(serviceType === 'objectstorage' || serviceType === 'blockstorage') &&
@@ -354,10 +366,6 @@ export const AlertResources = React.memo((props: AlertResourcesProp) => {
354366
!isDataLoadingError && !isSelectionsNeeded && alertResourceIds.length === 0;
355367
const showEditInformation = isSelectionsNeeded && alertType === 'system';
356368

357-
if (isResourcesLoading || isRegionsLoading) {
358-
return <CircleProgress />;
359-
}
360-
361369
if (isNoResources) {
362370
return (
363371
<Stack gap={2}>
@@ -385,7 +393,6 @@ export const AlertResources = React.memo((props: AlertResourcesProp) => {
385393
serviceToFiltersMap[serviceType ?? ''] ?? serviceToFiltersMap[''];
386394
const noticeStyles: React.CSSProperties = {
387395
alignItems: 'center',
388-
backgroundColor: theme.tokens.alias.Background.Normal,
389396
borderRadius: 1,
390397
display: 'flex',
391398
flexWrap: 'nowrap',
@@ -396,22 +403,33 @@ export const AlertResources = React.memo((props: AlertResourcesProp) => {
396403
maxSelectionCount && selectedResources
397404
? Math.max(0, maxSelectionCount - selectedResources.length)
398405
: undefined;
406+
407+
const isLoading = isRegionsLoading || isResourcesLoading;
399408
return (
400409
<Stack gap={2}>
410+
{isLoading && <CircleProgress />}
401411
{!hideLabel && (
402-
<Typography ref={titleRef} variant="h2">
412+
<Typography
413+
display={isLoading ? 'none' : 'block'}
414+
ref={titleRef}
415+
variant="h2"
416+
>
403417
{alertLabel || 'Entities'}
404418
{/* It can be either the passed alert label or just Resources */}
405419
</Typography>
406420
)}
407421
{showEditInformation && (
408-
<Typography ref={titleRef} variant="body1">
422+
<Typography
423+
display={isLoading ? 'none' : 'block'}
424+
ref={titleRef}
425+
variant="body1"
426+
>
409427
You can enable or disable this system alert for each entities you have
410428
access to. Select the entities listed below you want to enable the
411429
alert for.
412430
</Typography>
413431
)}
414-
<GridLegacy container spacing={2}>
432+
<GridLegacy container display={isLoading ? 'none' : 'block'} spacing={2}>
415433
<GridLegacy
416434
columnSpacing={2}
417435
container
@@ -450,8 +468,8 @@ export const AlertResources = React.memo((props: AlertResourcesProp) => {
450468
new Set(
451469
regionFilteredResources
452470
? regionFilteredResources.flatMap(
453-
({ tags }) => tags ?? []
454-
)
471+
({ tags }) => tags ?? []
472+
)
455473
: []
456474
)
457475
),

packages/manager/src/features/CloudPulse/Alerts/AlertsResources/DisplayAlertResources.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ export const DisplayAlertResources = React.memo(
226226
}}
227227
title={
228228
maxSelectionCount !== undefined &&
229-
isRootCheckBoxDisabled ? (
229+
isRootCheckBoxDisabled ? (
230230
<AlertMaxSelectionText
231231
maxSelectionCount={maxSelectionCount}
232232
/>
@@ -301,7 +301,7 @@ export const DisplayAlertResources = React.memo(
301301
}}
302302
title={
303303
isItemCheckboxDisabled &&
304-
maxSelectionCount !== undefined ? (
304+
maxSelectionCount !== undefined ? (
305305
<AlertMaxSelectionText
306306
maxSelectionCount={maxSelectionCount}
307307
/>
@@ -339,7 +339,7 @@ export const DisplayAlertResources = React.memo(
339339
message="Table data is unavailable. Please try again later."
340340
/>
341341
)}
342-
{paginatedData.length === 0 && (
342+
{!isDataLoadingError && paginatedData.length === 0 && (
343343
<TableRow>
344344
<TableCell
345345
align="center"

packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/DimensionFilterField.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ interface DimensionFilterFieldProps {
3434
export const DimensionFilterField = (props: DimensionFilterFieldProps) => {
3535
const { dataFieldDisabled, dimensionOptions, name, onFilterDelete } = props;
3636

37-
const { control, resetField } = useFormContext<CreateAlertDefinitionForm>();
37+
const { control, resetField, setValue } =
38+
useFormContext<CreateAlertDefinitionForm>();
3839

3940
const dataFieldOptions =
4041
dimensionOptions.map((dimension) => ({
@@ -90,10 +91,17 @@ export const DimensionFilterField = (props: DimensionFilterFieldProps) => {
9091
const selectedDimension =
9192
dimensionOptions && dimensionFieldWatcher
9293
? (dimensionOptions.find(
93-
(dim) => dim.dimension_label === dimensionFieldWatcher
94-
) ?? null)
94+
(dim) => dim.dimension_label === dimensionFieldWatcher
95+
) ?? null)
9596
: null;
9697

98+
const handleError = React.useCallback(
99+
(hasError: boolean) => {
100+
setValue('hasAPIError', hasError);
101+
},
102+
[setValue]
103+
);
104+
97105
return (
98106
<GridLegacy
99107
container
@@ -175,6 +183,7 @@ export const DimensionFilterField = (props: DimensionFilterFieldProps) => {
175183
entities={entities}
176184
entityType={entityType ?? undefined}
177185
errorText={fieldState.error?.message}
186+
handleError={handleError}
178187
name={name}
179188
onBlur={field.onBlur}
180189
onChange={field.onChange}

packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/DimensionFilterValue/BlockStorageDimensionFilterAutocomplete.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ describe('<BlockStorageDimensionFilterAutocomplete />', () => {
4141
serviceType: 'blockstorage',
4242
type: 'alerts',
4343
values: [],
44+
handleError: vi.fn(),
4445
};
4546

4647
beforeEach(() => {

packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/DimensionFilterValue/BlockStorageDimensionFilterAutocomplete.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ export const BlockStorageDimensionFilterAutocomplete = (
3131
selectedRegions,
3232
serviceType,
3333
type,
34+
handleError,
3435
maxSelections,
3536
} = props;
3637

37-
const { data: regions } = useRegionsQuery();
38+
const { data: regions, isError: isRegionsError } = useRegionsQuery();
3839
const { values, isLoading, isError } = useBlockStorageFetchOptions({
3940
entities,
4041
dimensionLabel,
@@ -45,12 +46,20 @@ export const BlockStorageDimensionFilterAutocomplete = (
4546
serviceType,
4647
});
4748

49+
React.useEffect(() => {
50+
const hasError = isError || isRegionsError;
51+
if (handleError) {
52+
handleError(hasError);
53+
}
54+
}, [isError, isRegionsError, handleError]);
55+
4856
useCleanupStaleValues({
4957
options: values,
5058
fieldValue,
5159
multiple,
5260
onChange: fieldOnChange,
5361
isLoading,
62+
isError,
5463
});
5564

5665
const maxReached = React.useMemo(() => {

packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/DimensionFilterValue/DimensionFilterAutocomplete.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe('<DimensionFilterAutocomplete />', () => {
3131
selectedRegions: [],
3232
serviceType: 'nodebalancer',
3333
values: mockOptions.map((o) => o.value),
34+
handleError: vi.fn(),
3435
type: 'alerts',
3536
};
3637

packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/DimensionFilterValue/FirewallDimensionFilterAutocomplete.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ describe('<FirewallDimensionFilterAutocomplete />', () => {
3939
serviceType: 'firewall',
4040
type: 'alerts',
4141
entityType: 'linode',
42+
handleError: vi.fn(),
4243
};
4344

4445
beforeEach(() => {

0 commit comments

Comments
 (0)