Skip to content

Commit

Permalink
upcoming: [M3-8839] - Designate LKE-E clusters with 'Enterprise' chip (
Browse files Browse the repository at this point in the history
…#11442)

* Create component for rendering cluster chips

* Add test coverage for new component

* Replace current chip with ClusterChips component

* Fix spacing in column specs due to long LKE-E version

* Remove Kube dashboard button for LKE-E clusters

* Fix the fix for specs spacing

* Add changeset

* Fix a conditional before I forget

* Address feedback - clean up flag mocks in test

* Update packages/manager/src/features/Kubernetes/ClusterList/ClusterChips.test.tsx

Co-authored-by: Hana Xu <115299789+hana-akamai@users.noreply.github.com>

* Update packages/manager/src/features/Kubernetes/KubernetesClusterDetail/KubeClusterSpecs.tsx

Co-authored-by: Hana Xu <115299789+hana-akamai@users.noreply.github.com>

---------

Co-authored-by: Hana Xu <115299789+hana-akamai@users.noreply.github.com>
  • Loading branch information
mjac0bs and hana-akamai authored Jan 2, 2025
1 parent 3a93d2a commit de24b40
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Designate LKE-E clusters with 'Enterprise' chip ([#11442](https://github.com/linode/manager/pull/11442))
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { render } from '@testing-library/react';
import React from 'react';

import { kubernetesClusterFactory } from 'src/factories';
import { wrapWithTheme } from 'src/utilities/testHelpers';

import { ClusterChips } from './ClusterChips';

const mockCluster = kubernetesClusterFactory.build({
control_plane: { high_availability: false },
});
const mockHACluster = kubernetesClusterFactory.build({
control_plane: { high_availability: true },
});
const mockEnterpriseCluster = kubernetesClusterFactory.build({
tier: 'enterprise',
});
const mockStandardCluster = kubernetesClusterFactory.build({
tier: 'standard',
});

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

vi.mock('src/queries/account/account', () => {
const actual = vi.importActual('src/queries/account/account');
return {
...actual,
useAccount: queryMocks.useAccount,
};
});

describe('Kubernetes cluster action menu', () => {
it('renders an HA chip if the cluster is high availability', () => {
const { getByText } = render(
wrapWithTheme(<ClusterChips cluster={mockHACluster} />)
);

expect(getByText('HA', { exact: false }));
});

it('does not render an HA chip if the cluster is not high availability', () => {
const { queryByText } = render(
wrapWithTheme(<ClusterChips cluster={mockCluster} />)
);

expect(queryByText('HA', { exact: false })).toBe(null);
});

it('renders both enterprise and HA chips for an enterprise cluster if the feature is enabled', () => {
queryMocks.useAccount.mockReturnValue({
data: {
capabilities: ['Kubernetes Enterprise'],
},
});

const { getByText } = render(
wrapWithTheme(<ClusterChips cluster={mockEnterpriseCluster} />, {
flags: {
lkeEnterprise: {
enabled: true,
ga: false,
la: true,
},
},
})
);

expect(getByText('HA', { exact: false })).toBeVisible();
expect(getByText('ENTERPRISE')).toBeVisible();
});

it('does not render an enterprise chip for an enterprise cluster if the feature is disabled', () => {
queryMocks.useAccount.mockReturnValue({
data: {
capabilities: ['Kubernetes Enterprise'],
},
});

const { getByText, queryByText } = render(
wrapWithTheme(<ClusterChips cluster={mockEnterpriseCluster} />, {
flags: {
lkeEnterprise: {
enabled: false,
ga: false,
la: true,
},
},
})
);

expect(getByText('HA', { exact: false })).toBeVisible();
expect(queryByText('ENTERPRISE')).toBe(null);
});

it('does not render an enterprise chip for a standard cluster', () => {
queryMocks.useAccount.mockReturnValue({
data: {
capabilities: ['Kubernetes Enterprise'],
},
});

const { queryByText } = render(
wrapWithTheme(<ClusterChips cluster={mockStandardCluster} />, {
flags: {
lkeEnterprise: {
enabled: true,
ga: false,
la: true,
},
},
})
);

expect(queryByText('HA')).toBe(null);
expect(queryByText('ENTERPRISE')).toBe(null);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Chip, Stack } from '@linode/ui';
import React from 'react';
import { useHistory } from 'react-router-dom';

import { useIsLkeEnterpriseEnabled } from '../kubeUtils';

import type { KubernetesCluster } from '@linode/api-v4';
import type { SxProps, Theme } from '@mui/material';

interface Props {
cluster: KubernetesCluster;
sx?: SxProps<Theme>;
}

export const ClusterChips = (props: Props) => {
const { cluster, sx } = props;
const { isLkeEnterpriseLAFeatureEnabled } = useIsLkeEnterpriseEnabled();

const { location } = useHistory();

return (
<Stack columnGap={0.5} flexDirection="row" sx={sx}>
{isLkeEnterpriseLAFeatureEnabled && cluster?.tier === 'enterprise' && (
<Chip
data-testid="lke-e-chip"
label="ENTERPRISE"
size="small"
sx={(theme) => ({ borderColor: theme.color.blue })}
variant="outlined"
/>
)}
{cluster.control_plane.high_availability && (
<Chip
label={
location.pathname === '/kubernetes/clusters' ? 'HA' : 'HA CLUSTER'
}
data-testid="ha-chip"
size="small"
sx={(theme) => ({ borderColor: theme.color.green })}
variant="outlined"
/>
)}
</Stack>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
useLkeStandardOrEnterpriseVersions,
} from '../kubeUtils';
import { ClusterActionMenu } from './ClusterActionMenu';
import { ClusterChips } from './ClusterChips';

import type { KubeNodePoolResponse, KubernetesCluster } from '@linode/api-v4';

Expand Down Expand Up @@ -71,7 +72,7 @@ export const KubernetesClusterRow = (props: Props) => {
const region = regions?.find((r) => r.id === cluster.region);

const { versions } = useLkeStandardOrEnterpriseVersions(
cluster.tier ?? 'standard' //TODO LKE: remove fallback once LKE-E is in GA and tier is required
cluster.tier ?? 'standard' // TODO LKE: remove fallback once LKE-E is in GA and tier is required
);

const nextVersion = getNextVersion(cluster.k8s_version, versions ?? []);
Expand Down Expand Up @@ -108,17 +109,7 @@ export const KubernetesClusterRow = (props: Props) => {
</Link>
</div>
</Grid>
{cluster.control_plane.high_availability && (
<Grid>
<Chip
data-testid="ha-chip"
label="HA"
size="small"
sx={(theme) => ({ borderColor: theme.color.green })}
variant="outlined"
/>
</Grid>
)}
<ClusterChips cluster={cluster} sx={{ marginLeft: 1 }} />
</Grid>
</TableCell>
<Hidden mdDown>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CircleProgress, TooltipIcon, Typography } from '@linode/ui';
import { useMediaQuery } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';
Expand Down Expand Up @@ -69,6 +70,10 @@ export const KubeClusterSpecs = React.memo((props: Props) => {
isLoading: isLoadingKubernetesTypes,
} = useKubernetesTypesQuery();

const matchesColGapBreakpointDown = useMediaQuery(
theme.breakpoints.down(theme.breakpoints.values.lg)
);

const lkeHAType = kubernetesHighAvailabilityTypesData?.find(
(type) => type.id === 'lke-ha'
);
Expand Down Expand Up @@ -134,7 +139,13 @@ export const KubeClusterSpecs = React.memo((props: Props) => {
};

return (
<Grid container direction="row" lg={3} xs={12}>
<Grid
columnGap={matchesColGapBreakpointDown ? 2 : 0}
container
direction="row"
lg={3}
xs={12}
>
<Grid lg={6}>{kubeSpecsLeft.map(kubeSpecItem)}</Grid>
<Grid lg={6}>{kubeSpecsRight.map(kubeSpecItem)}</Grid>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, Chip, Stack, StyledActionButton, Typography } from '@linode/ui';
import { Box, Stack, StyledActionButton, Typography } from '@linode/ui';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import { useSnackbar } from 'notistack';
import * as React from 'react';
Expand All @@ -10,7 +10,10 @@ import { EntityDetail } from 'src/components/EntityDetail/EntityDetail';
import { EntityHeader } from 'src/components/EntityHeader/EntityHeader';
import { Hidden } from 'src/components/Hidden';
import { KubeClusterSpecs } from 'src/features/Kubernetes/KubernetesClusterDetail/KubeClusterSpecs';
import { getKubeControlPlaneACL } from 'src/features/Kubernetes/kubeUtils';
import {
getKubeControlPlaneACL,
useIsLkeEnterpriseEnabled,
} from 'src/features/Kubernetes/kubeUtils';
import { useIsResourceRestricted } from 'src/hooks/useIsResourceRestricted';
import { useAccount } from 'src/queries/account/account';
import {
Expand All @@ -20,6 +23,7 @@ import {
} from 'src/queries/kubernetes';
import { getErrorStringOrDefault } from 'src/utilities/errorUtils';

import { ClusterChips } from '../ClusterList/ClusterChips';
import { DeleteKubernetesClusterDialog } from './DeleteKubernetesClusterDialog';
import { KubeConfigDisplay } from './KubeConfigDisplay';
import { KubeConfigDrawer } from './KubeConfigDrawer';
Expand Down Expand Up @@ -70,6 +74,8 @@ export const KubeSummaryPanel = React.memo((props: Props) => {
isLoading: isLoadingKubernetesACL,
} = useKubernetesControlPlaneACLQuery(cluster.id, !!showControlPlaneACL);

const { isLkeEnterpriseLAFeatureEnabled } = useIsLkeEnterpriseEnabled();

const [
resetKubeConfigDialogOpen,
setResetKubeConfigDialogOpen,
Expand Down Expand Up @@ -111,18 +117,19 @@ export const KubeSummaryPanel = React.memo((props: Props) => {
isResettingKubeConfig={isResettingKubeConfig}
setResetKubeConfigDialogOpen={setResetKubeConfigDialogOpen}
/>
{cluster.control_plane.high_availability && (
<Chip
sx={(theme) => ({
borderColor: theme.color.green,
position: 'absolute',
right: theme.spacing(3),
})}
label="HA CLUSTER"
size="small"
variant="outlined"
/>
)}
<ClusterChips
sx={(theme) => ({
position: 'absolute',
right: theme.spacing(3),
[theme.breakpoints.down('sm')]: {
'& .MuiChip-root': {
marginRight: 0,
},
flexDirection: 'column',
},
})}
cluster={cluster}
/>
</Stack>
}
footer={
Expand Down Expand Up @@ -172,13 +179,16 @@ export const KubeSummaryPanel = React.memo((props: Props) => {
/>
</Hidden>
<Hidden smDown>
<StyledActionButton
disabled={Boolean(dashboardError) || !dashboard}
endIcon={<OpenInNewIcon sx={{ height: '14px' }} />}
onClick={() => window.open(dashboard?.url, '_blank')}
>
Kubernetes Dashboard
</StyledActionButton>
{isLkeEnterpriseLAFeatureEnabled &&
cluster.tier === 'enterprise' ? undefined : (
<StyledActionButton
disabled={Boolean(dashboardError) || !dashboard}
endIcon={<OpenInNewIcon sx={{ height: '14px' }} />}
onClick={() => window.open(dashboard?.url, '_blank')}
>
Kubernetes Dashboard
</StyledActionButton>
)}
<StyledActionButton onClick={() => setIsDeleteDialogOpen(true)}>
Delete Cluster
</StyledActionButton>
Expand Down

0 comments on commit de24b40

Please sign in to comment.