Skip to content

Commit 68f0add

Browse files
upcoming: [M3-7986] - Fix & Improve Placement Groups feature restriction (linode#10372)
* Initial commit: save work * Add coverage * fix test * Added changeset: Fix & Improve Placement Groups feature restriction * Fix e2e
1 parent 391b846 commit 68f0add

File tree

15 files changed

+173
-48
lines changed

15 files changed

+173
-48
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Upcoming Features
3+
---
4+
5+
Fix & Improve Placement Groups feature restriction ([#10372](https://github.com/linode/manager/pull/10372))

packages/manager/cypress/e2e/core/placementGroups/placement-groups-landing-page.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ import {
55
import { makeFeatureFlagData } from 'support/util/feature-flags';
66
import { mockGetPlacementGroups } from 'support/intercepts/vm-placement';
77
import { ui } from 'support/ui';
8+
import { accountFactory } from 'src/factories';
89

910
import type { Flags } from 'src/featureFlags';
11+
import { mockGetAccount } from 'support/intercepts/account';
12+
13+
const mockAccount = accountFactory.build();
1014

1115
describe('VM Placement landing page', () => {
1216
// Mock the VM Placement Groups feature flag to be enabled for each test in this block.
@@ -18,6 +22,7 @@ describe('VM Placement landing page', () => {
1822
}),
1923
});
2024
mockGetFeatureFlagClientstream();
25+
mockGetAccount(mockAccount).as('getAccount');
2126
});
2227

2328
/**

packages/manager/src/GoTo.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { makeStyles } from 'tss-react/mui';
77
import EnhancedSelect, { Item } from 'src/components/EnhancedSelect/Select';
88

99
import { useIsACLBEnabled } from './features/LoadBalancers/utils';
10+
import { useIsPlacementGroupsEnabled } from './features/PlacementGroups/utils';
1011
import { useAccountManagement } from './hooks/useAccountManagement';
1112
import { useGlobalKeyboardListener } from './hooks/useGlobalKeyboardListener';
1213

@@ -60,6 +61,7 @@ export const GoTo = React.memo(() => {
6061
const { _hasAccountAccess, _isManagedAccount } = useAccountManagement();
6162

6263
const { isACLBEnabled } = useIsACLBEnabled();
64+
const { isPlacementGroupsEnabled } = useIsPlacementGroupsEnabled();
6365
const { goToOpen, setGoToOpen } = useGlobalKeyboardListener();
6466

6567
const onClose = () => {
@@ -113,6 +115,11 @@ export const GoTo = React.memo(() => {
113115
display: 'Images',
114116
href: '/images',
115117
},
118+
{
119+
display: 'Placement Groups',
120+
hide: !isPlacementGroupsEnabled,
121+
href: '/placement-groups',
122+
},
116123
{
117124
display: 'Domains',
118125
href: '/domains',
@@ -149,7 +156,12 @@ export const GoTo = React.memo(() => {
149156
href: '/profile/display',
150157
},
151158
],
152-
[_hasAccountAccess, _isManagedAccount, isACLBEnabled]
159+
[
160+
_hasAccountAccess,
161+
_isManagedAccount,
162+
isACLBEnabled,
163+
isPlacementGroupsEnabled,
164+
]
153165
);
154166

155167
const options: Item[] = React.useMemo(

packages/manager/src/MainContent.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import Grid from '@mui/material/Unstable_Grid2';
21
import { Theme } from '@mui/material/styles';
2+
import Grid from '@mui/material/Unstable_Grid2';
33
import { isEmpty } from 'ramda';
44
import * as React from 'react';
55
import { Redirect, Route, Switch } from 'react-router-dom';
@@ -10,8 +10,8 @@ import { Box } from 'src/components/Box';
1010
import { MainContentBanner } from 'src/components/MainContentBanner';
1111
import { MaintenanceScreen } from 'src/components/MaintenanceScreen';
1212
import { NotFound } from 'src/components/NotFound';
13-
import { SIDEBAR_WIDTH } from 'src/components/PrimaryNav/SideMenu';
1413
import { SideMenu } from 'src/components/PrimaryNav/SideMenu';
14+
import { SIDEBAR_WIDTH } from 'src/components/PrimaryNav/SideMenu';
1515
import { SuspenseLoader } from 'src/components/SuspenseLoader';
1616
import { useDialogContext } from 'src/context/useDialogContext';
1717
import { Footer } from 'src/features/Footer';
@@ -33,6 +33,7 @@ import { complianceUpdateContext } from './context/complianceUpdateContext';
3333
import { switchAccountSessionContext } from './context/switchAccountSessionContext';
3434
import { FlagSet } from './featureFlags';
3535
import { useIsACLBEnabled } from './features/LoadBalancers/utils';
36+
import { useIsPlacementGroupsEnabled } from './features/PlacementGroups/utils';
3637
import { useGlobalErrors } from './hooks/useGlobalErrors';
3738

3839
const useStyles = makeStyles()((theme: Theme) => ({
@@ -226,6 +227,7 @@ export const MainContent = () => {
226227
(checkRestrictedUser && !enginesLoading && !enginesError);
227228

228229
const { isACLBEnabled } = useIsACLBEnabled();
230+
const { isPlacementGroupsEnabled } = useIsPlacementGroupsEnabled();
229231

230232
const defaultRoot = _isManagedAccount ? '/managed' : '/linodes';
231233

@@ -337,10 +339,12 @@ export const MainContent = () => {
337339
<React.Suspense fallback={<SuspenseLoader />}>
338340
<Switch>
339341
<Route component={LinodesRoutes} path="/linodes" />
340-
<Route
341-
component={PlacementGroups}
342-
path="/placement-groups"
343-
/>
342+
{isPlacementGroupsEnabled && (
343+
<Route
344+
component={PlacementGroups}
345+
path="/placement-groups"
346+
/>
347+
)}
344348
<Route component={Volumes} path="/volumes" />
345349
<Redirect path="/volumes*" to="/volumes" />
346350
{isACLBEnabled && (

packages/manager/src/components/DetailsPanel/DetailsPanel.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { TagsInput, TagsInputProps } from 'src/components/TagsInput/TagsInput';
77
import { TextField, TextFieldProps } from 'src/components/TextField';
88
import { Typography } from 'src/components/Typography';
99
import { PlacementGroupsDetailPanel } from 'src/features/PlacementGroups/PlacementGroupsDetailPanel';
10-
import { useFlags } from 'src/hooks/useFlags';
10+
import { useIsPlacementGroupsEnabled } from 'src/features/PlacementGroups/utils';
1111

1212
import type { PlacementGroup } from '@linode/api-v4';
1313

@@ -28,9 +28,7 @@ export const DetailsPanel = (props: DetailsPanelProps) => {
2828
tagsInputProps,
2929
} = props;
3030
const theme = useTheme();
31-
const flags = useFlags();
32-
33-
const showPlacementGroups = Boolean(flags.placementGroups?.enabled);
31+
const { isPlacementGroupsEnabled } = useIsPlacementGroupsEnabled();
3432

3533
return (
3634
<Paper
@@ -60,8 +58,7 @@ export const DetailsPanel = (props: DetailsPanelProps) => {
6058
/>
6159

6260
{tagsInputProps && <TagsInput {...tagsInputProps} />}
63-
64-
{showPlacementGroups && (
61+
{isPlacementGroupsEnabled && (
6562
<PlacementGroupsDetailPanel
6663
handlePlacementGroupChange={handlePlacementGroupChange}
6764
selectedRegionId={selectedRegionId}

packages/manager/src/components/PrimaryNav/PrimaryNav.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { BetaChip } from 'src/components/BetaChip/BetaChip';
2626
import { Box } from 'src/components/Box';
2727
import { Divider } from 'src/components/Divider';
2828
import { useIsACLBEnabled } from 'src/features/LoadBalancers/utils';
29+
import { useIsPlacementGroupsEnabled } from 'src/features/PlacementGroups/utils';
2930
import { useAccountManagement } from 'src/hooks/useAccountManagement';
3031
import { useFlags } from 'src/hooks/useFlags';
3132
import { usePrefetch } from 'src/hooks/usePreFetch';
@@ -170,6 +171,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
170171
(checkRestrictedUser && !enginesLoading && !enginesError);
171172

172173
const { isACLBEnabled } = useIsACLBEnabled();
174+
const { isPlacementGroupsEnabled } = useIsPlacementGroupsEnabled();
173175

174176
const prefetchObjectStorage = () => {
175177
if (!enableObjectPrefetch) {
@@ -245,7 +247,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
245247
{
246248
betaChipClassName: 'beta-chip-placement-groups',
247249
display: 'Placement Groups',
248-
hide: !flags.placementGroups?.enabled,
250+
hide: !isPlacementGroupsEnabled,
249251
href: '/placement-groups',
250252
icon: <PlacementGroups />,
251253
isBeta: flags.placementGroups?.beta,
@@ -322,6 +324,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
322324
allowMarketplacePrefetch,
323325
flags.databaseBeta,
324326
isACLBEnabled,
327+
isPlacementGroupsEnabled,
325328
flags.placementGroups,
326329
]
327330
);

packages/manager/src/factories/account.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export const accountFactory = Factory.Sync.makeFactory<Account>({
4646
'LKE HA Control Planes',
4747
'Machine Images',
4848
'Managed Databases',
49+
'Placement Group',
4950
],
5051
city: 'Colorado',
5152
company: Factory.each((i) => `company-${i}`),

packages/manager/src/features/Linodes/LinodeCreatev2/Details/Details.test.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { waitFor } from '@testing-library/react';
22
import React from 'react';
33

4-
import { profileFactory } from 'src/factories';
5-
import { grantsFactory } from 'src/factories/grants';
4+
import { grantsFactory, profileFactory } from 'src/factories';
65
import { HttpResponse, http, server } from 'src/mocks/testServer';
76
import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers';
87

@@ -37,17 +36,19 @@ describe('Linode Create Details', () => {
3736
expect(getByText('Type to choose or create a tag.')).toBeVisible();
3837
});
3938

40-
it('renders an placement group details if the flag is on', () => {
39+
it('renders an placement group details if the flag is on', async () => {
4140
const { getByText } = renderWithThemeAndHookFormContext({
4241
component: <Details />,
4342
options: {
4443
flags: { placementGroups: { beta: true, enabled: true } },
4544
},
4645
});
4746

48-
expect(
49-
getByText('Select a region above to see available Placement Groups.')
50-
).toBeVisible();
47+
await waitFor(() => {
48+
expect(
49+
getByText('Select a region above to see available Placement Groups.')
50+
).toBeVisible();
51+
});
5152
});
5253

5354
it('does not render the placement group select if the flag is off', () => {

packages/manager/src/features/Linodes/LinodeCreatev2/Details/Details.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@ import { Paper } from 'src/components/Paper';
66
import { TagsInput } from 'src/components/TagsInput/TagsInput';
77
import { TextField } from 'src/components/TextField';
88
import { Typography } from 'src/components/Typography';
9-
import { useFlags } from 'src/hooks/useFlags';
9+
import { useIsPlacementGroupsEnabled } from 'src/features/PlacementGroups/utils';
1010
import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck';
1111

1212
import { PlacementGroupPanel } from './PlacementGroupPanel';
1313

1414
export const Details = () => {
1515
const { control } = useFormContext<CreateLinodeRequest>();
16-
const flags = useFlags();
17-
18-
const showPlacementGroups = Boolean(flags.placementGroups?.enabled);
16+
const { isPlacementGroupsEnabled } = useIsPlacementGroupsEnabled();
1917

2018
const isCreateLinodeRestricted = useRestrictedGlobalGrantCheck({
2119
globalGrantType: 'add_linodes',
@@ -51,7 +49,7 @@ export const Details = () => {
5149
control={control}
5250
name="tags"
5351
/>
54-
{showPlacementGroups && <PlacementGroupPanel />}
52+
{isPlacementGroupsEnabled && <PlacementGroupPanel />}
5553
</Paper>
5654
);
5755
};

packages/manager/src/features/Linodes/LinodesCreate/LinodeCreate.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -861,10 +861,7 @@ export class LinodeCreate extends React.PureComponent<
861861
image: this.props.selectedImageID,
862862
label: this.props.label,
863863
placement_group:
864-
this.props.flags.placementGroups?.enabled &&
865-
placement_group_payload.id !== -1
866-
? placement_group_payload
867-
: undefined,
864+
placement_group_payload.id !== -1 ? placement_group_payload : undefined,
868865
private_ip: this.props.privateIPEnabled,
869866
region: this.props.selectedRegionID ?? '',
870867
root_pass: this.props.password,

packages/manager/src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsDetail.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { LandingHeader } from 'src/components/LandingHeader';
99
import { NotFound } from 'src/components/NotFound';
1010
import { Notice } from 'src/components/Notice/Notice';
1111
import { getRestrictedResourceText } from 'src/features/Account/utils';
12-
import { useFlags } from 'src/hooks/useFlags';
1312
import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck';
1413
import { useAllLinodesQuery } from 'src/queries/linodes/linodes';
1514
import {
@@ -23,18 +22,14 @@ import { PlacementGroupsLinodes } from './PlacementGroupsLinodes/PlacementGroups
2322
import { PlacementGroupsSummary } from './PlacementGroupsSummary/PlacementGroupsSummary';
2423

2524
export const PlacementGroupsDetail = () => {
26-
const flags = useFlags();
2725
const { id } = useParams<{ id: string }>();
2826
const placementGroupId = +id;
2927

3028
const {
3129
data: placementGroup,
3230
error: placementGroupError,
3331
isLoading,
34-
} = usePlacementGroupQuery(
35-
placementGroupId,
36-
Boolean(flags.placementGroups?.enabled)
37-
);
32+
} = usePlacementGroupQuery(placementGroupId);
3833
const { data: linodes, isFetching: isFetchingLinodes } = useAllLinodesQuery(
3934
{},
4035
{

packages/manager/src/features/PlacementGroups/PlacementGroupsDetailPanel.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { TextTooltip } from 'src/components/TextTooltip';
1010
import { Typography } from 'src/components/Typography';
1111
import { PlacementGroupsCreateDrawer } from 'src/features/PlacementGroups/PlacementGroupsCreateDrawer';
1212
import { hasRegionReachedPlacementGroupCapacity } from 'src/features/PlacementGroups/utils';
13-
import { useFlags } from 'src/hooks/useFlags';
1413
import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck';
1514
import { useAllPlacementGroupsQuery } from 'src/queries/placementGroups';
1615
import { useRegionsQuery } from 'src/queries/regions/regions';
@@ -26,7 +25,6 @@ interface Props {
2625
}
2726

2827
export const PlacementGroupsDetailPanel = (props: Props) => {
29-
const flags = useFlags();
3028
const theme = useTheme();
3129
const { handlePlacementGroupChange, selectedRegionId } = props;
3230
const { data: allPlacementGroups } = useAllPlacementGroupsQuery();
@@ -151,16 +149,14 @@ export const PlacementGroupsDetailPanel = (props: Props) => {
151149
</Button>
152150
)}
153151
</Box>
154-
{flags.placementGroups?.enabled && (
155-
<PlacementGroupsCreateDrawer
156-
allPlacementGroups={allPlacementGroups || []}
157-
disabledPlacementGroupCreateButton={isLinodeReadOnly}
158-
onClose={() => setIsCreatePlacementGroupDrawerOpen(false)}
159-
onPlacementGroupCreate={handlePlacementGroupCreated}
160-
open={isCreatePlacementGroupDrawerOpen}
161-
selectedRegionId={selectedRegionId}
162-
/>
163-
)}
152+
<PlacementGroupsCreateDrawer
153+
allPlacementGroups={allPlacementGroups || []}
154+
disabledPlacementGroupCreateButton={isLinodeReadOnly}
155+
onClose={() => setIsCreatePlacementGroupDrawerOpen(false)}
156+
onPlacementGroupCreate={handlePlacementGroupCreated}
157+
open={isCreatePlacementGroupDrawerOpen}
158+
selectedRegionId={selectedRegionId}
159+
/>
164160
</>
165161
);
166162
};

0 commit comments

Comments
 (0)