Skip to content

Commit

Permalink
upcoming: [M3-7609] - Placement Groups Landing Page (#10068)
Browse files Browse the repository at this point in the history
* Initial commit - menu entry, routes and skeletons

* Saving progress: table rows

* Saving progress: Table data

* Tests and cleanup

* moar cleanup

* Compliance col remove, styling and cleanup

* Cleanup and changeset

* Fix unit test

* Update icon and limits

* Feedback

* Moar Feedback
  • Loading branch information
abailly-akamai authored Jan 19, 2024
1 parent 162f471 commit 9582ca1
Show file tree
Hide file tree
Showing 15 changed files with 580 additions and 6 deletions.
3 changes: 2 additions & 1 deletion packages/api-v4/src/placement-groups/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Region } from '../regions/types';

export type AffinityType = 'affinity' | 'anti-affinity';
export type AffinityType = 'affinity' | 'anti_affinity';

export interface PlacementGroup {
id: number;
Expand All @@ -9,6 +9,7 @@ export interface PlacementGroup {
affinity_type: AffinityType;
compliant: boolean;
linode_ids: number[];
limits: number;
}

export type CreatePlacementGroupPayload = Pick<
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Placement Groups Landing Page ([#10068](https://github.com/linode/manager/pull/10068))
9 changes: 9 additions & 0 deletions packages/manager/src/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ const Firewalls = React.lazy(() => import('src/features/Firewalls'));
const Databases = React.lazy(() => import('src/features/Databases'));
const BetaRoutes = React.lazy(() => import('src/features/Betas'));
const VPC = React.lazy(() => import('src/features/VPCs'));
const PlacementGroups = React.lazy(() =>
import('src/features/PlacementGroups').then((module) => ({
default: module.PlacementGroups,
}))
);

export const MainContent = () => {
const { classes, cx } = useStyles();
Expand Down Expand Up @@ -325,6 +330,10 @@ export const MainContent = () => {
<React.Suspense fallback={<SuspenseLoader />}>
<Switch>
<Route component={LinodesRoutes} path="/linodes" />
<Route
component={PlacementGroups}
path="/placement-groups"
/>
<Route component={Volumes} path="/volumes" />
<Redirect path="/volumes*" to="/volumes" />
{flags.aglb && (
Expand Down
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
Expand Up @@ -21,6 +21,11 @@ const useStyles = makeStyles<void, 'linkItem'>()(
left: 70,
position: 'absolute',
},
'&.beta-chip-placement-groups': {
bottom: -2,
left: 52,
position: 'absolute',
},
marginTop: 2,
},
divider: {
Expand Down
11 changes: 11 additions & 0 deletions packages/manager/src/components/PrimaryNav/PrimaryNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Linode from 'src/assets/icons/entityIcons/linode.svg';
import Managed from 'src/assets/icons/entityIcons/managed.svg';
import NodeBalancer from 'src/assets/icons/entityIcons/nodebalancer.svg';
import OCA from 'src/assets/icons/entityIcons/oneclick.svg';
import PlacementGroups from 'src/assets/icons/entityIcons/placement-groups.svg';
import StackScript from 'src/assets/icons/entityIcons/stackscript.svg';
import Volume from 'src/assets/icons/entityIcons/volume.svg';
import VPC from 'src/assets/icons/entityIcons/vpc.svg';
Expand Down Expand Up @@ -54,6 +55,7 @@ type NavEntity =
| 'Marketplace'
| 'NodeBalancers'
| 'Object Storage'
| 'Placement Groups'
| 'StackScripts'
| 'VPC'
| 'Volumes';
Expand Down Expand Up @@ -171,6 +173,14 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
href: '/linodes',
icon: <Linode />,
},
{
betaChipClassName: 'beta-chip-placement-groups',
display: 'Placement Groups',
hide: !flags.vmPlacement,
href: '/placement-groups',
icon: <PlacementGroups />,
isBeta: true,
},
{
display: 'Volumes',
href: '/volumes',
Expand Down Expand Up @@ -288,6 +298,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
allowMarketplacePrefetch,
flags.databaseBeta,
flags.aglb,
flags.vmPlacement,
showVPCs,
]
);
Expand Down
17 changes: 12 additions & 5 deletions packages/manager/src/factories/placementGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,24 @@ import type {
} from '@linode/api-v4';

export const placementGroupFactory = Factory.Sync.makeFactory<PlacementGroup>({
affinity_type: 'anti-affinity',
compliant: true,
affinity_type: Factory.each(() => pickRandom(['affinity', 'anti_affinity'])),
compliant: Factory.each(() => pickRandom([true, false])),
id: Factory.each((id) => id),
label: Factory.each((id) => `pg-${id}`),
linode_ids: [1, 2, 3],
region: pickRandom(['us-east', 'us-southeast', 'ca-central']),
limits: 10,
linode_ids: Factory.each(() => [
pickRandom([1, 2, 3]),
pickRandom([4, 5, 6]),
pickRandom([7, 8, 9]),
]),
region: Factory.each(() =>
pickRandom(['us-east', 'us-southeast', 'ca-central'])
),
});

export const createPlacementGroupPayloadFactory = Factory.Sync.makeFactory<CreatePlacementGroupPayload>(
{
affinity_type: 'anti-affinity',
affinity_type: 'anti_affinity',
label: Factory.each((id) => `mock-pg-${id}`),
region: pickRandom(['us-east', 'us-southeast', 'ca-central']),
}
Expand Down
1 change: 1 addition & 0 deletions packages/manager/src/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export type ProductInformationBannerLocation =
| 'Managed'
| 'NodeBalancers'
| 'Object Storage'
| 'Placement Groups'
| 'StackScripts'
| 'VPC'
| 'Volumes';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as React from 'react';

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

import { PlacementGroupsLanding } from './PlacementGroupsLanding';

const queryMocks = vi.hoisted(() => ({
usePlacementGroupsQuery: vi.fn().mockReturnValue({}),
}));

vi.mock('src/queries/placementGroups', async () => {
const actual = await vi.importActual('src/queries/placementGroups');
return {
...actual,
usePlacementGroupsQuery: queryMocks.usePlacementGroupsQuery,
};
});

describe('PlacementGroupsLanding', () => {
it('renders loading state', () => {
queryMocks.usePlacementGroupsQuery.mockReturnValue({
isLoading: true,
});

const { getByRole } = renderWithTheme(<PlacementGroupsLanding />);

expect(getByRole('progressbar')).toBeInTheDocument();
});

it('renders error state', () => {
queryMocks.usePlacementGroupsQuery.mockReturnValue({
error: [{ reason: 'Not found' }],
});

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

expect(getByText(/not found/i)).toBeInTheDocument();
});

it('renders docs link and create button', () => {
queryMocks.usePlacementGroupsQuery.mockReturnValue({
data: {
data: [],
results: 0,
},
});

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

expect(getByText(/create placement group/i)).toBeInTheDocument();
expect(getByText(/docs/i)).toBeInTheDocument();
});

it('renders placement groups', () => {
queryMocks.usePlacementGroupsQuery.mockReturnValue({
data: {
data: [
placementGroupFactory.build({
label: 'group 1',
}),
placementGroupFactory.build({
label: 'group 2',
}),
],
results: 2,
},
});

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

expect(getByText(/group 1/i)).toBeInTheDocument();
expect(getByText(/group 2/i)).toBeInTheDocument();
});
});
Loading

0 comments on commit 9582ca1

Please sign in to comment.