Skip to content

Commit a0b6aa5

Browse files
upcoming: [DI-25992] - Updated AlertRegions component to extend the functionality (linode#12582)
* upcoming: [DI-25992] - Updated AlertRegions component to extend the functionality * Added changeset * Fix typo issue
1 parent bb043d8 commit a0b6aa5

File tree

11 files changed

+315
-55
lines changed

11 files changed

+315
-55
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Upcoming Features
3+
---
4+
5+
ACLP: add checkbox functionality in `AlertRegions`. ([#12582](https://github.com/linode/manager/pull/12582))

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { regionFactory } from '@linode/utilities';
2-
import { screen } from '@testing-library/react';
2+
import { screen, within } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
34
import React from 'react';
45

56
import { databaseFactory } from 'src/factories';
67
import { renderWithTheme } from 'src/utilities/testHelpers';
78

9+
import { REGION_GROUP_INFO_MESSAGE } from '../constants';
810
import { AlertRegions } from './AlertRegions';
911

1012
import type { AlertServiceType } from '@linode/api-v4';
@@ -38,7 +40,7 @@ vi.mock('src/hooks/useFlags', async (importOriginal) => ({
3840
queryMocks.useRegionsQuery.mockReturnValue({
3941
data: regions,
4042
isLoading: false,
41-
isError: false,
43+
isErrro: false,
4244
});
4345

4446
queryMocks.useResourcesQuery.mockReturnValue({
@@ -57,11 +59,41 @@ const component = (
5759
describe('Alert Regions', () => {
5860
it('Should render the filters and notices ', () => {
5961
renderWithTheme(component);
62+
const text = screen.getByText(REGION_GROUP_INFO_MESSAGE);
6063

6164
const regionSearch = screen.getByTestId('region-search');
6265
const showSelectedOnly = screen.getByTestId('show-selected-only');
66+
expect(text).toBeInTheDocument();
6367

6468
expect(regionSearch).toBeInTheDocument();
6569
expect(showSelectedOnly).toBeInTheDocument();
6670
});
71+
72+
it('should select all regions when the select all checkbox is checked', async () => {
73+
renderWithTheme(component);
74+
75+
const selectAllCheckbox = within(
76+
screen.getByTestId('select-all-checkbox')
77+
).getByRole('checkbox');
78+
await userEvent.click(selectAllCheckbox);
79+
80+
expect(selectAllCheckbox).toBeChecked();
81+
82+
const notice = screen.getByTestId('selection_notice');
83+
84+
expect(notice.textContent).toBe('1 of 1 regions are selected.');
85+
});
86+
87+
it('should show only header on click of show selected only', async () => {
88+
renderWithTheme(component);
89+
90+
const checkbox = within(screen.getByTestId('show-selected-only')).getByRole(
91+
'checkbox'
92+
);
93+
94+
await userEvent.click(checkbox);
95+
expect(checkbox).toBeChecked();
96+
97+
expect(screen.getAllByRole('row').length).toBe(1); // Only header row should be visible
98+
});
6799
});

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

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@ import React from 'react';
66
import { DebouncedSearchTextField } from 'src/components/DebouncedSearchTextField';
77
import { useResourcesQuery } from 'src/queries/cloudpulse/resources';
88

9-
import { type AlertFormMode } from '../constants';
9+
import {
10+
type AlertFormMode,
11+
REGION_GROUP_INFO_MESSAGE,
12+
type SelectDeselectAll,
13+
} from '../constants';
1014
import { AlertListNoticeMessages } from '../Utils/AlertListNoticeMessages';
11-
import { getSupportedRegions } from '../Utils/utils';
15+
import { AlertSelectedInfoNotice } from '../Utils/AlertSelectedInfoNotice';
16+
import { getFilteredRegions } from '../Utils/utils';
1217
import { DisplayAlertRegions } from './DisplayAlertRegions';
1318

14-
import type { AlertServiceType, Filter, Region } from '@linode/api-v4';
19+
import type { AlertRegion } from './DisplayAlertRegions';
20+
import type { AlertServiceType, Filter } from '@linode/api-v4';
1521

1622
interface AlertRegionsProps {
1723
/**
@@ -40,9 +46,7 @@ export const AlertRegions = React.memo((props: AlertRegionsProps) => {
4046
const { serviceType, handleChange, value = [], errorText, mode } = props;
4147
const [searchText, setSearchText] = React.useState<string>('');
4248
const { data: regions, isLoading: isRegionsLoading } = useRegionsQuery();
43-
44-
// Todo: State variable will be added when checkbox functionality implemented
45-
const [, setSelectedRegions] = React.useState<string[]>(value);
49+
const [selectedRegions, setSelectedRegions] = React.useState<string[]>(value);
4650
const [showSelected, setShowSelected] = React.useState<boolean>(false);
4751

4852
const resourceFilterMap: Record<string, Filter> = {
@@ -84,27 +88,50 @@ export const AlertRegions = React.memo((props: AlertRegionsProps) => {
8488
[handleChange]
8589
);
8690

87-
const filteredRegionsWithStatus: Region[] = React.useMemo(
91+
const filteredRegionsWithStatus: AlertRegion[] = React.useMemo(
8892
() =>
89-
getSupportedRegions({
93+
getFilteredRegions({
9094
serviceType,
95+
selectedRegions,
9196
resources,
9297
regions,
9398
}),
94-
[regions, resources, serviceType]
99+
[regions, resources, selectedRegions, serviceType]
100+
);
101+
102+
const handleSelectAll = React.useCallback(
103+
(action: SelectDeselectAll) => {
104+
let regionIds: string[] = [];
105+
if (action === 'Select All') {
106+
regionIds = filteredRegionsWithStatus?.map((region) => region.id) ?? [];
107+
}
108+
109+
setSelectedRegions(regionIds);
110+
if (handleChange) {
111+
handleChange(regionIds);
112+
}
113+
},
114+
[filteredRegionsWithStatus, handleChange]
95115
);
96116

97117
if (isRegionsLoading || isResourcesLoading) {
98118
return <CircleProgress />;
99119
}
100120
const filteredRegionsBySearchText = filteredRegionsWithStatus.filter(
101-
({ label }) => label.toLowerCase().includes(searchText.toLowerCase())
121+
({ label, checked }) =>
122+
label.toLowerCase().includes(searchText.toLowerCase()) &&
123+
((mode && checked) || !mode)
102124
);
103125

104126
return (
105127
<Stack gap={2}>
106128
{mode === 'view' && <Typography variant="h2">Regions</Typography>}
107129

130+
<AlertListNoticeMessages
131+
errorMessage={REGION_GROUP_INFO_MESSAGE}
132+
variant="info"
133+
/>
134+
108135
<Box display="flex" gap={2}>
109136
<DebouncedSearchTextField
110137
data-testid="region-search"
@@ -139,8 +166,25 @@ export const AlertRegions = React.memo((props: AlertRegionsProps) => {
139166
<AlertListNoticeMessages errorMessage={errorText} variant="error" />
140167
)}
141168

169+
{mode !== 'view' && (
170+
<AlertSelectedInfoNotice
171+
handleSelectionChange={handleSelectAll}
172+
property="regions"
173+
selectedCount={selectedRegions.length}
174+
totalCount={filteredRegionsWithStatus.length}
175+
/>
176+
)}
142177
<DisplayAlertRegions
178+
handleSelectAll={handleSelectAll}
143179
handleSelectionChange={handleSelectionChange}
180+
isAllSelected={
181+
filteredRegionsWithStatus.length > 0 &&
182+
selectedRegions.length === filteredRegionsWithStatus.length
183+
}
184+
isSomeSelected={
185+
selectedRegions.length > 0 &&
186+
selectedRegions.length !== filteredRegionsWithStatus.length
187+
}
144188
mode={mode}
145189
regions={filteredRegionsBySearchText}
146190
showSelected={showSelected}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,21 @@ import { renderWithTheme } from 'src/utilities/testHelpers';
77

88
import { DisplayAlertRegions } from './DisplayAlertRegions';
99

10-
const regions = regionFactory.buildList(10);
10+
const regions = regionFactory.buildList(10).map(({ id, label }) => ({
11+
id,
12+
label,
13+
checked: false,
14+
count: Math.random(),
15+
}));
1116

1217
const handleChange = vi.fn();
18+
const handleSelectAll = vi.fn();
1319

1420
describe('DisplayAlertRegions', () => {
1521
it('should render the regions table', () => {
1622
renderWithTheme(
1723
<DisplayAlertRegions
24+
handleSelectAll={handleSelectAll}
1825
handleSelectionChange={handleChange}
1926
regions={regions}
2027
/>
@@ -30,6 +37,7 @@ describe('DisplayAlertRegions', () => {
3037
it('should display checkbox and label', () => {
3138
renderWithTheme(
3239
<DisplayAlertRegions
40+
handleSelectAll={handleSelectAll}
3341
handleSelectionChange={handleChange}
3442
regions={regions}
3543
/>
@@ -48,6 +56,7 @@ describe('DisplayAlertRegions', () => {
4856
it('should select checkbox when clicked', async () => {
4957
renderWithTheme(
5058
<DisplayAlertRegions
59+
handleSelectAll={handleSelectAll}
5160
handleSelectionChange={handleChange}
5261
regions={regions}
5362
/>

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

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,53 @@ import { TableContentWrapper } from 'src/components/TableContentWrapper/TableCon
1010
import { TableRow } from 'src/components/TableRow';
1111
import { TableSortCell } from 'src/components/TableSortCell';
1212

13-
import type { AlertFormMode } from '../constants';
14-
import type { Region } from '@linode/api-v4';
13+
import type { AlertFormMode, SelectDeselectAll } from '../constants';
14+
15+
export interface AlertRegion {
16+
/**
17+
* Indicates if the region is selected.
18+
* This is used to determine if the region should be checked in the UI.
19+
*/
20+
checked: boolean;
21+
/**
22+
* The number of associated entities in the region.
23+
*/
24+
count: number;
25+
/**
26+
* Id of the region
27+
*/
28+
id: string;
29+
/**
30+
* Label of the region.
31+
*/
32+
label: string;
33+
}
1534

1635
interface DisplayAlertRegionProps {
36+
/**
37+
* Function to handle the selection of all regions.
38+
*/
39+
handleSelectAll: (action: SelectDeselectAll) => void;
1740
/**
1841
* Function to handle the change in selection of a region.
1942
*/
2043
handleSelectionChange: (regionId: string, isChecked: boolean) => void;
21-
44+
/**
45+
* Indicates if all regions are selected.
46+
*/
47+
isAllSelected?: boolean;
48+
/**
49+
* Indicates if some regions are selected.
50+
*/
51+
isSomeSelected?: boolean;
2252
/**
2353
* Flag to indicate the mode of the form
2454
*/
2555
mode?: AlertFormMode;
2656
/**
2757
* List of regions to be displayed.
2858
*/
29-
regions?: Region[];
59+
regions?: AlertRegion[];
3060
/**
3161
* To indicate whether to show only selected regions or not.
3262
*/
@@ -35,7 +65,15 @@ interface DisplayAlertRegionProps {
3565

3666
export const DisplayAlertRegions = React.memo(
3767
(props: DisplayAlertRegionProps) => {
38-
const { regions, handleSelectionChange, mode } = props;
68+
const {
69+
regions,
70+
handleSelectionChange,
71+
isSomeSelected,
72+
isAllSelected,
73+
showSelected,
74+
handleSelectAll,
75+
mode,
76+
} = props;
3977

4078
return (
4179
<Paginate data={regions ?? []}>
@@ -60,8 +98,14 @@ export const DisplayAlertRegions = React.memo(
6098
<TableCell>
6199
<Box>
62100
<Checkbox
101+
checked={!isSomeSelected && isAllSelected}
63102
data-testid="select-all-checkbox"
64-
onChange={(_, _checked) => {}}
103+
indeterminate={isSomeSelected && !isAllSelected}
104+
onChange={(_, checked) =>
105+
handleSelectAll(
106+
checked ? 'Select All' : 'Deselect All'
107+
)
108+
}
65109
/>
66110
</Box>
67111
</TableCell>
@@ -76,32 +120,46 @@ export const DisplayAlertRegions = React.memo(
76120
>
77121
Region
78122
</TableSortCell>
123+
<TableSortCell
124+
active={true}
125+
data-qa-header="associated-entities"
126+
data-qa-sorting="associated-header"
127+
direction="asc"
128+
handleClick={() => {}}
129+
label="Associated Entities"
130+
>
131+
Associated Entities
132+
</TableSortCell>
79133
</TableRow>
80134
</TableHead>
81135
<TableBody>
82136
<TableContentWrapper
83137
length={regions?.length ?? 0}
84138
loading={false}
85139
>
86-
{paginatedData.map(({ label, id }) => {
87-
return (
88-
<TableRow data-testid={`region-row-${id}`} key={id}>
89-
{mode !== 'view' && (
140+
{paginatedData
141+
?.filter(({ checked }) => (showSelected ? checked : true))
142+
.map(({ label, id, checked, count }) => {
143+
return (
144+
<TableRow data-testid={`region-row-${id}`} key={id}>
145+
{mode !== 'view' && (
146+
<TableCell>
147+
<Checkbox
148+
checked={checked}
149+
onChange={(_, status) =>
150+
handleSelectionChange(id, status)
151+
}
152+
/>
153+
</TableCell>
154+
)}
155+
90156
<TableCell>
91-
<Checkbox
92-
onChange={(_, status) =>
93-
handleSelectionChange(id, status)
94-
}
95-
/>
157+
{label} ({id})
96158
</TableCell>
97-
)}
98-
99-
<TableCell>
100-
{label} ({id})
101-
</TableCell>
102-
</TableRow>
103-
);
104-
})}
159+
<TableCell>{count}</TableCell>
160+
</TableRow>
161+
);
162+
})}
105163
</TableContentWrapper>
106164
</TableBody>
107165
</Table>

0 commit comments

Comments
 (0)