Skip to content

Commit 0314dc5

Browse files
authored
feat: [M3-8837] - Add LKE-E feature flag (linode#11259)
* Add lkeEnterprise flag, hook, and account beta query * Add WIP tests for hook * Refactor added tests, trying to address failures * Different attempt for debugging purposes; will likely revert this * Fix the APL test error and some linter warnings * Add changesets * Address feedback: add MSW preset for LKE-E account capability
1 parent efc7b22 commit 0314dc5

File tree

13 files changed

+234
-12
lines changed

13 files changed

+234
-12
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/api-v4": Upcoming Features
3+
---
4+
5+
Add v4beta/account endpoint and update Capabilities for LKE-E ([#11259](https://github.com/linode/manager/pull/11259))

packages/api-v4/src/account/account.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ export const getAccountInfo = () => {
3535
return Request<Account>(setURL(`${API_ROOT}/account`), setMethod('GET'));
3636
};
3737

38+
/**
39+
* getAccountInfoBeta
40+
*
41+
* Return beta endpoint account information,
42+
* including contact and billing info.
43+
*
44+
* @TODO LKE-E - M3-8838: Clean up after released to GA, if not otherwise in use
45+
*/
46+
export const getAccountInfoBeta = () => {
47+
return Request<Account>(setURL(`${BETA_API_ROOT}/account`), setMethod('GET'));
48+
};
49+
3850
/**
3951
* getNetworkUtilization
4052
*

packages/api-v4/src/account/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export type AccountCapability =
6868
| 'CloudPulse'
6969
| 'Disk Encryption'
7070
| 'Kubernetes'
71+
| 'Kubernetes Enterprise'
7172
| 'Linodes'
7273
| 'LKE HA Control Planes'
7374
| 'LKE Network Access Control List (IP ACL)'
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+
Add feature flag and hook for LKE-E enablement ([#11259](https://github.com/linode/manager/pull/11259))

packages/manager/src/dev-tools/FeatureFlagTool.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const options: { flag: keyof Flags; label: string }[] = [
2727
{ flag: 'imageServiceGen2', label: 'Image Service Gen2' },
2828
{ flag: 'imageServiceGen2Ga', label: 'Image Service Gen2 GA' },
2929
{ flag: 'linodeDiskEncryption', label: 'Linode Disk Encryption (LDE)' },
30+
{ flag: 'lkeEnterprise', label: 'LKE-Enterprise' },
3031
{ flag: 'objMultiCluster', label: 'OBJ Multi-Cluster' },
3132
{ flag: 'objectStorageGen2', label: 'OBJ Gen2' },
3233
{ flag: 'selfServeBetas', label: 'Self Serve Betas' },

packages/manager/src/featureFlags.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ interface AclpFlag {
6464
enabled: boolean;
6565
}
6666

67+
interface LkeEnterpriseFlag extends BaseFeatureFlag {
68+
ga: boolean;
69+
la: boolean;
70+
}
71+
6772
export interface CloudPulseResourceTypeMapFlag {
6873
dimensionKey: string;
6974
maxResourceSelections?: number;
@@ -109,6 +114,7 @@ export interface Flags {
109114
imageServiceGen2Ga: boolean;
110115
ipv6Sharing: boolean;
111116
linodeDiskEncryption: boolean;
117+
lkeEnterprise: LkeEnterpriseFlag;
112118
mainContentBanner: MainContentBanner;
113119
marketplaceAppOverrides: MarketplaceAppOverride[];
114120
metadata: boolean;

packages/manager/src/features/Kubernetes/kubeUtils.test.ts

Lines changed: 111 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,54 @@
1-
import { renderHook, waitFor } from '@testing-library/react';
1+
import { renderHook } from '@testing-library/react';
22

33
import {
44
accountBetaFactory,
55
kubeLinodeFactory,
66
linodeTypeFactory,
77
nodePoolFactory,
88
} from 'src/factories';
9-
import { HttpResponse, http, server } from 'src/mocks/testServer';
109
import { extendType } from 'src/utilities/extendType';
11-
import { wrapWithTheme } from 'src/utilities/testHelpers';
1210

1311
import {
1412
getLatestVersion,
1513
getTotalClusterMemoryCPUAndStorage,
1614
useAPLAvailability,
15+
useIsLkeEnterpriseEnabled,
1716
} from './kubeUtils';
1817

18+
const queryMocks = vi.hoisted(() => ({
19+
useAccountBeta: vi.fn().mockReturnValue({}),
20+
useAccountBetaQuery: vi.fn().mockReturnValue({}),
21+
useFlags: vi.fn().mockReturnValue({}),
22+
}));
23+
24+
vi.mock('src/queries/account/account', () => {
25+
const actual = vi.importActual('src/queries/account/account');
26+
return {
27+
...actual,
28+
useAccountBeta: queryMocks.useAccountBeta,
29+
};
30+
});
31+
32+
vi.mock('src/queries/account/betas', () => {
33+
const actual = vi.importActual('src/queries/account/betas');
34+
return {
35+
...actual,
36+
useAccountBetaQuery: queryMocks.useAccountBetaQuery,
37+
};
38+
});
39+
40+
vi.mock('src/hooks/useFlags', () => {
41+
const actual = vi.importActual('src/hooks/useFlags');
42+
return {
43+
...actual,
44+
useFlags: queryMocks.useFlags,
45+
};
46+
});
47+
48+
afterEach(() => {
49+
vi.clearAllMocks();
50+
});
51+
1952
describe('helper functions', () => {
2053
const badPool = nodePoolFactory.build({
2154
type: 'not-a-real-type',
@@ -73,25 +106,26 @@ describe('helper functions', () => {
73106
});
74107
});
75108
});
109+
76110
describe('APL availability', () => {
77111
it('should return true if the apl flag is true and beta is active', async () => {
78112
const accountBeta = accountBetaFactory.build({
79113
enrolled: '2023-01-15T00:00:00Z',
80114
id: 'apl',
81115
});
82-
server.use(
83-
http.get('*/account/betas/apl', () => {
84-
return HttpResponse.json(accountBeta);
85-
})
86-
);
87-
const { result } = renderHook(() => useAPLAvailability(), {
88-
wrapper: (ui) => wrapWithTheme(ui, { flags: { apl: true } }),
116+
117+
queryMocks.useAccountBetaQuery.mockReturnValue({
118+
data: accountBeta,
89119
});
90-
await waitFor(() => {
91-
expect(result.current.showAPL).toBe(true);
120+
queryMocks.useFlags.mockReturnValue({
121+
apl: true,
92122
});
123+
124+
const { result } = renderHook(() => useAPLAvailability());
125+
expect(result.current.showAPL).toBe(true);
93126
});
94127
});
128+
95129
describe('getLatestVersion', () => {
96130
it('should return the correct latest version from a list of versions', () => {
97131
const versions = [
@@ -128,3 +162,68 @@ describe('helper functions', () => {
128162
});
129163
});
130164
});
165+
166+
describe('useIsLkeEnterpriseEnabled', () => {
167+
it('returns false if the account does not have the capability', () => {
168+
queryMocks.useAccountBeta.mockReturnValue({
169+
data: {
170+
capabilities: [],
171+
},
172+
});
173+
queryMocks.useFlags.mockReturnValue({
174+
lkeEnterprise: {
175+
enabled: true,
176+
ga: true,
177+
la: true,
178+
},
179+
});
180+
181+
const { result } = renderHook(() => useIsLkeEnterpriseEnabled());
182+
expect(result.current).toStrictEqual({
183+
isLkeEnterpriseGAEnabled: false,
184+
isLkeEnterpriseLAEnabled: false,
185+
});
186+
});
187+
188+
it('returns true for LA if the account has the capability + enabled LA feature flag values', () => {
189+
queryMocks.useAccountBeta.mockReturnValue({
190+
data: {
191+
capabilities: ['Kubernetes Enterprise'],
192+
},
193+
});
194+
queryMocks.useFlags.mockReturnValue({
195+
lkeEnterprise: {
196+
enabled: true,
197+
ga: false,
198+
la: true,
199+
},
200+
});
201+
202+
const { result } = renderHook(() => useIsLkeEnterpriseEnabled());
203+
expect(result.current).toStrictEqual({
204+
isLkeEnterpriseGAEnabled: false,
205+
isLkeEnterpriseLAEnabled: true,
206+
});
207+
});
208+
209+
it('returns true for GA if the account has the capability + enabled GA feature flag values', () => {
210+
queryMocks.useAccountBeta.mockReturnValue({
211+
data: {
212+
capabilities: ['Kubernetes Enterprise'],
213+
},
214+
});
215+
queryMocks.useFlags.mockReturnValue({
216+
lkeEnterprise: {
217+
enabled: true,
218+
ga: true,
219+
la: true,
220+
},
221+
});
222+
223+
const { result } = renderHook(() => useIsLkeEnterpriseEnabled());
224+
expect(result.current).toStrictEqual({
225+
isLkeEnterpriseGAEnabled: true,
226+
isLkeEnterpriseLAEnabled: true,
227+
});
228+
});
229+
});

packages/manager/src/features/Kubernetes/kubeUtils.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useFlags } from 'src/hooks/useFlags';
2+
import { useAccountBeta } from 'src/queries/account/account';
23
import { useAccountBetaQuery } from 'src/queries/account/betas';
34
import { getBetaStatus } from 'src/utilities/betaUtils';
45
import { sortByVersion } from 'src/utilities/sort-by';
@@ -11,6 +12,7 @@ import type {
1112
} from '@linode/api-v4/lib/kubernetes';
1213
import type { Region } from '@linode/api-v4/lib/regions';
1314
import type { ExtendedType } from 'src/utilities/extendType';
15+
import { isFeatureEnabledV2 } from 'src/utilities/accountCapabilities';
1416
export const nodeWarning = `We recommend a minimum of 3 nodes in each Node Pool to avoid downtime during upgrades and maintenance.`;
1517
export const nodesDeletionWarning = `All nodes will be deleted and new nodes will be created to replace them.`;
1618
export const localStorageWarning = `Any local storage (such as \u{2019}hostPath\u{2019} volumes) will be erased.`;
@@ -184,3 +186,34 @@ export const getLatestVersion = (
184186

185187
return { label: `${latestVersion.value}`, value: `${latestVersion.value}` };
186188
};
189+
190+
/**
191+
* Hook to determine if the LKE-Enterprise feature should be visible to the user.
192+
* Based on the user's account capability and the feature flag.
193+
*
194+
* @returns {boolean, boolean} - Whether the LKE-Enterprise feature is enabled for the current user in LA and GA, respectively.
195+
*/
196+
export const useIsLkeEnterpriseEnabled = () => {
197+
const flags = useFlags();
198+
const { data: account } = useAccountBeta();
199+
200+
const isLkeEnterpriseLA = Boolean(
201+
flags?.lkeEnterprise?.enabled && flags.lkeEnterprise.la
202+
);
203+
const isLkeEnterpriseGA = Boolean(
204+
flags.lkeEnterprise?.enabled && flags.lkeEnterprise.ga
205+
);
206+
207+
const isLkeEnterpriseLAEnabled = isFeatureEnabledV2(
208+
'Kubernetes Enterprise',
209+
isLkeEnterpriseLA,
210+
account?.capabilities ?? []
211+
);
212+
const isLkeEnterpriseGAEnabled = isFeatureEnabledV2(
213+
'Kubernetes Enterprise',
214+
isLkeEnterpriseGA,
215+
account?.capabilities ?? []
216+
);
217+
218+
return { isLkeEnterpriseLAEnabled, isLkeEnterpriseGAEnabled };
219+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// New file at `src/mocks/presets/extra/account/lkeEnterpriseEnabled.ts` or similar
2+
3+
import { http } from 'msw';
4+
5+
import { accountFactory } from 'src/factories';
6+
import { makeResponse } from 'src/mocks/utilities/response';
7+
8+
import type { MockPresetExtra } from 'src/mocks/types';
9+
10+
const mockLkeEnabledCapability = () => {
11+
return [
12+
http.get('*/v4*/account', async ({ request }) => {
13+
return makeResponse(
14+
accountFactory.build({
15+
capabilities: [
16+
// Other account capabilities might be necessary here, too...
17+
// TODO Make a `defaultAccountCapabilities` factory.
18+
'Kubernetes',
19+
'Kubernetes Enterprise',
20+
],
21+
})
22+
);
23+
}),
24+
];
25+
};
26+
27+
export const lkeEnterpriseEnabledPreset: MockPresetExtra = {
28+
desc: 'Mock account with LKE Enterprise capability',
29+
group: { id: 'Account', type: 'select' },
30+
handlers: [mockLkeEnabledCapability],
31+
id: 'account:lke-enterprise-enabled',
32+
label: 'LKE Enterprise Enabled',
33+
};

packages/manager/src/mocks/presets/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { baselineCrudPreset } from './baseline/crud';
66
import { baselineLegacyPreset } from './baseline/legacy';
77
import { baselineNoMocksPreset } from './baseline/noMocks';
88
import { childAccountPreset } from './extra/account/childAccount';
9+
import { lkeEnterpriseEnabledPreset } from './extra/account/lkeEnterpriseEnabled';
910
import { managedDisabledPreset } from './extra/account/managedDisabled';
1011
import { managedEnabledPreset } from './extra/account/managedEnabled';
1112
import { parentAccountPreset } from './extra/account/parentAccount';
@@ -43,6 +44,7 @@ export const extraMockPresets: MockPresetExtra[] = [
4344
childAccountPreset,
4445
linodeLimitsPreset,
4546
lkeLimitsPreset,
47+
lkeEnterpriseEnabledPreset,
4648
managedEnabledPreset,
4749
managedDisabledPreset,
4850
coreAndDistributedRegionsPreset,

0 commit comments

Comments
 (0)