Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

ACLP: add checkbox functionality in `AlertRegions`. ([#12582](https://github.com/linode/manager/pull/12582))
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { regionFactory } from '@linode/utilities';
import { screen } from '@testing-library/react';
import { screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';

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

import { REGION_GROUP_INFO_MESSAGE } from '../constants';
import { AlertRegions } from './AlertRegions';

import type { AlertServiceType } from '@linode/api-v4';
Expand Down Expand Up @@ -38,7 +40,7 @@ vi.mock('src/hooks/useFlags', async (importOriginal) => ({
queryMocks.useRegionsQuery.mockReturnValue({
data: regions,
isLoading: false,
isError: false,
isErrro: false,
});

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

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

expect(regionSearch).toBeInTheDocument();
expect(showSelectedOnly).toBeInTheDocument();
});

it('should select all regions when the select all checkbox is checked', async () => {
renderWithTheme(component);

const selectAllCheckbox = within(
screen.getByTestId('select-all-checkbox')
).getByRole('checkbox');
await userEvent.click(selectAllCheckbox);

expect(selectAllCheckbox).toBeChecked();

const notice = screen.getByTestId('selection_notice');

expect(notice.textContent).toBe('1 of 1 regions are selected.');
});

it('should show only header on click of show selected only', async () => {
renderWithTheme(component);

const checkbox = within(screen.getByTestId('show-selected-only')).getByRole(
'checkbox'
);

await userEvent.click(checkbox);
expect(checkbox).toBeChecked();

expect(screen.getAllByRole('row').length).toBe(1); // Only header row should be visible
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ import React from 'react';
import { DebouncedSearchTextField } from 'src/components/DebouncedSearchTextField';
import { useResourcesQuery } from 'src/queries/cloudpulse/resources';

import { type AlertFormMode } from '../constants';
import {
type AlertFormMode,
REGION_GROUP_INFO_MESSAGE,
type SelectDeselectAll,
} from '../constants';
import { AlertListNoticeMessages } from '../Utils/AlertListNoticeMessages';
import { getSupportedRegions } from '../Utils/utils';
import { AlertSelectedInfoNotice } from '../Utils/AlertSelectedInfoNotice';
import { getFilteredRegions } from '../Utils/utils';
import { DisplayAlertRegions } from './DisplayAlertRegions';

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

interface AlertRegionsProps {
/**
Expand Down Expand Up @@ -40,9 +46,7 @@ export const AlertRegions = React.memo((props: AlertRegionsProps) => {
const { serviceType, handleChange, value = [], errorText, mode } = props;
const [searchText, setSearchText] = React.useState<string>('');
const { data: regions, isLoading: isRegionsLoading } = useRegionsQuery();

// Todo: State variable will be added when checkbox functionality implemented
const [, setSelectedRegions] = React.useState<string[]>(value);
const [selectedRegions, setSelectedRegions] = React.useState<string[]>(value);
const [showSelected, setShowSelected] = React.useState<boolean>(false);

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

const filteredRegionsWithStatus: Region[] = React.useMemo(
const filteredRegionsWithStatus: AlertRegion[] = React.useMemo(
() =>
getSupportedRegions({
getFilteredRegions({
serviceType,
selectedRegions,
resources,
regions,
}),
[regions, resources, serviceType]
[regions, resources, selectedRegions, serviceType]
);

const handleSelectAll = React.useCallback(
(action: SelectDeselectAll) => {
let regionIds: string[] = [];
if (action === 'Select All') {
regionIds = filteredRegionsWithStatus?.map((region) => region.id) ?? [];
}

setSelectedRegions(regionIds);
if (handleChange) {
handleChange(regionIds);
}
},
[filteredRegionsWithStatus, handleChange]
);

if (isRegionsLoading || isResourcesLoading) {
return <CircleProgress />;
}
const filteredRegionsBySearchText = filteredRegionsWithStatus.filter(
({ label }) => label.toLowerCase().includes(searchText.toLowerCase())
({ label, checked }) =>
label.toLowerCase().includes(searchText.toLowerCase()) &&
((mode && checked) || !mode)
);

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

<AlertListNoticeMessages
errorMessage={REGION_GROUP_INFO_MESSAGE}
variant="info"
/>

<Box display="flex" gap={2}>
<DebouncedSearchTextField
data-testid="region-search"
Expand Down Expand Up @@ -139,8 +166,25 @@ export const AlertRegions = React.memo((props: AlertRegionsProps) => {
<AlertListNoticeMessages errorMessage={errorText} variant="error" />
)}

{mode !== 'view' && (
<AlertSelectedInfoNotice
handleSelectionChange={handleSelectAll}
property="regions"
selectedCount={selectedRegions.length}
totalCount={filteredRegionsWithStatus.length}
/>
)}
<DisplayAlertRegions
handleSelectAll={handleSelectAll}
handleSelectionChange={handleSelectionChange}
isAllSelected={
filteredRegionsWithStatus.length > 0 &&
selectedRegions.length === filteredRegionsWithStatus.length
}
isSomeSelected={
selectedRegions.length > 0 &&
selectedRegions.length !== filteredRegionsWithStatus.length
}
mode={mode}
regions={filteredRegionsBySearchText}
showSelected={showSelected}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ import { renderWithTheme } from 'src/utilities/testHelpers';

import { DisplayAlertRegions } from './DisplayAlertRegions';

const regions = regionFactory.buildList(10);
const regions = regionFactory.buildList(10).map(({ id, label }) => ({
id,
label,
checked: false,
count: Math.random(),
}));

const handleChange = vi.fn();
const handleSelectAll = vi.fn();

describe('DisplayAlertRegions', () => {
it('should render the regions table', () => {
renderWithTheme(
<DisplayAlertRegions
handleSelectAll={handleSelectAll}
handleSelectionChange={handleChange}
regions={regions}
/>
Expand All @@ -30,6 +37,7 @@ describe('DisplayAlertRegions', () => {
it('should display checkbox and label', () => {
renderWithTheme(
<DisplayAlertRegions
handleSelectAll={handleSelectAll}
handleSelectionChange={handleChange}
regions={regions}
/>
Expand All @@ -48,6 +56,7 @@ describe('DisplayAlertRegions', () => {
it('should select checkbox when clicked', async () => {
renderWithTheme(
<DisplayAlertRegions
handleSelectAll={handleSelectAll}
handleSelectionChange={handleChange}
regions={regions}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,53 @@ import { TableContentWrapper } from 'src/components/TableContentWrapper/TableCon
import { TableRow } from 'src/components/TableRow';
import { TableSortCell } from 'src/components/TableSortCell';

import type { AlertFormMode } from '../constants';
import type { Region } from '@linode/api-v4';
import type { AlertFormMode, SelectDeselectAll } from '../constants';

export interface AlertRegion {
/**
* Indicates if the region is selected.
* This is used to determine if the region should be checked in the UI.
*/
checked: boolean;
/**
* The number of associated entities in the region.
*/
count: number;
/**
* Id of the region
*/
id: string;
/**
* Label of the region.
*/
label: string;
}

interface DisplayAlertRegionProps {
/**
* Function to handle the selection of all regions.
*/
handleSelectAll: (action: SelectDeselectAll) => void;
/**
* Function to handle the change in selection of a region.
*/
handleSelectionChange: (regionId: string, isChecked: boolean) => void;

/**
* Indicates if all regions are selected.
*/
isAllSelected?: boolean;
/**
* Indicates if some regions are selected.
*/
isSomeSelected?: boolean;
/**
* Flag to indicate the mode of the form
*/
mode?: AlertFormMode;
/**
* List of regions to be displayed.
*/
regions?: Region[];
regions?: AlertRegion[];
/**
* To indicate whether to show only selected regions or not.
*/
Expand All @@ -35,7 +65,15 @@ interface DisplayAlertRegionProps {

export const DisplayAlertRegions = React.memo(
(props: DisplayAlertRegionProps) => {
const { regions, handleSelectionChange, mode } = props;
const {
regions,
handleSelectionChange,
isSomeSelected,
isAllSelected,
showSelected,
handleSelectAll,
mode,
} = props;

return (
<Paginate data={regions ?? []}>
Expand All @@ -60,8 +98,14 @@ export const DisplayAlertRegions = React.memo(
<TableCell>
<Box>
<Checkbox
checked={!isSomeSelected && isAllSelected}
data-testid="select-all-checkbox"
onChange={(_, _checked) => {}}
indeterminate={isSomeSelected && !isAllSelected}
onChange={(_, checked) =>
handleSelectAll(
checked ? 'Select All' : 'Deselect All'
)
}
/>
</Box>
</TableCell>
Expand All @@ -76,32 +120,46 @@ export const DisplayAlertRegions = React.memo(
>
Region
</TableSortCell>
<TableSortCell
active={true}
data-qa-header="associated-entities"
data-qa-sorting="associated-header"
direction="asc"
handleClick={() => {}}
label="Associated Entities"
>
Associated Entities
</TableSortCell>
</TableRow>
</TableHead>
<TableBody>
<TableContentWrapper
length={regions?.length ?? 0}
loading={false}
>
{paginatedData.map(({ label, id }) => {
return (
<TableRow data-testid={`region-row-${id}`} key={id}>
{mode !== 'view' && (
{paginatedData
?.filter(({ checked }) => (showSelected ? checked : true))
.map(({ label, id, checked, count }) => {
return (
<TableRow data-testid={`region-row-${id}`} key={id}>
{mode !== 'view' && (
<TableCell>
<Checkbox
checked={checked}
onChange={(_, status) =>
handleSelectionChange(id, status)
}
/>
</TableCell>
)}

<TableCell>
<Checkbox
onChange={(_, status) =>
handleSelectionChange(id, status)
}
/>
{label} ({id})
</TableCell>
)}

<TableCell>
{label} ({id})
</TableCell>
</TableRow>
);
})}
<TableCell>{count}</TableCell>
</TableRow>
);
})}
</TableContentWrapper>
</TableBody>
</Table>
Expand Down
Loading