Skip to content

Commit

Permalink
feat: [M3-7169, M3-7193, M3-7211, M3-7255, M3-7258] - VPC UX feedback (
Browse files Browse the repository at this point in the history
…#9832)

## Description 📝
Address UX feedback for VPC

## Changes  🔄
- Updated VPC empty landing text
- New VPC icon
- Minor UI changes to Assign Linode to Subnet drawer
- Updated tooltip text for the Auto-assign a VPC IPv4 address checkbox
- Minor text UI changes to VPC details page

## How to test 🧪

### Prerequisites
- Point to the dev env and ensure your account has vpc customer tags

### Verification steps 
Verify the ticket changes requested and check these flows:
- VPC empty landing (set line 87 to true in `VPCLanding`)
- VPC side menu and create menu icons
- Assign Linode to Subnet drawer
- VPC details page (changes are more noticeable in Safari)
  • Loading branch information
hana-akamai authored Oct 27, 2023
1 parent 654d1bf commit 9d323ef
Show file tree
Hide file tree
Showing 15 changed files with 123 additions and 83 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

VPC UX feedback ([#9832](https://github.com/linode/manager/pull/9832))
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe('VPC details page', () => {

// Confirm that user is redirected to VPC landing page.
cy.url().should('endWith', '/vpcs');
cy.findByText('Create a private and isolated network.');
cy.findByText('Create a private and isolated network');
});

/**
Expand Down
11 changes: 4 additions & 7 deletions packages/manager/cypress/e2e/core/vpc/vpc-landing-page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { vpcFactory } from '@src/factories';
import { ui } from 'support/ui';
import { randomLabel, randomPhrase } from 'support/util/random';
import { chooseRegion, getRegionById } from 'support/util/regions';
import { VPC_LABEL } from 'src/features/VPCs/constants';

// TODO Remove feature flag mocks when feature flag is removed from codebase.
describe('VPC landing page', () => {
Expand Down Expand Up @@ -65,10 +66,8 @@ describe('VPC landing page', () => {
cy.wait(['@getFeatureFlags', '@getClientStream', '@getVPCs']);

// Confirm that empty state is shown and that each section is present.
cy.findByText('VPCs').should('be.visible');
cy.findByText('Create a private and isolated network.').should(
'be.visible'
);
cy.findByText(VPC_LABEL).should('be.visible');
cy.findByText('Create a private and isolated network').should('be.visible');
cy.findByText('Getting Started Guides').should('be.visible');
cy.findByText('Video Playlist').should('be.visible');

Expand Down Expand Up @@ -268,9 +267,7 @@ describe('VPC landing page', () => {
cy.wait(['@deleteVPC', '@getVPCs']);
ui.toast.assertMessage('VPC deleted successfully.');
cy.findByText(mockVPCs[1].label).should('not.exist');
cy.findByText('Create a private and isolated network.').should(
'be.visible'
);
cy.findByText('Create a private and isolated network').should('be.visible');
});

/*
Expand Down
7 changes: 3 additions & 4 deletions packages/manager/src/assets/icons/entityIcons/vpc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Stack } from 'src/components/Stack';
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import * as React from 'react';
Expand All @@ -9,10 +8,12 @@ import Select, { Item } from 'src/components/EnhancedSelect';
import { FormControlLabel } from 'src/components/FormControlLabel';
import { Link } from 'src/components/Link';
import { Paper } from 'src/components/Paper';
import { Stack } from 'src/components/Stack';
import { TextField } from 'src/components/TextField';
import { TooltipIcon } from 'src/components/TooltipIcon';
import { Typography } from 'src/components/Typography';
import { APP_ROOT } from 'src/constants';
import { VPC_AUTO_ASSIGN_IPV4_TOOLTIP } from 'src/features/VPCs/constants';
import { useAccountManagement } from 'src/hooks/useAccountManagement';
import { useFlags } from 'src/hooks/useFlags';
import { useRegionsQuery } from 'src/queries/regions';
Expand Down Expand Up @@ -255,10 +256,8 @@ export const VPCPanel = (props: VPCPanelProps) => {
Auto-assign a VPC IPv4 address for this Linode in the VPC
</Typography>
<TooltipIcon
text={
'A range of non-internet facing IP addresses used in an internal network.'
}
status="help"
text={VPC_AUTO_ASSIGN_IPV4_TOOLTIP}
/>
</Box>
}
Expand Down
21 changes: 11 additions & 10 deletions packages/manager/src/features/OneClickApps/oneClickApps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1137,11 +1137,11 @@ export const oneClickApps: OCA[] = [
{
href:
'https://www.linode.com/docs/products/tools/marketplace/guides/mcffmpegplugins/',
title: 'Deploy MainConcept FFmpeg Plugins through the Linode Marketplace',
title:
'Deploy MainConcept FFmpeg Plugins through the Linode Marketplace',
},
],
summary:
'MainConcept FFmpeg Plugins are advanced video encoding tools.',
summary: 'MainConcept FFmpeg Plugins are advanced video encoding tools.',
website: 'https://www.mainconcept.com/ffmpeg',
},
{
Expand All @@ -1162,8 +1162,7 @@ export const oneClickApps: OCA[] = [
title: 'Deploy MainConcept Live Encoder through the Linode Marketplace',
},
],
summary:
'MainConcept Live Encoder is a real time video encoding engine.',
summary: 'MainConcept Live Encoder is a real time video encoding engine.',
website: 'https://www.mainconcept.com/live-encoder',
},
{
Expand All @@ -1181,7 +1180,8 @@ export const oneClickApps: OCA[] = [
{
href:
'https://www.linode.com/docs/products/tools/marketplace/guides/mcp2avcultratranscoder/',
title: 'Deploy MainConcept P2 AVC Ultra Transcoder through the Linode Marketplace',
title:
'Deploy MainConcept P2 AVC Ultra Transcoder through the Linode Marketplace',
},
],
summary:
Expand All @@ -1203,7 +1203,8 @@ export const oneClickApps: OCA[] = [
{
href:
'https://www.linode.com/docs/products/tools/marketplace/guides/mcp2xavc/',
title: 'Deploy MainConcept XAVC Transcoder through the Linode Marketplace',
title:
'Deploy MainConcept XAVC Transcoder through the Linode Marketplace',
},
],
summary:
Expand All @@ -1225,7 +1226,8 @@ export const oneClickApps: OCA[] = [
{
href:
'https://www.linode.com/docs/products/tools/marketplace/guides/mcp2xdcam/',
title: 'Deploy MainConcept XDCAM Transcoder through the Linode Marketplace',
title:
'Deploy MainConcept XDCAM Transcoder through the Linode Marketplace',
},
],
summary:
Expand Down Expand Up @@ -2250,8 +2252,7 @@ export const oneClickApps: OCA[] = [
website: 'https://docs.splunk.com/Documentation/Splunk',
},
{
alt_description:
'A private by design messaging platform.',
alt_description: 'A private by design messaging platform.',
alt_name: 'Anonymous messaging platform.',
categories: ['Productivity'],
colors: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ import { useFormik } from 'formik';
import * as React from 'react';

import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { Checkbox } from 'src/components/Checkbox';
import { DownloadCSV } from 'src/components/DownloadCSV/DownloadCSV';
import { Drawer } from 'src/components/Drawer';
import { FormControlLabel } from 'src/components/FormControlLabel';
import { FormHelperText } from 'src/components/FormHelperText';
import { Link } from 'src/components/Link';
import { Notice } from 'src/components/Notice/Notice';
import { RemovableSelectionsList } from 'src/components/RemovableSelectionsList/RemovableSelectionsList';
import { TextField } from 'src/components/TextField';
import { TooltipIcon } from 'src/components/TooltipIcon';
import { Typography } from 'src/components/Typography';
import { VPC_AUTO_ASSIGN_IPV4_TOOLTIP } from 'src/features/VPCs/constants';
import { useFormattedDate } from 'src/hooks/useFormattedDate';
import { useUnassignLinode } from 'src/hooks/useUnassignLinode';
import { useAllLinodesQuery } from 'src/queries/linodes/linodes';
Expand Down Expand Up @@ -371,16 +376,25 @@ export const SubnetAssignLinodesDrawer = (
sx={{ marginBottom: '8px' }}
value={values.selectedLinode || null}
/>
<Checkbox
toolTipText={
'A range of non-internet facing IP used in an internal network'
}
checked={autoAssignIPv4}
disabled={userCannotAssignLinodes}
onChange={handleAutoAssignIPv4Change}
sx={{ marginLeft: `2px`, marginTop: `8px` }}
text={'Auto-assign a VPC IPv4 address for this Linode'}
/>
<Box alignItems="center" display="flex" flexDirection="row">
<FormControlLabel
control={
<Checkbox
checked={autoAssignIPv4}
onChange={handleAutoAssignIPv4Change}
/>
}
label={
<Typography>
Auto-assign a VPC IPv4 address for this Linode
</Typography>
}
data-testid="vpc-ipv4-checkbox"
disabled={userCannotAssignLinodes}
sx={{ marginRight: 0 }}
/>
<TooltipIcon status="help" text={VPC_AUTO_ASSIGN_IPV4_TOOLTIP} />
</Box>
{!autoAssignIPv4 && (
<TextField
onChange={(e) => {
Expand Down Expand Up @@ -450,22 +464,24 @@ export const SubnetAssignLinodesDrawer = (
preferredDataLabel="linodeConfigLabel"
selectionData={assignedLinodesAndConfigData}
/>
<DownloadCSV
sx={{
alignItems: 'flex-start',
display: 'flex',
gap: 1,
marginTop: 2,
textAlign: 'left',
}}
buttonType="styledLink"
csvRef={csvRef}
data={assignedLinodesAndConfigData}
filename={`linodes-assigned-${formattedDate}.csv`}
headers={SUBNET_LINODE_CSV_HEADERS}
onClick={downloadCSV}
text={'Download List of Assigned Linodes (.csv)'}
/>
{assignedLinodesAndConfigData.length > 0 && (
<DownloadCSV
sx={{
alignItems: 'flex-start',
display: 'flex',
gap: 1,
marginTop: 2,
textAlign: 'left',
}}
buttonType="styledLink"
csvRef={csvRef}
data={assignedLinodesAndConfigData}
filename={`linodes-assigned-${formattedDate}.csv`}
headers={SUBNET_LINODE_CSV_HEADERS}
onClick={downloadCSV}
text={'Download List of Assigned Linodes (.csv)'}
/>
)}
<StyledButtonBox>
<Button buttonType="outlined" onClick={handleOnClose}>
Done
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const SubnetLinodeRow = (props: Props) => {
if (linodeLoading || !linode) {
return (
<TableRow>
<TableCell sx={{ marginLeft: 6 }}>
<TableCell colSpan={6}>
<CircleProgress mini />
</TableCell>
</TableRow>
Expand All @@ -69,7 +69,7 @@ export const SubnetLinodeRow = (props: Props) => {
if (linodeError) {
return (
<TableRow data-testid="subnet-linode-row-error">
<TableCell colSpan={5} style={{ paddingLeft: 48 }}>
<TableCell colSpan={5} style={{ paddingLeft: 24 }}>
<Box alignItems="center" display="flex">
<ErrorOutline
data-qa-error-icon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,22 +284,24 @@ export const SubnetUnassignLinodesDrawer = React.memo(
onRemove={handleRemoveLinode}
selectionData={selectedLinodes}
/>
<DownloadCSV
sx={{
alignItems: 'flex-start',
display: 'flex',
gap: 1,
marginTop: 2,
textAlign: 'left',
}}
buttonType="styledLink"
csvRef={csvRef}
data={selectedLinodes}
filename={`linodes-unassigned-${formattedDate}.csv`}
headers={SUBNET_LINODE_CSV_HEADERS}
onClick={downloadCSV}
text={'Download List of Unassigned Linodes (.csv)'}
/>
{selectedLinodes.length > 0 && (
<DownloadCSV
sx={{
alignItems: 'flex-start',
display: 'flex',
gap: 1,
marginTop: 2,
textAlign: 'left',
}}
buttonType="styledLink"
csvRef={csvRef}
data={selectedLinodes}
filename={`linodes-unassigned-${formattedDate}.csv`}
headers={SUBNET_LINODE_CSV_HEADERS}
onClick={downloadCSV}
text={'Download List of Unassigned Linodes (.csv)'}
/>
)}
<ActionsPanel
primaryButtonProps={{
'data-testid': 'unassign-submit-button',
Expand Down
23 changes: 17 additions & 6 deletions packages/manager/src/features/VPCs/VPCDetail/VPCDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import { EntityHeader } from 'src/components/EntityHeader/EntityHeader';
import { ErrorState } from 'src/components/ErrorState/ErrorState';
import { LandingHeader } from 'src/components/LandingHeader';
import { VPC_LABEL } from 'src/features/VPCs/constants';
import { useRegionsQuery } from 'src/queries/regions';
import { useVPCQuery } from 'src/queries/vpcs';
import { truncate } from 'src/utilities/truncate';
Expand Down Expand Up @@ -99,7 +100,7 @@ const VPCDetail = () => {
breadcrumbProps={{
crumbOverrides: [
{
label: 'Virtual Private Cloud (VPC)',
label: VPC_LABEL,
position: 1,
},
],
Expand Down Expand Up @@ -132,15 +133,21 @@ const VPCDetail = () => {
</Box>
</EntityHeader>
<StyledPaper>
<StyledSummaryBox display="flex" flex={1} data-qa-vpc-summary>
<StyledSummaryBox data-qa-vpc-summary display="flex" flex={1}>
{summaryData.map((col) => {
return (
<Box key={col[0].label} paddingRight={6}>
<StyledSummaryTextTypography>
<strong>{col[0].label}</strong> {col[0].value}
<span style={{ fontFamily: theme.font.bold }}>
{col[0].label}
</span>{' '}
{col[0].value}
</StyledSummaryTextTypography>
<StyledSummaryTextTypography>
<strong>{col[1].label}</strong> {col[1].value}
<span style={{ fontFamily: theme.font.bold }}>
{col[1].label}
</span>{' '}
{col[1].value}
</StyledSummaryTextTypography>
</Box>
);
Expand All @@ -149,7 +156,9 @@ const VPCDetail = () => {
{vpc.description.length > 0 && (
<StyledDescriptionBox display="flex" flex={1}>
<Typography>
<strong style={{ paddingRight: 8 }}>Description</strong>{' '}
<span style={{ fontFamily: theme.font.bold, paddingRight: 8 }}>
Description
</span>{' '}
</Typography>
<Typography>
{description}{' '}
Expand Down Expand Up @@ -184,7 +193,9 @@ const VPCDetail = () => {
})}
padding={`${theme.spacing(2)} ${theme.spacing()}`}
>
<Typography variant="h2">Subnets ({vpc.subnets.length})</Typography>
<Typography sx={{ fontSize: '1rem' }} variant="h2">
Subnets ({vpc.subnets.length})
</Typography>
</Box>
{numLinodes > 0 && (
<DismissibleBanner
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {
gettingStartedGuides,
youtubeLinkData,
} from 'src/features/Linodes/LinodesLanding/LinodesLandingEmptyStateData';
import { headers, linkAnalyticsEvent } from './VPCEmptyStateData';
import { sendEvent } from 'src/utilities/analytics';

import { headers, linkAnalyticsEvent } from './VPCEmptyStateData';

export const VPCEmptyState = () => {
const { push } = useHistory();

Expand Down
Loading

0 comments on commit 9d323ef

Please sign in to comment.