Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/api-v4/src/account/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const accountCapabilities = [
'Managed Databases',
'Managed Databases Beta',
'NETINT Quadra T1U',
'Network LoadBalancer',
'NodeBalancers',
'Object Storage Access Key Regions',
'Object Storage Endpoint Types',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Implement feature flag and routing for NLB ([#13068](https://github.com/linode/manager/pull/13068))
26 changes: 26 additions & 0 deletions packages/manager/src/components/PrimaryNav/PrimaryNav.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -561,4 +561,30 @@ describe('PrimaryNav', () => {
).toBeNull();
});
});

it('should show Network Load Balancers menu item if the user has the account capability and the flag is enabled', async () => {
const account = accountFactory.build({
capabilities: ['Network LoadBalancer'],
});

queryMocks.useAccount.mockReturnValue({
data: account,
isLoading: false,
error: null,
});

const flags: Partial<Flags> = {
networkLoadBalancer: true,
};

const { findByTestId } = renderWithTheme(<PrimaryNav {...props} />, {
flags,
});

const databaseNavItem = await findByTestId(
'menu-item-Network Load Balancer'
);

expect(databaseNavItem).toBeVisible();
});
});
9 changes: 9 additions & 0 deletions packages/manager/src/components/PrimaryNav/PrimaryNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useIsACLPEnabled } from 'src/features/CloudPulse/Utils/utils';
import { useIsDatabasesEnabled } from 'src/features/Databases/utilities';
import { useIsACLPLogsEnabled } from 'src/features/Delivery/deliveryUtils';
import { useIsIAMEnabled } from 'src/features/IAM/hooks/useIsIAMEnabled';
import { useIsNetworkLoadBalancerEnabled } from 'src/features/NetworkLoadBalancers/utils';
import { useIsPlacementGroupsEnabled } from 'src/features/PlacementGroups/utils';
import { useFlags } from 'src/hooks/useFlags';

Expand Down Expand Up @@ -56,6 +57,7 @@ export type NavEntity =
| 'Marketplace'
| 'Metrics'
| 'Monitor'
| 'Network Load Balancer'
| 'NodeBalancers'
| 'Object Storage'
| 'Placement Groups'
Expand Down Expand Up @@ -115,6 +117,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
const { isDatabasesEnabled, isDatabasesV2Beta } = useIsDatabasesEnabled();

const { isIAMBeta, isIAMEnabled } = useIsIAMEnabled();
const { isNetworkLoadBalancerEnabled } = useIsNetworkLoadBalancerEnabled();

const {
data: preferences,
Expand Down Expand Up @@ -202,6 +205,11 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
display: 'Firewalls',
to: '/firewalls',
},
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a corresponding test case for this functionality?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added in edd0c48

display: 'Network Load Balancer',
hide: !isNetworkLoadBalancerEnabled,
to: '/netloadbalancers',
},
{
display: 'NodeBalancers',
to: '/nodebalancers',
Expand Down Expand Up @@ -340,6 +348,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
isIAMBeta,
isIAMEnabled,
iamRbacPrimaryNavChanges,
isNetworkLoadBalancerEnabled,
limitsEvolution,
]
);
Expand Down
1 change: 1 addition & 0 deletions packages/manager/src/dev-tools/FeatureFlagTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const options: { flag: keyof Flags; label: string }[] = [
{ flag: 'linodeDiskEncryption', label: 'Linode Disk Encryption (LDE)' },
{ flag: 'linodeInterfaces', label: 'Linode Interfaces' },
{ flag: 'lkeEnterprise2', label: 'LKE-Enterprise' },
{ flag: 'networkLoadBalancer', label: 'Network Load Balancer' },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have networkLoadBalancers as plural everywhere for consistency?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this (networkLoadBalancer) is alright , since it needs to be aligned with the LaunchDarkly response

{ flag: 'nodebalancerIpv6', label: 'NodeBalancer Dual Stack (IPv6)' },
{ flag: 'nodebalancerVpc', label: 'NodeBalancer-VPC Integration' },
{ flag: 'objMultiCluster', label: 'OBJ Multi-Cluster' },
Expand Down
2 changes: 2 additions & 0 deletions packages/manager/src/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ export interface Flags {
marketplaceAppOverrides: MarketplaceAppOverride[];
metadata: boolean;
mtc: MTC;
networkLoadBalancer: boolean;
nodebalancerIpv6: boolean;
nodebalancerVpc: boolean;
objectStorageGen2: BaseFeatureFlag;
Expand Down Expand Up @@ -341,6 +342,7 @@ export type ProductInformationBannerLocation =
| 'Logs'
| 'Longview'
| 'Managed'
| 'Network LoadBalancers'
| 'NodeBalancers'
| 'Object Storage'
| 'Placement Groups'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Notice } from '@linode/ui';
import * as React from 'react';

import { LandingHeader } from 'src/components/LandingHeader';

export const NetworkLoadBalancersLanding = () => {
return (
<>
<LandingHeader
breadcrumbProps={{
pathname: 'Network Load Balancer',
removeCrumbX: 1,
}}
spacingBottom={16}
title="Network Load Balancer"
/>
<Notice variant="info">Network Load Balancer is coming soon...</Notice>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createLazyRoute } from '@tanstack/react-router';

import { NetworkLoadBalancersLanding } from './NetworkLoadBalancersLanding';

export const networkLoadBalancersLazyRoute = createLazyRoute(
'/netloadbalancers'
)({
component: NetworkLoadBalancersLanding,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { renderHook, waitFor } from '@testing-library/react';

import { accountFactory } from 'src/factories';
import { http, HttpResponse, server } from 'src/mocks/testServer';
import { wrapWithTheme } from 'src/utilities/testHelpers';

import { useIsNetworkLoadBalancerEnabled } from './utils';

describe('useIsNetworkLoadBalancerEnabled', () => {
it('returns true if the feature is enabled', async () => {
const options = { flags: { networkLoadBalancer: true } };
const account = accountFactory.build({
capabilities: ['Network LoadBalancer'],
});

server.use(
http.get('*/v4*/account', () => {
return HttpResponse.json(account);
})
);

const { result } = renderHook(() => useIsNetworkLoadBalancerEnabled(), {
wrapper: (ui) => wrapWithTheme(ui, options),
});

await waitFor(() => {
expect(result.current.isNetworkLoadBalancerEnabled).toBe(true);
});
});

it('returns false if the feature is NOT enabled', async () => {
const options = { flags: { networkLoadBalancer: false } };

const { result } = renderHook(() => useIsNetworkLoadBalancerEnabled(), {
wrapper: (ui) => wrapWithTheme(ui, options),
});

await waitFor(() => {
expect(result.current.isNetworkLoadBalancerEnabled).toBe(false);
});
});
});
27 changes: 27 additions & 0 deletions packages/manager/src/features/NetworkLoadBalancers/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useAccount } from '@linode/queries';
import { isFeatureEnabledV2 } from '@linode/utilities';

import { useFlags } from 'src/hooks/useFlags';

/**
*
* @returns an object that contains boolean property to check whether Network LoadBalancer is enabled or not
*/
export const useIsNetworkLoadBalancerEnabled = (): {
isNetworkLoadBalancerEnabled: boolean;
} => {
const { data: account } = useAccount();
const flags = useFlags();

if (!flags) {
return { isNetworkLoadBalancerEnabled: false };
}

const isNetworkLoadBalancerEnabled = isFeatureEnabledV2(
'Network LoadBalancer',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be 'Network LoadBalancers' since we are referring to the capabilities API

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API returns `Network LoadBalancer'
image

Boolean(flags.networkLoadBalancer),
account?.capabilities ?? []
);

return { isNetworkLoadBalancerEnabled };
};
2 changes: 2 additions & 0 deletions packages/manager/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { longviewRouteTree } from './longview';
import { maintenanceRouteTree } from './maintenance';
import { managedRouteTree } from './managed';
import { cloudPulseMetricsRouteTree } from './metrics';
import { networkLoadBalancersRouteTree } from './networkLoadBalancer';
import { nodeBalancersRouteTree } from './nodeBalancers';
import { objectStorageRouteTree } from './objectStorage';
import { placementGroupsRouteTree } from './placementGroups';
Expand Down Expand Up @@ -79,6 +80,7 @@ export const routeTree = rootRoute.addChildren([
longviewRouteTree,
maintenanceRouteTree,
managedRouteTree,
networkLoadBalancersRouteTree,
nodeBalancersRouteTree,
objectStorageRouteTree,
placementGroupsRouteTree,
Expand Down
22 changes: 22 additions & 0 deletions packages/manager/src/routes/networkLoadBalancer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createRoute } from '@tanstack/react-router';

import { rootRoute } from '../root';
import { NetworkLoadBalancersRoute } from './networkLoadBalancersRoute';

const networkLoadBalancersRoute = createRoute({
component: NetworkLoadBalancersRoute,
getParentRoute: () => rootRoute,
path: 'netloadbalancers',
});

const networkLoadBalancersIndexRoute = createRoute({
getParentRoute: () => networkLoadBalancersRoute,
path: '/',
}).lazy(() =>
import(
'src/features/NetworkLoadBalancers/networkLoadBalancersLazyRoute'
).then((m) => m.networkLoadBalancersLazyRoute)
);

export const networkLoadBalancersRouteTree =
networkLoadBalancersRoute.addChildren([networkLoadBalancersIndexRoute]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NotFound } from '@linode/ui';
import { Outlet } from '@tanstack/react-router';
import React from 'react';

import { ProductInformationBanner } from 'src/components/ProductInformationBanner/ProductInformationBanner';
import { SuspenseLoader } from 'src/components/SuspenseLoader';
import { useIsNetworkLoadBalancerEnabled } from 'src/features/NetworkLoadBalancers/utils';

export const NetworkLoadBalancersRoute = () => {
const { isNetworkLoadBalancerEnabled } = useIsNetworkLoadBalancerEnabled();

if (!isNetworkLoadBalancerEnabled) {
return <NotFound />;
}
return (
<React.Suspense fallback={<SuspenseLoader />}>
<ProductInformationBanner bannerLocation="Network LoadBalancers" />
<Outlet />
</React.Suspense>
);
};