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
5 changes: 5 additions & 0 deletions packages/api-v4/.changeset/pr-12903-added-1759133172067.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Added
---

Volume io_ready property ([#12903](https://github.com/linode/manager/pull/12903))
13 changes: 12 additions & 1 deletion packages/api-v4/src/volumes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@

export interface Volume {
created: string;
encryption?: VolumeEncryption; // @TODO BSE: Remove optionality once BSE is fully rolled out
/**
* Indicates whether a volume is encrypted or not
*
* @TODO BSE: Remove optionality once BSE is fully rolled out

Check warning on line 8 in packages/api-v4/src/volumes/types.ts

View workflow job for this annotation

GitHub Actions / ESLint Review (api-v4)

[eslint] reported by reviewdog 🐢 Complete the task associated to this "TODO" comment. Raw Output: {"ruleId":"sonarjs/todo-tag","severity":1,"message":"Complete the task associated to this \"TODO\" comment.","line":8,"column":7,"nodeType":null,"messageId":"completeTODO","endLine":8,"endColumn":11}
*/
encryption?: VolumeEncryption; //

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
encryption?: VolumeEncryption; //
encryption?: VolumeEncryption;

filesystem_path: string;
hardware_type: VolumeHardwareType;
id: number;
/**
* Indicates whether a volume is ready for I/O operations
*
* @TODO Remove optionality once io_ready is fully rolled out

Check warning on line 17 in packages/api-v4/src/volumes/types.ts

View workflow job for this annotation

GitHub Actions / ESLint Review (api-v4)

[eslint] reported by reviewdog 🐢 Complete the task associated to this "TODO" comment. Raw Output: {"ruleId":"sonarjs/todo-tag","severity":1,"message":"Complete the task associated to this \"TODO\" comment.","line":17,"column":7,"nodeType":null,"messageId":"completeTODO","endLine":17,"endColumn":11}
*/
io_ready?: boolean;
label: string;
linode_id: null | number;
linode_label: null | string;
Expand Down
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-12903-added-1759133146190.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Added
---

Volume attached to state ([#12903](https://github.com/linode/manager/pull/12903))
Comment thread
dchyrva-akamai marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as React from 'react';

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

import { AttachedToValue } from './AttachedToValue';

describe('Volume action menu', () => {
it('should show Linode label if the volume is attached', () => {
const volume = volumeFactory.build({
linode_id: 1,
linode_label: 'linode_1',
io_ready: true,
});

const { getByText } = renderWithTheme(<AttachedToValue volume={volume} />);

expect(getByText(volume.linode_label!)).toBeVisible();
});

it('should show Detach button if Linode is attached and onDetach function is provided', () => {
const volume = volumeFactory.build({
linode_id: 1,
linode_label: 'linode_1',
io_ready: true,
});

const onDetach = () => {};

const { getByText } = renderWithTheme(
<AttachedToValue onDetach={onDetach} volume={volume} />
);

expect(getByText(volume.linode_label!)).toBeVisible();
expect(getByText('Detach')).toBeVisible();
});

it('should show Linode (restricted) if the Volume is attached to a restricted Linode', () => {
const volume = volumeFactory.build({
linode_id: 1,
linode_label: null,
io_ready: true,
});

const { getByText } = renderWithTheme(<AttachedToValue volume={volume} />);

expect(getByText('Linode (restricted)')).toBeVisible();
});

it('should show Unattached if the Volume is not attached to a Linode', () => {
const volume = volumeFactory.build({
linode_id: null,
linode_label: null,
io_ready: false,
});

const { getByText } = renderWithTheme(<AttachedToValue volume={volume} />);

expect(getByText('Unattached')).toBeVisible();
});
});
53 changes: 53 additions & 0 deletions packages/manager/src/features/Volumes/Partials/AttachedToValue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Box, LinkButton, TooltipIcon, Typography, useTheme } from '@linode/ui';
import * as React from 'react';

import { Link } from 'src/components/Link';

import type { Volume } from '@linode/api-v4';

interface Props {
onDetach?: () => void;
volume: Volume;
}

export const AttachedToValue = ({ onDetach, volume }: Props) => {
const theme = useTheme();

if (volume.linode_label !== null && volume.linode_id !== null) {
return (
<Box sx={{ display: 'flex', gap: theme.spacingFunction(8) }}>
<Link
params={{ linodeId: volume.linode_id }}
to="/linodes/$linodeId/storage"
>
{volume.linode_label}
</Link>

{onDetach && (
<>
| <LinkButton onClick={onDetach}>Detach</LinkButton>
</>
)}
</Box>
);
Comment thread
bnussman-akamai marked this conversation as resolved.
}

if (volume.linode_label === null && volume.io_ready) {
return (
<Box alignItems="center" display="flex" gap={theme.spacingFunction(4)}>
<Typography color={theme.tokens.color.Neutrals[30]} data-qa-restricted>
Linode (restricted)
</Typography>
<TooltipIcon
data-qa-tooltip-restricted
status="info"
sxTooltipIcon={{ padding: 0 }}
text="Contact your account manager to change permissions."
tooltipPosition="right"
/>
</Box>
);
}

return <Typography data-qa-unattached>Unattached</Typography>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ describe('Volume table row', () => {
);

// Check row for basic values
expect(getByText(attachedVolume.label));
expect(getByText(attachedVolume.size, { exact: false }));
expect(getByTestId('region'));
expect(getByText(attachedVolume.linode_label!));
expect(getByText(attachedVolume.label)).toBeVisible();
expect(getByText(attachedVolume.size, { exact: false })).toBeVisible();
expect(getByTestId('region')).toBeVisible();
expect(getByText(attachedVolume.linode_label!)).toBeVisible();

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

// Make sure there is a detach button
expect(getByText('Detach'));
expect(getByText('Detach')).toBeVisible();
});

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

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

// Make sure there is an attach button
expect(getByText('Attach'));
expect(getByText('Attach')).toBeVisible();
});

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

// Check row for basic values
expect(getByText(attachedVolume.label));
expect(getByText(attachedVolume.size, { exact: false }));
expect(getByText(attachedVolume.label)).toBeVisible();
expect(getByText(attachedVolume.size, { exact: false })).toBeVisible();

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

// Make sure there is a detach button
expect(getByText('Detach'));
expect(getByText('Detach')).toBeVisible();
});

it('should show a high performance icon tooltip if Linode has the capability', async () => {
Expand Down
14 changes: 3 additions & 11 deletions packages/manager/src/features/Volumes/Partials/VolumeTableRow.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useNotificationsQuery, useRegionsQuery } from '@linode/queries';
import { Box, Chip, Typography } from '@linode/ui';
import { Box, Chip } from '@linode/ui';
import { Hidden } from '@linode/ui';
import { getFormattedStatus } from '@linode/utilities';
import { useNavigate } from '@tanstack/react-router';
Expand All @@ -19,6 +19,7 @@ import {
getEventProgress,
volumeStatusIconMap,
} from '../utils';
import { AttachedToValue } from './AttachedToValue';
import { VolumesActionMenu } from './VolumesActionMenu';

import type { ActionHandlers } from './VolumesActionMenu';
Expand Down Expand Up @@ -193,16 +194,7 @@ export const VolumeTableRow = React.memo((props: Props) => {
)}
{isVolumesLanding && (
<TableCell data-qa-volume-cell-attachment={volume.linode_label}>
{volume.linode_id !== null ? (
<Link
className="link secondaryLink"
to={`/linodes/${volume.linode_id}/storage`}
>
{volume.linode_label}
</Link>
) : (
<Typography data-qa-unattached>Unattached</Typography>
)}
<AttachedToValue volume={volume} />
</TableCell>
)}
{isBlockStorageEncryptionFeatureEnabled && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { useProfile, useRegionsQuery } from '@linode/queries';
import { Box, LinkButton, Typography } from '@linode/ui';
import { Box, Typography } from '@linode/ui';
import { getFormattedStatus } from '@linode/utilities';
import Grid from '@mui/material/Grid';
import { useTheme } from '@mui/material/styles';
import React from 'react';

import Lock from 'src/assets/icons/lock.svg';
import Unlock from 'src/assets/icons/unlock.svg';
import { Link } from 'src/components/Link';
import { StatusIcon } from 'src/components/StatusIcon/StatusIcon';
import { formatDate } from 'src/utilities/formatDate';

import { AttachedToValue } from '../../Partials/AttachedToValue';
import { volumeStatusIconMap } from '../../utils';

import type { Volume } from '@linode/api-v4';
Expand Down Expand Up @@ -103,19 +103,7 @@ export const VolumeEntityDetailBody = ({ volume, detachHandler }: Props) => {
<Box>
<Typography>Attached To</Typography>
<Typography sx={(theme) => ({ font: theme.font.bold })}>
{volume.linode_id !== null ? (
<Box sx={{ display: 'flex', gap: theme.spacingFunction(8) }}>
<Link
className="link secondaryLink"
to={`/linodes/${volume.linode_id}/storage`}
>
{volume.linode_label}
</Link>
| <LinkButton onClick={detachHandler}>Detach</LinkButton>
</Box>
) : (
'Unattached'
)}
<AttachedToValue onDetach={detachHandler} volume={volume} />
</Typography>
</Box>

Expand Down