Skip to content

Commit

Permalink
change: [M3-6546] Volume drawers and action menu refactored.
Browse files Browse the repository at this point in the history
  • Loading branch information
dchyrva-akamai committed Sep 16, 2024
1 parent f0d9765 commit 008e322
Show file tree
Hide file tree
Showing 26 changed files with 364 additions and 126 deletions.
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-10820-changed-1725610034084.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Changed
---

Volume drawers and action menu refactored ([#10820](https://github.com/linode/manager/pull/10820))
18 changes: 1 addition & 17 deletions packages/manager/cypress/e2e/core/volumes/clone-volume.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import type { VolumeRequestPayload } from '@linode/api-v4';
import { createVolume } from '@linode/api-v4/lib/volumes';
import { Volume } from '@linode/api-v4';
import { volumeRequestPayloadFactory } from 'src/factories/volume';
import { authenticate } from 'support/api/authentication';
import { interceptCloneVolume } from 'support/intercepts/volumes';
import { SimpleBackoffMethod } from 'support/util/backoff';
import { cleanUp } from 'support/util/cleanup';
import { pollVolumeStatus } from 'support/util/polling';
import { randomLabel } from 'support/util/random';
import { chooseRegion } from 'support/util/regions';
import { createActiveVolume } from 'support/api/volumes';

// Local storage override to force volume table to list up to 100 items.
// This is a workaround while we wait to get stuck volumes removed.
Expand All @@ -17,19 +14,6 @@ const pageSizeOverride = {
PAGE_SIZE: 100,
};

/**
* Creates a Volume and waits for it to become active.
*
* @param volumeRequest - Volume create request payload.
*
* @returns Promise that resolves to created Volume.
*/
const createActiveVolume = async (volumeRequest: VolumeRequestPayload) => {
const volume = await createVolume(volumeRequest);
await pollVolumeStatus(volume.id, 'active', new SimpleBackoffMethod(10000));
return volume;
};

authenticate();
describe('volume clone flow', () => {
before(() => {
Expand Down
103 changes: 87 additions & 16 deletions packages/manager/cypress/e2e/core/volumes/update-volume.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { createVolume } from '@linode/api-v4/lib/volumes';
import { Volume } from '@linode/api-v4';

import { volumeRequestPayloadFactory } from 'src/factories/volume';
import { authenticate } from 'support/api/authentication';
import { randomLabel } from 'support/util/random';
import { chooseRegion } from 'support/util/regions';
import { cleanUp } from 'support/util/cleanup';
import { ui } from 'support/ui';
import { createActiveVolume } from 'support/api/volumes';

authenticate();
describe('volume update flow', () => {
Expand All @@ -16,18 +18,17 @@ describe('volume update flow', () => {
});

/*
* - Confirms that volume label and tags can be changed from the Volumes landing page.
* - Confirms that volume label can be changed from the Volumes landing page.
*/
it("updates a volume's label and tags", () => {
it("updates a volume's label", () => {
const volumeRequest = volumeRequestPayloadFactory.build({
label: randomLabel(),
region: chooseRegion().id,
});

const newLabel = randomLabel();
const newTags = [randomLabel(5), randomLabel(5), randomLabel(5)];

cy.defer(() => createVolume(volumeRequest), 'creating volume').then(
cy.defer(() => createActiveVolume(volumeRequest), 'creating volume').then(
(volume: Volume) => {
cy.visitWithLogin('/volumes', {
// Temporarily force volume table to show up to 100 results per page.
Expand All @@ -43,17 +44,83 @@ describe('volume update flow', () => {
.should('be.visible')
.closest('tr')
.within(() => {
cy.findByText('Edit').click();
cy.findByText('active').should('be.visible');
});
ui.actionMenu
.findByTitle(`Action menu for Volume ${volume.label}`)
.should('be.visible')
.click();
cy.get('[data-testid="Edit"]').click();

// Enter new label and add tags, click "Save Changes".
// Enter new label, click "Save Changes".
cy.get('[data-qa-drawer="true"]').within(() => {
cy.findByText('Edit Volume').should('be.visible');
cy.findByDisplayValue(volume.label)
.should('be.visible')
.click()
.type(`{selectall}{backspace}${newLabel}`);

cy.findByText('Save Changes').should('be.visible').click();
});

// Confirm new label is applied, click "Edit" to re-open drawer.
cy.findByText(newLabel).should('be.visible');
ui.actionMenu
.findByTitle(`Action menu for Volume ${newLabel}`)
.should('be.visible')
.click();
cy.get('[data-testid="Edit"]').click();

// Confirm new label is shown.
cy.get('[data-qa-drawer="true"]').within(() => {
cy.findByText('Edit Volume').should('be.visible');
cy.findByDisplayValue(newLabel).should('be.visible');
});
}
);
});

/*
* - Confirms that volume tags can be changed from the Volumes landing page.
*/
it("updates volume's tags", () => {
const volumeRequest = volumeRequestPayloadFactory.build({
label: randomLabel(),
region: chooseRegion().id,
});

const newTags = [randomLabel(5), randomLabel(5), randomLabel(5)];

cy.defer(() => createActiveVolume(volumeRequest), 'creating volume').then(
(volume: Volume) => {
cy.visitWithLogin('/volumes', {
// Temporarily force volume table to show up to 100 results per page.
// This is a workaround while we wait to get stuck volumes removed.
// @TODO Remove local storage override when stuck volumes are removed from test accounts.
localStorageOverrides: {
PAGE_SIZE: 100,
},
});

// Confirm that volume is listed on landing page, click "Edit" to open drawer.
cy.findByText(volume.label)
.should('be.visible')
.closest('tr')
.within(() => {
cy.findByText('active').should('be.visible');
});

ui.actionMenu
.findByTitle(`Action menu for Volume ${volume.label}`)
.should('be.visible')
.click();

cy.get('[data-testid="Manage Tags"]').click();

// Add tags, click "Save Changes".
cy.get('[data-qa-drawer="true"]').within(() => {
cy.findByText('Manage Volume Tags').should('be.visible');

cy.findByPlaceholderText('Type to choose or create a tag.')
.should('be.visible')
.click()
Expand All @@ -62,18 +129,18 @@ describe('volume update flow', () => {
cy.findByText('Save Changes').should('be.visible').click();
});

// Confirm new label is applied, click "Edit" to re-open drawer.
cy.findByText(newLabel)
// Confirm new tags are shown, click "Manage Volume Tags" to re-open drawer.
cy.findByText(volumeRequest.label).should('be.visible');

ui.actionMenu
.findByTitle(`Action menu for Volume ${volume.label}`)
.should('be.visible')
.closest('tr')
.within(() => {
cy.findByText('Edit').click();
});
.click();

cy.get('[data-testid="Manage Tags"]').click();

// Confirm new label and tags are shown.
cy.get('[data-qa-drawer="true"]').within(() => {
cy.findByText('Edit Volume').should('be.visible');
cy.findByDisplayValue(newLabel).should('be.visible');
cy.findByText('Manage Volume Tags').should('be.visible');

// Click the tags input field to see all the selected tags
cy.findByRole('combobox').should('be.visible').click();
Expand All @@ -85,4 +152,8 @@ describe('volume update flow', () => {
}
);
});

after(() => {
cleanUp(['tags', 'volumes']);
});
});
28 changes: 26 additions & 2 deletions packages/manager/cypress/support/api/volumes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { Volume, deleteVolume, detachVolume, getVolumes } from '@linode/api-v4';
import {
createVolume,
deleteVolume,
detachVolume,
getVolumes,
} from '@linode/api-v4';
import { pageSize } from 'support/constants/api';
import { SimpleBackoffMethod, attemptWithBackoff } from 'support/util/backoff';
import { depaginate } from 'support/util/paginate';
import { pollVolumeStatus } from 'support/util/polling';

import { isTestLabel } from './common';
import { attemptWithBackoff, SimpleBackoffMethod } from 'support/util/backoff';

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

/**
* Delete all Volumes whose labels are prefixed "cy-test-".
Expand Down Expand Up @@ -45,3 +54,18 @@ export const deleteAllTestVolumes = async (): Promise<void> => {

await Promise.all(detachDeletePromises);
};

/**
* Creates a Volume and waits for it to become active.
*
* @param volumeRequest - Volume create request payload.
*
* @returns Promise that resolves to created Volume.
*/
export const createActiveVolume = async (
volumeRequest: VolumeRequestPayload
) => {
const volume = await createVolume(volumeRequest);
await pollVolumeStatus(volume.id, 'active', new SimpleBackoffMethod(10000));
return volume;
};
1 change: 1 addition & 0 deletions packages/manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"build:analyze": "bunx vite-bundle-visualizer",
"precommit": "lint-staged && yarn typecheck",
"test": "vitest run",
"test:ui": "vitest --ui",
"test:debug": "node --inspect-brk scripts/test.js --runInBand",
"storybook": "storybook dev -p 6006",
"storybook-static": "storybook build -c .storybook -o .out",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,16 @@ import type { LinodeCreateFormValues } from './utilities';

describe('Security', () => {
// TODO: Unskip once M3-8559 is addressed.
it.skip(
'should render a root password input',
async () => {
const { findByLabelText } = renderWithThemeAndHookFormContext({
component: <Security />,
});

const rootPasswordInput = await findByLabelText('Root Password');

expect(rootPasswordInput).toBeVisible();
expect(rootPasswordInput).toBeEnabled();
},
{ timeout: 5_000 }
);
it.skip('should render a root password input', async () => {
const { findByLabelText } = renderWithThemeAndHookFormContext({
component: <Security />,
});

const rootPasswordInput = await findByLabelText('Root Password');

expect(rootPasswordInput).toBeVisible();
expect(rootPasswordInput).toBeEnabled();
});

it('should render a SSH Keys heading', async () => {
const { getAllByText } = renderWithThemeAndHookFormContext({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import { useParams } from 'react-router-dom';
import type { Volume } from '@linode/api-v4';

import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
Expand All @@ -17,12 +18,13 @@ import { TableRowError } from 'src/components/TableRowError/TableRowError';
import { TableRowLoading } from 'src/components/TableRowLoading/TableRowLoading';
import { TableSortCell } from 'src/components/TableSortCell';
import { Typography } from 'src/components/Typography';
import { CloneVolumeDrawer } from 'src/features/Volumes/CloneVolumeDrawer';
import { DeleteVolumeDialog } from 'src/features/Volumes/DeleteVolumeDialog';
import { DetachVolumeDialog } from 'src/features/Volumes/DetachVolumeDialog';
import { EditVolumeDrawer } from 'src/features/Volumes/EditVolumeDrawer';
import { ResizeVolumeDrawer } from 'src/features/Volumes/ResizeVolumeDrawer';
import { VolumeDetailsDrawer } from 'src/features/Volumes/VolumeDetailsDrawer';
import { DeleteVolumeDialog } from 'src/features/Volumes/Dialogs/DeleteVolumeDialog';
import { DetachVolumeDialog } from 'src/features/Volumes/Dialogs/DetachVolumeDialog';
import { CloneVolumeDrawer } from 'src/features/Volumes/Drawers/CloneVolumeDrawer';
import { EditVolumeDrawer } from 'src/features/Volumes/Drawers/EditVolumeDrawer';
import { ManageTagsDrawer } from 'src/features/Volumes/Drawers/ManageTagsDrawer';
import { ResizeVolumeDrawer } from 'src/features/Volumes/Drawers/ResizeVolumeDrawer';
import { VolumeDetailsDrawer } from 'src/features/Volumes/Drawers/VolumeDetailsDrawer';
import { LinodeVolumeAddDrawer } from 'src/features/Volumes/VolumeDrawer/LinodeVolumeAddDrawer';
import { VolumeTableRow } from 'src/features/Volumes/VolumeTableRow';
import { useIsResourceRestricted } from 'src/hooks/useIsResourceRestricted';
Expand All @@ -32,8 +34,6 @@ import { useLinodeQuery } from 'src/queries/linodes/linodes';
import { useRegionsQuery } from 'src/queries/regions/regions';
import { useLinodeVolumesQuery } from 'src/queries/volumes/volumes';

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

export const preferenceKey = 'linode-volumes';

export const LinodeVolumes = () => {
Expand Down Expand Up @@ -78,6 +78,9 @@ export const LinodeVolumes = () => {
isBlockStorageEncryptionFeatureEnabled,
} = useIsBlockStorageEncryptionFeatureEnabled();

const [isManageTagsDrawerOpen, setisManageTagsDrawerOpen] = React.useState(
false
);
const [selectedVolumeId, setSelectedVolumeId] = React.useState<number>();
const [isDetailsDrawerOpen, setIsDetailsDrawerOpen] = React.useState(false);
const [isEditDrawerOpen, setIsEditDrawerOpen] = React.useState(false);
Expand Down Expand Up @@ -109,6 +112,11 @@ export const LinodeVolumes = () => {
setIsEditDrawerOpen(true);
};

const handleManageTags = (volume: Volume) => {
setSelectedVolumeId(volume.id);
setisManageTagsDrawerOpen(true);
};

const handleResize = (volume: Volume) => {
setSelectedVolumeId(volume.id);
setIsResizeDrawerOpen(true);
Expand Down Expand Up @@ -159,6 +167,7 @@ export const LinodeVolumes = () => {
handleDetach: () => handleDetach(volume),
handleDetails: () => handleDetails(volume),
handleEdit: () => handleEdit(volume),
handleManageTags: () => handleManageTags(volume),
handleResize: () => handleResize(volume),
handleUpgrade: () => null,
}}
Expand Down Expand Up @@ -261,6 +270,11 @@ export const LinodeVolumes = () => {
open={isEditDrawerOpen}
volume={selectedVolume}
/>
<ManageTagsDrawer
onClose={() => setisManageTagsDrawerOpen(false)}
open={isManageTagsDrawerOpen}
volume={selectedVolume}
/>
<ResizeVolumeDrawer
onClose={() => setIsResizeDrawerOpen(false)}
open={isResizeDrawerOpen}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ConfirmationDialog } from 'src/components/ConfirmationDialog/Confirmati
import { Notice } from 'src/components/Notice/Notice';
import { Stack } from 'src/components/Stack';
import { Typography } from 'src/components/Typography';
import { VolumeUpgradeCopy } from 'src/features/Volumes/UpgradeVolumeDialog';
import { VolumeUpgradeCopy } from 'src/features/Volumes/Dialogs/UpgradeVolumeDialog';
import { getUpgradeableVolumeIds } from 'src/features/Volumes/utils';
import { useNotificationsQuery } from 'src/queries/account/notifications';
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { accountFactory, volumeFactory } from 'src/factories';
import { HttpResponse, http, server } from 'src/mocks/testServer';
import { renderWithTheme } from 'src/utilities/testHelpers';

import { AttachVolumeDrawer } from './AttachVolumeDrawer';
import { AttachVolumeDrawer } from './Drawers/AttachVolumeDrawer';

const accountEndpoint = '*/v4/account';
const encryptionLabelText = 'Encrypt Volume';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { useGrants } from 'src/queries/profile/profile';
import { useAttachVolumeMutation } from 'src/queries/volumes/volumes';
import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor';

import { ConfigSelect } from './VolumeDrawer/ConfigSelect';
import { ConfigSelect } from '../VolumeDrawer/ConfigSelect';

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import {
} from 'src/utilities/formikErrorUtils';
import { PRICES_RELOAD_ERROR_NOTICE_TEXT } from 'src/utilities/pricing/constants';

import { PricePanel } from './VolumeDrawer/PricePanel';

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

interface Props {
onClose: () => void;
open: boolean;
Expand Down
Loading

0 comments on commit 008e322

Please sign in to comment.