-
Notifications
You must be signed in to change notification settings - Fork 367
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
upcoming: [M3-7612] Placement Group Linodes List (#10123)
* Initial commit - save work * Post rebase fixes * Formatting and styling * Cleanup and sorting improvements * Cleanup and sorting improvements * Adding unit tests * Cleanup * Added changeset: Placement GroupLinode List * Simplify logic - avoid useEffect * Feedback
- Loading branch information
1 parent
418f4f6
commit 3bb8475
Showing
16 changed files
with
451 additions
and
14 deletions.
There are no files selected for viewing
5 changes: 5 additions & 0 deletions
5
packages/manager/.changeset/pr-10123-upcoming-features-1706733948755.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@linode/manager": Upcoming Features | ||
--- | ||
|
||
Add Placement Group Linodes List ([#10123](https://github.com/linode/manager/pull/10123)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
...cementGroups/PlacementGroupsDetail/PlacementGroupsLinodes/PlacementGroupsLinodes.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import * as React from 'react'; | ||
|
||
import { placementGroupFactory } from 'src/factories'; | ||
import { renderWithTheme } from 'src/utilities/testHelpers'; | ||
|
||
import { PLACEMENT_GROUP_LINODES_ERROR_MESSAGE } from '../../constants'; | ||
import { PlacementGroupsLinodes } from './PlacementGroupsLinodes'; | ||
|
||
describe('PlacementGroupsLanding', () => { | ||
it('renders an error state if placement groups are undefined', () => { | ||
const { getByText } = renderWithTheme( | ||
<PlacementGroupsLinodes placementGroup={undefined} /> | ||
); | ||
|
||
expect( | ||
getByText(PLACEMENT_GROUP_LINODES_ERROR_MESSAGE) | ||
).toBeInTheDocument(); | ||
}); | ||
|
||
it('features the linodes table, a filter field, a create button and a docs link', () => { | ||
const placementGroup = placementGroupFactory.build({ | ||
capacity: 2, | ||
linode_ids: [1], | ||
}); | ||
|
||
const { getByPlaceholderText, getByRole, getByTestId } = renderWithTheme( | ||
<PlacementGroupsLinodes placementGroup={placementGroup} /> | ||
); | ||
|
||
expect(getByTestId('add-linode-to-placement-group-button')).toHaveAttribute( | ||
'aria-disabled', | ||
'false' | ||
); | ||
expect(getByPlaceholderText('Search Linodes')).toBeInTheDocument(); | ||
expect(getByRole('table')).toBeInTheDocument(); | ||
}); | ||
|
||
it('has a disabled create button if the placement group has reached capacity', () => { | ||
const placementGroup = placementGroupFactory.build({ | ||
capacity: 1, | ||
linode_ids: [1], | ||
}); | ||
|
||
const { getByTestId } = renderWithTheme( | ||
<PlacementGroupsLinodes placementGroup={placementGroup} /> | ||
); | ||
|
||
expect(getByTestId('add-linode-to-placement-group-button')).toHaveAttribute( | ||
'aria-disabled', | ||
'true' | ||
); | ||
}); | ||
}); |
111 changes: 111 additions & 0 deletions
111
...s/PlacementGroups/PlacementGroupsDetail/PlacementGroupsLinodes/PlacementGroupsLinodes.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import { useTheme } from '@mui/material'; | ||
import { useMediaQuery } from '@mui/material'; | ||
import Grid from '@mui/material/Unstable_Grid2/Grid2'; | ||
import * as React from 'react'; | ||
|
||
import { Box } from 'src/components/Box'; | ||
import { Button } from 'src/components/Button/Button'; | ||
import { DebouncedSearchTextField } from 'src/components/DebouncedSearchTextField'; | ||
import { ErrorState } from 'src/components/ErrorState/ErrorState'; | ||
import { Stack } from 'src/components/Stack'; | ||
import { Typography } from 'src/components/Typography'; | ||
import { useAllLinodesQuery } from 'src/queries/linodes/linodes'; | ||
|
||
import { | ||
MAX_NUMBER_OF_LINODES_IN_PLACEMENT_GROUP_MESSAGE, | ||
PLACEMENT_GROUP_LINODES_ERROR_MESSAGE, | ||
} from '../../constants'; | ||
import { hasPlacementGroupReachedCapacity } from '../../utils'; | ||
import { PlacementGroupsLinodesTable } from './PlacementGroupsLinodesTable'; | ||
|
||
import type { Linode, PlacementGroup } from '@linode/api-v4'; | ||
|
||
interface Props { | ||
placementGroup: PlacementGroup | undefined; | ||
} | ||
|
||
export const PlacementGroupsLinodes = (props: Props) => { | ||
const { placementGroup } = props; | ||
const { | ||
data: placementGroupLinodes, | ||
error: linodesError, | ||
isLoading: linodesLoading, | ||
} = useAllLinodesQuery( | ||
{}, | ||
{ | ||
'+or': placementGroup?.linode_ids.map((id) => ({ | ||
id, | ||
})), | ||
} | ||
); | ||
const theme = useTheme(); | ||
const matchesSmDown = useMediaQuery(theme.breakpoints.down('md')); | ||
const [searchText, setSearchText] = React.useState(''); | ||
|
||
if (!placementGroup) { | ||
return <ErrorState errorText={PLACEMENT_GROUP_LINODES_ERROR_MESSAGE} />; | ||
} | ||
|
||
const { capacity } = placementGroup; | ||
|
||
const getLinodesList = () => { | ||
if (!placementGroupLinodes) { | ||
return []; | ||
} | ||
|
||
if (searchText) { | ||
return placementGroupLinodes.filter((linode: Linode) => { | ||
return linode.label.toLowerCase().includes(searchText.toLowerCase()); | ||
}); | ||
} | ||
|
||
return placementGroupLinodes; | ||
}; | ||
|
||
return ( | ||
<Stack spacing={2}> | ||
<Box sx={{ px: matchesSmDown ? 2 : 0, py: 2 }}> | ||
<Typography> | ||
The following Linodes have been assigned to this Placement Group. A | ||
Linode can only be assigned to a single Placement Group. | ||
</Typography> | ||
<Typography sx={{ mt: 1 }}> | ||
Limit of Linodes for this Placement Group: {capacity} | ||
</Typography> | ||
</Box> | ||
|
||
<Grid container justifyContent="space-between"> | ||
<Grid flexGrow={1} sm={6} sx={{ mb: 1 }} xs={12}> | ||
<DebouncedSearchTextField | ||
onSearch={(value) => { | ||
setSearchText(value); | ||
}} | ||
debounceTime={250} | ||
hideLabel | ||
label="Search Linodes" | ||
placeholder="Search Linodes" | ||
value={searchText} | ||
/> | ||
</Grid> | ||
<Grid> | ||
<Button | ||
buttonType="primary" | ||
data-testid="add-linode-to-placement-group-button" | ||
disabled={hasPlacementGroupReachedCapacity(placementGroup)} | ||
// onClick={TODO VM_Placement: open assign linode drawer} | ||
tooltipText={MAX_NUMBER_OF_LINODES_IN_PLACEMENT_GROUP_MESSAGE} | ||
> | ||
Add Linode to Placement Group | ||
</Button> | ||
</Grid> | ||
</Grid> | ||
<PlacementGroupsLinodesTable | ||
error={linodesError ?? []} | ||
linodes={getLinodesList() ?? []} | ||
loading={linodesLoading} | ||
/> | ||
{/* TODO VM_Placement: ASSIGN LINODE DRAWER */} | ||
{/* TODO VM_Placement: UNASSIGN LINODE DRAWER */} | ||
</Stack> | ||
); | ||
}; |
49 changes: 49 additions & 0 deletions
49
...tGroups/PlacementGroupsDetail/PlacementGroupsLinodes/PlacementGroupsLinodesTable.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import * as React from 'react'; | ||
|
||
import { linodeFactory } from 'src/factories'; | ||
import { renderWithTheme } from 'src/utilities/testHelpers'; | ||
|
||
import { PlacementGroupsLinodesTable } from './PlacementGroupsLinodesTable'; | ||
|
||
const defaultProps = { | ||
error: [], | ||
linodes: linodeFactory.buildList(5), | ||
loading: false, | ||
}; | ||
|
||
describe('PlacementGroupsLanding', () => { | ||
it('renders an error state when encountering an API error', () => { | ||
const { getByText } = renderWithTheme( | ||
<PlacementGroupsLinodesTable | ||
{...defaultProps} | ||
error={[{ reason: 'Not found' }]} | ||
/> | ||
); | ||
|
||
expect(getByText(/not found/i)).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders a loading skeleton based on the loading prop', () => { | ||
const { getByTestId } = renderWithTheme( | ||
<PlacementGroupsLinodesTable {...defaultProps} loading /> | ||
); | ||
|
||
expect(getByTestId('table-row-loading')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should have the correct number of columns', () => { | ||
const { getAllByRole } = renderWithTheme( | ||
<PlacementGroupsLinodesTable {...defaultProps} /> | ||
); | ||
|
||
expect(getAllByRole('columnheader')).toHaveLength(3); | ||
}); | ||
|
||
it('should have the correct number of rows', () => { | ||
const { getAllByTestId } = renderWithTheme( | ||
<PlacementGroupsLinodesTable {...defaultProps} /> | ||
); | ||
|
||
expect(getAllByTestId(/placement-group-linode-/i)).toHaveLength(5); | ||
}); | ||
}); |
106 changes: 106 additions & 0 deletions
106
...cementGroups/PlacementGroupsDetail/PlacementGroupsLinodes/PlacementGroupsLinodesTable.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import * as React from 'react'; | ||
|
||
import OrderBy from 'src/components/OrderBy'; | ||
import Paginate from 'src/components/Paginate'; | ||
import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter'; | ||
import { Table } from 'src/components/Table'; | ||
import { TableBody } from 'src/components/TableBody'; | ||
import { TableCell } from 'src/components/TableCell'; | ||
import { TableContentWrapper } from 'src/components/TableContentWrapper/TableContentWrapper'; | ||
import { TableHead } from 'src/components/TableHead'; | ||
import { TableRow } from 'src/components/TableRow'; | ||
import { TableSortCell } from 'src/components/TableSortCell'; | ||
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; | ||
|
||
import { PLACEMENT_GROUP_LINODES_ERROR_MESSAGE } from '../../constants'; | ||
import { PlacementGroupsLinodesTableRow } from './PlacementGroupsLinodesTableRow'; | ||
|
||
import type { APIError, Linode } from '@linode/api-v4'; | ||
|
||
export interface Props { | ||
error?: APIError[]; | ||
linodes: Linode[]; | ||
loading: boolean; | ||
} | ||
|
||
export const PlacementGroupsLinodesTable = React.memo((props: Props) => { | ||
const { error, linodes, loading } = props; | ||
|
||
const orderLinodeKey = 'label'; | ||
const orderStatusKey = 'status'; | ||
|
||
const _error = error | ||
? getAPIErrorOrDefault(error, PLACEMENT_GROUP_LINODES_ERROR_MESSAGE) | ||
: undefined; | ||
|
||
return ( | ||
<OrderBy data={linodes} order="asc" orderBy={orderLinodeKey}> | ||
{({ data: orderedData, handleOrderChange, order, orderBy }) => ( | ||
<Paginate data={orderedData}> | ||
{({ | ||
count, | ||
data: paginatedAndOrderedLinodes, | ||
handlePageChange, | ||
handlePageSizeChange, | ||
page, | ||
pageSize, | ||
}) => ( | ||
<> | ||
<Table aria-label="List of Linodes in this Placement Group"> | ||
<TableHead> | ||
<TableRow> | ||
<TableSortCell | ||
active={orderBy === orderLinodeKey} | ||
data-qa-placement-group-linode-header | ||
direction={order} | ||
handleClick={handleOrderChange} | ||
label={orderLinodeKey} | ||
sx={{ width: '30%' }} | ||
> | ||
Linode | ||
</TableSortCell> | ||
<TableSortCell | ||
active={orderBy === orderStatusKey} | ||
data-qa-placement-group-linode-status-header | ||
direction={order} | ||
handleClick={handleOrderChange} | ||
label={orderStatusKey} | ||
> | ||
Linode Status | ||
</TableSortCell> | ||
<TableCell /> | ||
</TableRow> | ||
</TableHead> | ||
<TableBody> | ||
<TableContentWrapper | ||
loadingProps={{ | ||
columns: 3, | ||
}} | ||
error={_error} | ||
length={paginatedAndOrderedLinodes.length} | ||
loading={loading} | ||
> | ||
{paginatedAndOrderedLinodes.map((linode) => ( | ||
<PlacementGroupsLinodesTableRow | ||
key={`placement-group-linode-${linode.id}`} | ||
linode={linode} | ||
/> | ||
))} | ||
</TableContentWrapper> | ||
</TableBody> | ||
</Table> | ||
<PaginationFooter | ||
count={count} | ||
eventCategory="Placement Group Linodes Table" | ||
handlePageChange={handlePageChange} | ||
handleSizeChange={handlePageSizeChange} | ||
page={page} | ||
pageSize={pageSize} | ||
/> | ||
</> | ||
)} | ||
</Paginate> | ||
)} | ||
</OrderBy> | ||
); | ||
}); |
Oops, something went wrong.