diff --git a/packages/api-v4/.changeset/pr-11400-upcoming-features-1733998451458.md b/packages/api-v4/.changeset/pr-11400-upcoming-features-1733998451458.md new file mode 100644 index 00000000000..e41614586a6 --- /dev/null +++ b/packages/api-v4/.changeset/pr-11400-upcoming-features-1733998451458.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Upcoming Features +--- + +New `Block Storage Performance B1` linode capability ([#11400](https://github.com/linode/manager/pull/11400)) diff --git a/packages/api-v4/src/linodes/types.ts b/packages/api-v4/src/linodes/types.ts index 6f3d94caad3..5689c24ba34 100644 --- a/packages/api-v4/src/linodes/types.ts +++ b/packages/api-v4/src/linodes/types.ts @@ -20,7 +20,7 @@ export interface Linode { id: number; alerts: LinodeAlerts; backups: LinodeBackups; - capabilities?: LinodeCapabilities[]; // @TODO BSE: Remove optionality once BSE is fully rolled out + capabilities: LinodeCapabilities[]; created: string; disk_encryption?: EncryptionStatus; // @TODO LDE: Remove optionality once LDE is fully rolled out region: string; @@ -55,7 +55,10 @@ export interface LinodeBackups { last_successful: string | null; } -export type LinodeCapabilities = 'Block Storage Encryption' | 'SMTP Enabled'; +export type LinodeCapabilities = + | 'Block Storage Encryption' + | 'SMTP Enabled' + | 'Block Storage Performance B1'; export type Window = | 'Scheduling' diff --git a/packages/manager/.changeset/pr-11400-upcoming-features-1733927873131.md b/packages/manager/.changeset/pr-11400-upcoming-features-1733927873131.md new file mode 100644 index 00000000000..6db16c6f7d1 --- /dev/null +++ b/packages/manager/.changeset/pr-11400-upcoming-features-1733927873131.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +High performance volume indicator ([#11400](https://github.com/linode/manager/pull/11400)) diff --git a/packages/manager/src/__data__/linodes.ts b/packages/manager/src/__data__/linodes.ts index cf156461fa9..8cf0cac9de2 100644 --- a/packages/manager/src/__data__/linodes.ts +++ b/packages/manager/src/__data__/linodes.ts @@ -16,6 +16,7 @@ export const linode1: Linode = { window: 'W2', }, }, + capabilities: [], created: '2017-12-07T19:12:58', group: 'active', hypervisor: 'kvm', @@ -65,6 +66,7 @@ export const linode2: Linode = { window: 'Scheduling', }, }, + capabilities: [], created: '2018-02-22T16:11:07', group: 'inactive', hypervisor: 'kvm', @@ -114,6 +116,7 @@ export const linode3: Linode = { window: 'Scheduling', }, }, + capabilities: [], created: '2018-02-22T16:11:07', group: 'inactive', hypervisor: 'kvm', @@ -163,6 +166,7 @@ export const linode4: Linode = { window: 'Scheduling', }, }, + capabilities: [], created: '2018-02-22T16:11:07', group: 'inactive', hypervisor: 'kvm', diff --git a/packages/manager/src/features/Linodes/HighPerformanceVolumeIcon.tsx b/packages/manager/src/features/Linodes/HighPerformanceVolumeIcon.tsx new file mode 100644 index 00000000000..792e5d78462 --- /dev/null +++ b/packages/manager/src/features/Linodes/HighPerformanceVolumeIcon.tsx @@ -0,0 +1,33 @@ +import { IconButton, Tooltip } from '@linode/ui'; +import BoltIcon from '@mui/icons-material/Bolt'; +import React from 'react'; + +import type { LinodeCapabilities } from '@linode/api-v4'; + +interface Props { + linodeCapabilities: LinodeCapabilities[]; +} + +export function HighPerformanceVolumeIcon({ linodeCapabilities }: Props) { + const isHighPerformanceVolume = !!linodeCapabilities?.includes( + 'Block Storage Performance B1' + ); + + if (!isHighPerformanceVolume) { + return null; + } + + return ( + + + + + + ); +} diff --git a/packages/manager/src/features/Linodes/LinodeEntityDetail.tsx b/packages/manager/src/features/Linodes/LinodeEntityDetail.tsx index 8a698b468d5..4a9a9d19f65 100644 --- a/packages/manager/src/features/Linodes/LinodeEntityDetail.tsx +++ b/packages/manager/src/features/Linodes/LinodeEntityDetail.tsx @@ -117,6 +117,7 @@ export const LinodeEntityDetail = (props: Props) => { ipv6={trimmedIPv6} isLKELinode={Boolean(linode.lke_cluster_id)} isVPCOnlyLinode={isVPCOnlyLinode} + linodeCapabilities={linode.capabilities} linodeId={linode.id} linodeIsInDistributedRegion={linodeIsInDistributedRegion} linodeLabel={linode.label} diff --git a/packages/manager/src/features/Linodes/LinodeEntityDetailBody.tsx b/packages/manager/src/features/Linodes/LinodeEntityDetailBody.tsx index 3a305a8c6d0..262899eedb3 100644 --- a/packages/manager/src/features/Linodes/LinodeEntityDetailBody.tsx +++ b/packages/manager/src/features/Linodes/LinodeEntityDetailBody.tsx @@ -17,8 +17,9 @@ import { usePreferences } from 'src/queries/profile/preferences'; import { useProfile } from 'src/queries/profile/profile'; import { pluralize } from 'src/utilities/pluralize'; -import { encryptionStatusTestId } from '../Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable'; import { EncryptedStatus } from '../Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable'; +import { encryptionStatusTestId } from '../Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable'; +import { HighPerformanceVolumeIcon } from './HighPerformanceVolumeIcon'; import { StyledBodyGrid, StyledColumnLabelGrid, @@ -41,6 +42,7 @@ import type { EncryptionStatus, Interface, Linode, + LinodeCapabilities, } from '@linode/api-v4/lib/linodes/types'; import type { Subnet } from '@linode/api-v4/lib/vpcs'; import type { TypographyProps } from '@linode/ui'; @@ -66,6 +68,7 @@ export interface BodyProps { ipv6: Linode['ipv6']; isLKELinode: boolean; // indicates whether linode belongs to an LKE cluster isVPCOnlyLinode: boolean; + linodeCapabilities: LinodeCapabilities[]; linodeId: number; linodeIsInDistributedRegion: boolean; linodeLabel: string; @@ -85,6 +88,7 @@ export const LinodeEntityDetailBody = React.memo((props: BodyProps) => { ipv6, isLKELinode, isVPCOnlyLinode, + linodeCapabilities, linodeId, linodeIsInDistributedRegion, linodeLabel, @@ -151,9 +155,23 @@ export const LinodeEntityDetailBody = React.memo((props: BodyProps) => { {gbRAM} GB RAM - - {pluralize('Volume', 'Volumes', numVolumes)} - + ({ + alignItems: 'center', + display: 'flex', + gap: theme.spacing(), + })} + > + + {pluralize('Volume', 'Volumes', numVolumes)} + + + {numVolumes > 0 && ( + + )} + {isDiskEncryptionFeatureEnabled && encryptionStatus && ( diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/LinodeVolumes.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/LinodeVolumes.tsx index d24cac6b32d..a645ef6c422 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/LinodeVolumes.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/LinodeVolumes.tsx @@ -164,6 +164,7 @@ export const LinodeVolumes = () => { } isDetailsPageRow key={volume.id} + linodeCapabilities={linode?.capabilities} volume={volume} /> ); diff --git a/packages/manager/src/features/Linodes/LinodesLanding/LinodeRow/LinodeRow.test.tsx b/packages/manager/src/features/Linodes/LinodesLanding/LinodeRow/LinodeRow.test.tsx index 8d2a11edd56..d25057816fc 100644 --- a/packages/manager/src/features/Linodes/LinodesLanding/LinodeRow/LinodeRow.test.tsx +++ b/packages/manager/src/features/Linodes/LinodesLanding/LinodeRow/LinodeRow.test.tsx @@ -33,6 +33,7 @@ describe('LinodeRow', () => { }} alerts={linode.alerts} backups={linode.backups} + capabilities={linode.capabilities} created={linode.created} group={linode.group} hypervisor={linode.hypervisor} diff --git a/packages/manager/src/features/Linodes/LinodesLanding/ListView.tsx b/packages/manager/src/features/Linodes/LinodesLanding/ListView.tsx index e09cc3a0326..2ea351f5d50 100644 --- a/packages/manager/src/features/Linodes/LinodesLanding/ListView.tsx +++ b/packages/manager/src/features/Linodes/LinodesLanding/ListView.tsx @@ -38,6 +38,7 @@ export const ListView = (props: RenderLinodesProps) => { }} alerts={linode.alerts} backups={linode.backups} + capabilities={linode.capabilities} created={linode.created} group={linode.group} hypervisor={linode.hypervisor} diff --git a/packages/manager/src/features/Volumes/VolumeTableRow.test.tsx b/packages/manager/src/features/Volumes/VolumeTableRow.test.tsx index 2c745da6744..e101f8a40b0 100644 --- a/packages/manager/src/features/Volumes/VolumeTableRow.test.tsx +++ b/packages/manager/src/features/Volumes/VolumeTableRow.test.tsx @@ -1,3 +1,4 @@ +import { waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; @@ -174,4 +175,23 @@ describe('Volume table row - for linodes detail page', () => { // Make sure there is a detach button expect(getByText('Detach')); }); + + it('should show a high performance icon tooltip if Linode has the capability', async () => { + const { getByLabelText, getByText } = await renderWithThemeAndRouter( + wrapWithTableBody( + + ) + ); + + const highPerformanceIcon = getByLabelText('High Performance'); + + expect(highPerformanceIcon).toBeVisible(); + await userEvent.click(highPerformanceIcon); + await waitFor(() => expect(getByText('High Performance')).toBeVisible()); + }); }); diff --git a/packages/manager/src/features/Volumes/VolumeTableRow.tsx b/packages/manager/src/features/Volumes/VolumeTableRow.tsx index 6b0a7f81124..c6609a6b34f 100644 --- a/packages/manager/src/features/Volumes/VolumeTableRow.tsx +++ b/packages/manager/src/features/Volumes/VolumeTableRow.tsx @@ -12,6 +12,7 @@ import { useNotificationsQuery } from 'src/queries/account/notifications'; import { useInProgressEvents } from 'src/queries/events/events'; import { useRegionsQuery } from 'src/queries/regions/regions'; +import { HighPerformanceVolumeIcon } from '../Linodes/HighPerformanceVolumeIcon'; import { getDerivedVolumeStatusFromStatusAndEvent, getEventProgress, @@ -20,7 +21,7 @@ import { import { VolumesActionMenu } from './VolumesActionMenu'; import type { ActionHandlers } from './VolumesActionMenu'; -import type { Volume } from '@linode/api-v4'; +import type { LinodeCapabilities, Volume } from '@linode/api-v4'; export const useStyles = makeStyles()({ volumePath: { @@ -33,6 +34,7 @@ interface Props { handlers: ActionHandlers; isBlockStorageEncryptionFeatureEnabled?: boolean; isDetailsPageRow?: boolean; + linodeCapabilities?: LinodeCapabilities[]; volume: Volume; } @@ -42,6 +44,7 @@ export const VolumeTableRow = React.memo((props: Props) => { handlers, isBlockStorageEncryptionFeatureEnabled, isDetailsPageRow, + linodeCapabilities, volume, } = props; @@ -115,7 +118,21 @@ export const VolumeTableRow = React.memo((props: Props) => { wrap: 'nowrap', }} > - {volume.label} + ({ + alignItems: 'center', + display: 'flex', + gap: theme.spacing(), + })} + > + {volume.label} + {linodeCapabilities && ( + + )} + + {isEligibleForUpgradeToNVMe && ( ({ dbEntities: await mswDB.getAll('linodes'), - seedEntities: linodeFactory.buildList(count), + seedEntities: linodeFactory.buildList(count, { + capabilities: ['Block Storage Performance B1'], + }), }); const configs: [number, Config][] = linodeSeeds.map((linodeSeed) => {