Skip to content

Commit c7b7d76

Browse files
feat: [STORIF-102] - Volume "AttachedTo" column updated (#12903)
* feat: [STORIF-102] Volume "AttachedTo" column updated. * Added changeset: Volume attached to state * Added changeset: Volume io_ready property
1 parent f23ec80 commit c7b7d76

File tree

8 files changed

+152
-37
lines changed

8 files changed

+152
-37
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/api-v4": Added
3+
---
4+
5+
Volume io_ready property ([#12903](https://github.com/linode/manager/pull/12903))

packages/api-v4/src/volumes/types.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,21 @@ export type VolumeEncryption = 'disabled' | 'enabled';
22

33
export interface Volume {
44
created: string;
5-
encryption?: VolumeEncryption; // @TODO BSE: Remove optionality once BSE is fully rolled out
5+
/**
6+
* Indicates whether a volume is encrypted or not
7+
*
8+
* @TODO BSE: Remove optionality once BSE is fully rolled out
9+
*/
10+
encryption?: VolumeEncryption; //
611
filesystem_path: string;
712
hardware_type: VolumeHardwareType;
813
id: number;
14+
/**
15+
* Indicates whether a volume is ready for I/O operations
16+
*
17+
* @TODO Remove optionality once io_ready is fully rolled out
18+
*/
19+
io_ready?: boolean;
920
label: string;
1021
linode_id: null | number;
1122
linode_label: null | string;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Added
3+
---
4+
5+
Volume attached to state ([#12903](https://github.com/linode/manager/pull/12903))
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import * as React from 'react';
2+
3+
import { volumeFactory } from 'src/factories';
4+
import { renderWithTheme } from 'src/utilities/testHelpers';
5+
6+
import { AttachedToValue } from './AttachedToValue';
7+
8+
describe('Volume action menu', () => {
9+
it('should show Linode label if the volume is attached', () => {
10+
const volume = volumeFactory.build({
11+
linode_id: 1,
12+
linode_label: 'linode_1',
13+
io_ready: true,
14+
});
15+
16+
const { getByText } = renderWithTheme(<AttachedToValue volume={volume} />);
17+
18+
expect(getByText(volume.linode_label!)).toBeVisible();
19+
});
20+
21+
it('should show Detach button if Linode is attached and onDetach function is provided', () => {
22+
const volume = volumeFactory.build({
23+
linode_id: 1,
24+
linode_label: 'linode_1',
25+
io_ready: true,
26+
});
27+
28+
const onDetach = () => {};
29+
30+
const { getByText } = renderWithTheme(
31+
<AttachedToValue onDetach={onDetach} volume={volume} />
32+
);
33+
34+
expect(getByText(volume.linode_label!)).toBeVisible();
35+
expect(getByText('Detach')).toBeVisible();
36+
});
37+
38+
it('should show Linode (restricted) if the Volume is attached to a restricted Linode', () => {
39+
const volume = volumeFactory.build({
40+
linode_id: 1,
41+
linode_label: null,
42+
io_ready: true,
43+
});
44+
45+
const { getByText } = renderWithTheme(<AttachedToValue volume={volume} />);
46+
47+
expect(getByText('Linode (restricted)')).toBeVisible();
48+
});
49+
50+
it('should show Unattached if the Volume is not attached to a Linode', () => {
51+
const volume = volumeFactory.build({
52+
linode_id: null,
53+
linode_label: null,
54+
io_ready: false,
55+
});
56+
57+
const { getByText } = renderWithTheme(<AttachedToValue volume={volume} />);
58+
59+
expect(getByText('Unattached')).toBeVisible();
60+
});
61+
});
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Box, LinkButton, TooltipIcon, Typography, useTheme } from '@linode/ui';
2+
import * as React from 'react';
3+
4+
import { Link } from 'src/components/Link';
5+
6+
import type { Volume } from '@linode/api-v4';
7+
8+
interface Props {
9+
onDetach?: () => void;
10+
volume: Volume;
11+
}
12+
13+
export const AttachedToValue = ({ onDetach, volume }: Props) => {
14+
const theme = useTheme();
15+
16+
if (volume.linode_label !== null && volume.linode_id !== null) {
17+
return (
18+
<Box sx={{ display: 'flex', gap: theme.spacingFunction(8) }}>
19+
<Link
20+
params={{ linodeId: volume.linode_id }}
21+
to="/linodes/$linodeId/storage"
22+
>
23+
{volume.linode_label}
24+
</Link>
25+
26+
{onDetach && (
27+
<>
28+
| <LinkButton onClick={onDetach}>Detach</LinkButton>
29+
</>
30+
)}
31+
</Box>
32+
);
33+
}
34+
35+
if (volume.linode_label === null && volume.io_ready) {
36+
return (
37+
<Box alignItems="center" display="flex" gap={theme.spacingFunction(4)}>
38+
<Typography color={theme.tokens.color.Neutrals[30]} data-qa-restricted>
39+
Linode (restricted)
40+
</Typography>
41+
<TooltipIcon
42+
data-qa-tooltip-restricted
43+
status="info"
44+
sxTooltipIcon={{ padding: 0 }}
45+
text="Contact your account manager to change permissions."
46+
tooltipPosition="right"
47+
/>
48+
</Box>
49+
);
50+
}
51+
52+
return <Typography data-qa-unattached>Unattached</Typography>;
53+
};

packages/manager/src/features/Volumes/Partials/VolumeTableRow.test.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,15 @@ describe('Volume table row', () => {
6666
);
6767

6868
// Check row for basic values
69-
expect(getByText(attachedVolume.label));
70-
expect(getByText(attachedVolume.size, { exact: false }));
71-
expect(getByTestId('region'));
72-
expect(getByText(attachedVolume.linode_label!));
69+
expect(getByText(attachedVolume.label)).toBeVisible();
70+
expect(getByText(attachedVolume.size, { exact: false })).toBeVisible();
71+
expect(getByTestId('region')).toBeVisible();
72+
expect(getByText(attachedVolume.linode_label!)).toBeVisible();
7373

7474
await userEvent.click(getByLabelText(/^Action menu for/));
7575

7676
// Make sure there is a detach button
77-
expect(getByText('Detach'));
77+
expect(getByText('Detach')).toBeVisible();
7878
});
7979

8080
it('should show Unattached if the Volume is not attached to a Linode', async () => {
@@ -83,12 +83,12 @@ describe('Volume table row', () => {
8383
<VolumeTableRow handlers={handlers} volume={unattachedVolume} />
8484
)
8585
);
86-
expect(getByText('Unattached'));
86+
expect(getByText('Unattached')).toBeVisible();
8787

8888
await userEvent.click(getByLabelText(/^Action menu for/));
8989

9090
// Make sure there is an attach button
91-
expect(getByText('Attach'));
91+
expect(getByText('Attach')).toBeVisible();
9292
});
9393

9494
it('should render an upgrade chip if the volume is eligible for an upgrade', async () => {
@@ -173,8 +173,8 @@ describe('Volume table row - for linodes detail page', () => {
173173
);
174174

175175
// Check row for basic values
176-
expect(getByText(attachedVolume.label));
177-
expect(getByText(attachedVolume.size, { exact: false }));
176+
expect(getByText(attachedVolume.label)).toBeVisible();
177+
expect(getByText(attachedVolume.size, { exact: false })).toBeVisible();
178178

179179
// Because we are on a Linode details page that has the region, we don't need to show
180180
// the volume's region. A Volume attached to a Linode must be in the same region.
@@ -186,7 +186,7 @@ describe('Volume table row - for linodes detail page', () => {
186186
await userEvent.click(getByLabelText(/^Action menu for/));
187187

188188
// Make sure there is a detach button
189-
expect(getByText('Detach'));
189+
expect(getByText('Detach')).toBeVisible();
190190
});
191191

192192
it('should show a high performance icon tooltip if Linode has the capability', async () => {

packages/manager/src/features/Volumes/Partials/VolumeTableRow.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useNotificationsQuery, useRegionsQuery } from '@linode/queries';
2-
import { Box, Chip, Typography } from '@linode/ui';
2+
import { Box, Chip } from '@linode/ui';
33
import { Hidden } from '@linode/ui';
44
import { getFormattedStatus } from '@linode/utilities';
55
import { useNavigate } from '@tanstack/react-router';
@@ -19,6 +19,7 @@ import {
1919
getEventProgress,
2020
volumeStatusIconMap,
2121
} from '../utils';
22+
import { AttachedToValue } from './AttachedToValue';
2223
import { VolumesActionMenu } from './VolumesActionMenu';
2324

2425
import type { ActionHandlers } from './VolumesActionMenu';
@@ -193,16 +194,7 @@ export const VolumeTableRow = React.memo((props: Props) => {
193194
)}
194195
{isVolumesLanding && (
195196
<TableCell data-qa-volume-cell-attachment={volume.linode_label}>
196-
{volume.linode_id !== null ? (
197-
<Link
198-
className="link secondaryLink"
199-
to={`/linodes/${volume.linode_id}/storage`}
200-
>
201-
{volume.linode_label}
202-
</Link>
203-
) : (
204-
<Typography data-qa-unattached>Unattached</Typography>
205-
)}
197+
<AttachedToValue volume={volume} />
206198
</TableCell>
207199
)}
208200
{isBlockStorageEncryptionFeatureEnabled && (

packages/manager/src/features/Volumes/VolumeDetails/VolumeEntityDetails/VolumeEntityDetailBody.tsx

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { useProfile, useRegionsQuery } from '@linode/queries';
2-
import { Box, LinkButton, Typography } from '@linode/ui';
2+
import { Box, Typography } from '@linode/ui';
33
import { getFormattedStatus } from '@linode/utilities';
44
import Grid from '@mui/material/Grid';
55
import { useTheme } from '@mui/material/styles';
66
import React from 'react';
77

88
import Lock from 'src/assets/icons/lock.svg';
99
import Unlock from 'src/assets/icons/unlock.svg';
10-
import { Link } from 'src/components/Link';
1110
import { StatusIcon } from 'src/components/StatusIcon/StatusIcon';
1211
import { formatDate } from 'src/utilities/formatDate';
1312

13+
import { AttachedToValue } from '../../Partials/AttachedToValue';
1414
import { volumeStatusIconMap } from '../../utils';
1515

1616
import type { Volume } from '@linode/api-v4';
@@ -103,19 +103,7 @@ export const VolumeEntityDetailBody = ({ volume, detachHandler }: Props) => {
103103
<Box>
104104
<Typography>Attached To</Typography>
105105
<Typography sx={(theme) => ({ font: theme.font.bold })}>
106-
{volume.linode_id !== null ? (
107-
<Box sx={{ display: 'flex', gap: theme.spacingFunction(8) }}>
108-
<Link
109-
className="link secondaryLink"
110-
to={`/linodes/${volume.linode_id}/storage`}
111-
>
112-
{volume.linode_label}
113-
</Link>
114-
| <LinkButton onClick={detachHandler}>Detach</LinkButton>
115-
</Box>
116-
) : (
117-
'Unattached'
118-
)}
106+
<AttachedToValue onDetach={detachHandler} volume={volume} />
119107
</Typography>
120108
</Box>
121109

0 commit comments

Comments
 (0)