diff --git a/packages/api-v4/.changeset/pr-13062-added-1762436128763.md b/packages/api-v4/.changeset/pr-13062-added-1762436128763.md
new file mode 100644
index 00000000000..73d512d75e8
--- /dev/null
+++ b/packages/api-v4/.changeset/pr-13062-added-1762436128763.md
@@ -0,0 +1,5 @@
+---
+"@linode/api-v4": Added
+---
+
+Added `Akamai Cloud Pulse Logs ` to the `AccountCapability` type ([#13062](https://github.com/linode/manager/pull/13062))
diff --git a/packages/api-v4/src/account/types.ts b/packages/api-v4/src/account/types.ts
index 02fb1516200..73d008377cf 100644
--- a/packages/api-v4/src/account/types.ts
+++ b/packages/api-v4/src/account/types.ts
@@ -62,6 +62,7 @@ export type BillingSource = 'akamai' | 'linode';
export const accountCapabilities = [
'Akamai Cloud Load Balancer',
'Akamai Cloud Pulse',
+ 'Akamai Cloud Pulse Logs',
'Block Storage',
'Block Storage Encryption',
'Cloud Firewall',
diff --git a/packages/manager/.changeset/pr-13062-upcoming-features-1762436186094.md b/packages/manager/.changeset/pr-13062-upcoming-features-1762436186094.md
new file mode 100644
index 00000000000..98df19db365
--- /dev/null
+++ b/packages/manager/.changeset/pr-13062-upcoming-features-1762436186094.md
@@ -0,0 +1,5 @@
+---
+"@linode/manager": Upcoming Features
+---
+
+Limit Logs feature based on `Akamai Cloud Pulse Logs` Account Capability ([#13062](https://github.com/linode/manager/pull/13062))
diff --git a/packages/manager/cypress/e2e/core/delivery/create-destination.spec.ts b/packages/manager/cypress/e2e/core/delivery/create-destination.spec.ts
index 8b1583caba5..0418ab542b0 100644
--- a/packages/manager/cypress/e2e/core/delivery/create-destination.spec.ts
+++ b/packages/manager/cypress/e2e/core/delivery/create-destination.spec.ts
@@ -16,7 +16,11 @@ import type { AkamaiObjectStorageDetailsExtended } from '@linode/api-v4';
describe('Create Destination', () => {
beforeEach(() => {
mockAppendFeatureFlags({
- aclpLogs: { enabled: true, beta: true },
+ aclpLogs: {
+ enabled: true,
+ beta: true,
+ bypassAccountCapabilities: true,
+ },
});
});
diff --git a/packages/manager/cypress/e2e/core/delivery/create-stream.spec.ts b/packages/manager/cypress/e2e/core/delivery/create-stream.spec.ts
index 84fa2802a4b..1ca9b4cb643 100644
--- a/packages/manager/cypress/e2e/core/delivery/create-stream.spec.ts
+++ b/packages/manager/cypress/e2e/core/delivery/create-stream.spec.ts
@@ -17,7 +17,11 @@ import { kubernetesClusterFactory } from 'src/factories';
describe('Create Stream', () => {
beforeEach(() => {
mockAppendFeatureFlags({
- aclpLogs: { enabled: true, beta: true },
+ aclpLogs: {
+ enabled: true,
+ beta: true,
+ bypassAccountCapabilities: true,
+ },
});
});
diff --git a/packages/manager/cypress/e2e/core/delivery/destinations-empty-landing-page.spec.ts b/packages/manager/cypress/e2e/core/delivery/destinations-empty-landing-page.spec.ts
index ff8de40f562..e9ad224226b 100644
--- a/packages/manager/cypress/e2e/core/delivery/destinations-empty-landing-page.spec.ts
+++ b/packages/manager/cypress/e2e/core/delivery/destinations-empty-landing-page.spec.ts
@@ -8,6 +8,7 @@ describe('Destinations empty landing page', () => {
aclpLogs: {
enabled: true,
beta: true,
+ bypassAccountCapabilities: true,
},
});
});
diff --git a/packages/manager/cypress/e2e/core/delivery/destinations-non-empty-landing-page.spec.ts b/packages/manager/cypress/e2e/core/delivery/destinations-non-empty-landing-page.spec.ts
index c3bff68803e..ab17f15ce77 100644
--- a/packages/manager/cypress/e2e/core/delivery/destinations-non-empty-landing-page.spec.ts
+++ b/packages/manager/cypress/e2e/core/delivery/destinations-non-empty-landing-page.spec.ts
@@ -105,7 +105,11 @@ const mockDestinations: Destination[] = new Array(3)
describe('destinations landing checks for non-empty state', () => {
beforeEach(() => {
mockAppendFeatureFlags({
- aclpLogs: { enabled: true, beta: true },
+ aclpLogs: {
+ enabled: true,
+ beta: true,
+ bypassAccountCapabilities: true,
+ },
});
// Mock setup to display the Destinations landing page in a non-empty state
diff --git a/packages/manager/cypress/e2e/core/delivery/edit-destination.spec.ts b/packages/manager/cypress/e2e/core/delivery/edit-destination.spec.ts
index fbbd0a99328..5c9f4ba619e 100644
--- a/packages/manager/cypress/e2e/core/delivery/edit-destination.spec.ts
+++ b/packages/manager/cypress/e2e/core/delivery/edit-destination.spec.ts
@@ -21,7 +21,11 @@ import type { AkamaiObjectStorageDetailsExtended } from '@linode/api-v4';
describe('Edit Destination', () => {
beforeEach(() => {
mockAppendFeatureFlags({
- aclpLogs: { enabled: true, beta: true },
+ aclpLogs: {
+ enabled: true,
+ beta: true,
+ bypassAccountCapabilities: true,
+ },
});
cy.visitWithLogin(`/logs/delivery/destinations/${mockDestination.id}/edit`);
mockGetDestination(mockDestination);
diff --git a/packages/manager/cypress/e2e/core/delivery/edit-stream.spec.ts b/packages/manager/cypress/e2e/core/delivery/edit-stream.spec.ts
index 2b6d5bc28c0..9006b1935be 100644
--- a/packages/manager/cypress/e2e/core/delivery/edit-stream.spec.ts
+++ b/packages/manager/cypress/e2e/core/delivery/edit-stream.spec.ts
@@ -24,7 +24,11 @@ import { kubernetesClusterFactory } from 'src/factories';
describe('Edit Stream', () => {
beforeEach(() => {
mockAppendFeatureFlags({
- aclpLogs: { enabled: true, beta: true },
+ aclpLogs: {
+ enabled: true,
+ beta: true,
+ bypassAccountCapabilities: true,
+ },
});
});
diff --git a/packages/manager/cypress/e2e/core/delivery/streams-empty-landing-page.spec.ts b/packages/manager/cypress/e2e/core/delivery/streams-empty-landing-page.spec.ts
index c0e8aadfa2c..3b24062ea15 100644
--- a/packages/manager/cypress/e2e/core/delivery/streams-empty-landing-page.spec.ts
+++ b/packages/manager/cypress/e2e/core/delivery/streams-empty-landing-page.spec.ts
@@ -8,6 +8,7 @@ describe('Streams empty landing page', () => {
aclpLogs: {
enabled: true,
beta: true,
+ bypassAccountCapabilities: true,
},
});
});
diff --git a/packages/manager/cypress/e2e/core/delivery/streams-non-empty-landing-page.spec.ts b/packages/manager/cypress/e2e/core/delivery/streams-non-empty-landing-page.spec.ts
index db20e8c35ff..ad9df1e518b 100644
--- a/packages/manager/cypress/e2e/core/delivery/streams-non-empty-landing-page.spec.ts
+++ b/packages/manager/cypress/e2e/core/delivery/streams-non-empty-landing-page.spec.ts
@@ -130,7 +130,11 @@ const mockStreams: Stream[] = new Array(3)
describe('Streams non-empty landing page', () => {
beforeEach(() => {
mockAppendFeatureFlags({
- aclpLogs: { enabled: true, beta: true },
+ aclpLogs: {
+ enabled: true,
+ beta: true,
+ bypassAccountCapabilities: true,
+ },
});
// Mock setup to display the Streams landing page in a non-empty state
diff --git a/packages/manager/src/components/PrimaryNav/PrimaryNav.test.tsx b/packages/manager/src/components/PrimaryNav/PrimaryNav.test.tsx
index c560e1d0815..916dad9342c 100644
--- a/packages/manager/src/components/PrimaryNav/PrimaryNav.test.tsx
+++ b/packages/manager/src/components/PrimaryNav/PrimaryNav.test.tsx
@@ -333,6 +333,96 @@ describe('PrimaryNav', () => {
expect(betaChip).toBeVisible();
});
+ it('should show Logs menu item if user has capability and aclpLogs flag is enabled', async () => {
+ const account = accountFactory.build({
+ capabilities: ['Akamai Cloud Pulse Logs'],
+ });
+ server.use(http.get('*/account', () => HttpResponse.json(account)));
+
+ const flags = {
+ aclpLogs: {
+ enabled: true,
+ beta: false,
+ bypassAccountCapabilities: false,
+ },
+ };
+
+ const { findByTestId, queryByTestId } = renderWithTheme(
+ ,
+ { flags }
+ );
+
+ const logsNavItem = await findByTestId('menu-item-Logs');
+ expect(logsNavItem).toBeVisible();
+ expect(queryByTestId('betaChip')).toBeNull();
+ });
+
+ it('should not show Logs menu item if aclpLogs flag is not enabled', async () => {
+ const account = accountFactory.build({
+ capabilities: ['Akamai Cloud Pulse Logs'],
+ });
+ server.use(http.get('*/account', () => HttpResponse.json(account)));
+
+ const flags = {
+ aclpLogs: {
+ enabled: false,
+ beta: false,
+ bypassAccountCapabilities: true,
+ },
+ };
+
+ const { queryByTestId } = renderWithTheme(, {
+ flags,
+ });
+
+ expect(queryByTestId('menu-item-Logs')).toBeNull();
+ });
+
+ it('should show Logs menu item if user lacks capability, bypassAccountCapabilities is true and aclpLogs flag is enabled', async () => {
+ const account = accountFactory.build({
+ capabilities: [],
+ });
+ server.use(http.get('*/account', () => HttpResponse.json(account)));
+
+ const flags = {
+ aclpLogs: {
+ enabled: true,
+ beta: true,
+ bypassAccountCapabilities: true,
+ },
+ };
+
+ const { findByTestId } = renderWithTheme(, {
+ flags,
+ });
+
+ const logsNavItem = await findByTestId('menu-item-Logs');
+ expect(logsNavItem).toBeVisible();
+ const betaChip = await findByTestId('betaChip');
+ expect(betaChip).toBeVisible();
+ });
+
+ it('should not show Logs menu item if user lacks capability, bypassAccountCapabilities is false and aclpLogs flag is enabled', async () => {
+ const account = accountFactory.build({
+ capabilities: [],
+ });
+ server.use(http.get('*/account', () => HttpResponse.json(account)));
+
+ const flags = {
+ aclpLogs: {
+ enabled: true,
+ beta: false,
+ bypassAccountCapabilities: false,
+ },
+ };
+
+ const { queryByTestId } = renderWithTheme(, {
+ flags,
+ });
+
+ expect(queryByTestId('menu-item-Logs')).toBeNull();
+ });
+
it('should show Administration links if iamRbacPrimaryNavChanges flag is enabled', async () => {
const flags: Partial = {
iamRbacPrimaryNavChanges: true,
diff --git a/packages/manager/src/components/PrimaryNav/PrimaryNav.tsx b/packages/manager/src/components/PrimaryNav/PrimaryNav.tsx
index 4c14e98bbf3..a3f43866a76 100644
--- a/packages/manager/src/components/PrimaryNav/PrimaryNav.tsx
+++ b/packages/manager/src/components/PrimaryNav/PrimaryNav.tsx
@@ -20,6 +20,7 @@ import {
} from 'src/components/PrimaryNav/constants';
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 { useIsPlacementGroupsEnabled } from 'src/features/PlacementGroups/utils';
import { useFlags } from 'src/hooks/useFlags';
@@ -99,6 +100,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
const isManaged = accountSettings?.managed ?? false;
const { isACLPEnabled } = useIsACLPEnabled();
+ const { isACLPLogsEnabled, isACLPLogsBeta } = useIsACLPLogsEnabled();
const isAlertsEnabled =
isACLPEnabled &&
@@ -241,9 +243,9 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
},
{
display: 'Logs',
- hide: !flags.aclpLogs?.enabled,
+ hide: !isACLPLogsEnabled,
to: '/logs/delivery',
- isBeta: flags.aclpLogs?.beta,
+ isBeta: isACLPLogsBeta,
},
],
name: 'Monitor',
@@ -330,6 +332,8 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
isManaged,
isPlacementGroupsEnabled,
isACLPEnabled,
+ isACLPLogsBeta,
+ isACLPLogsEnabled,
isIAMBeta,
isIAMEnabled,
iamRbacPrimaryNavChanges,
diff --git a/packages/manager/src/featureFlags.ts b/packages/manager/src/featureFlags.ts
index ac48bcb0230..ce42d10a2e6 100644
--- a/packages/manager/src/featureFlags.ts
+++ b/packages/manager/src/featureFlags.ts
@@ -105,6 +105,13 @@ interface AclpFlag {
showWidgetDimensionFilters?: boolean;
}
+interface AclpLogsFlag extends BetaFeatureFlag {
+ /**
+ * This property indicates whether to bypass account capabilities check or not
+ */
+ bypassAccountCapabilities?: boolean;
+}
+
interface LkeEnterpriseFlag extends BaseFeatureFlag {
ga: boolean;
la: boolean;
@@ -169,7 +176,7 @@ export interface Flags {
aclp: AclpFlag;
aclpAlerting: AclpAlerting;
aclpAlertServiceTypeConfig: AclpAlertServiceTypeConfig[];
- aclpLogs: BetaFeatureFlag;
+ aclpLogs: AclpLogsFlag;
aclpReadEndpoint: string;
aclpResourceTypeMap: CloudPulseResourceTypeMapFlag[];
aclpServices: Partial;
diff --git a/packages/manager/src/features/Delivery/deliveryUtils.ts b/packages/manager/src/features/Delivery/deliveryUtils.ts
index 4ad6dddbbf1..4710be9407e 100644
--- a/packages/manager/src/features/Delivery/deliveryUtils.ts
+++ b/packages/manager/src/features/Delivery/deliveryUtils.ts
@@ -7,12 +7,15 @@ import {
type StreamType,
streamType,
} from '@linode/api-v4';
+import { useAccount } from '@linode/queries';
import { omitProps } from '@linode/ui';
+import { isFeatureEnabledV2 } from '@linode/utilities';
import {
destinationTypeOptions,
streamTypeOptions,
} from 'src/features/Delivery/Shared/types';
+import { useFlags } from 'src/hooks/useFlags';
import type {
AutocompleteOption,
@@ -20,6 +23,32 @@ import type {
FormMode,
} from 'src/features/Delivery/Shared/types';
+/**
+ * Hook to determine if the ACLP Logs feature is enabled for the current user.
+
+ * @returns {{ isACLPLogsEnabled: boolean, isACLPLogsBeta: boolean }} An object indicating if the feature is enabled and if it is in beta.
+ */
+export const useIsACLPLogsEnabled = (): {
+ isACLPLogsBeta: boolean;
+ isACLPLogsEnabled: boolean;
+} => {
+ const { data: account } = useAccount();
+ const flags = useFlags();
+
+ const isACLPLogsEnabled =
+ (flags.aclpLogs?.enabled && flags.aclpLogs?.bypassAccountCapabilities) ||
+ isFeatureEnabledV2(
+ 'Akamai Cloud Pulse Logs',
+ !!flags.aclpLogs?.enabled,
+ account?.capabilities ?? []
+ );
+
+ return {
+ isACLPLogsBeta: !!flags.aclpLogs?.beta,
+ isACLPLogsEnabled,
+ };
+};
+
export const getDestinationTypeOption = (
destinationTypeValue: string
): AutocompleteOption | undefined =>
diff --git a/packages/manager/src/routes/delivery/DeliveryRoute.tsx b/packages/manager/src/routes/delivery/DeliveryRoute.tsx
index b5b3166f892..2a79b5ffee1 100644
--- a/packages/manager/src/routes/delivery/DeliveryRoute.tsx
+++ b/packages/manager/src/routes/delivery/DeliveryRoute.tsx
@@ -3,14 +3,14 @@ import { Outlet } from '@tanstack/react-router';
import React from 'react';
import { SuspenseLoader } from 'src/components/SuspenseLoader';
-import { useFlags } from 'src/hooks/useFlags';
+import { useIsACLPLogsEnabled } from 'src/features/Delivery/deliveryUtils';
export const DeliveryRoute = () => {
- const flags = useFlags();
- const { aclpLogs } = flags;
+ const { isACLPLogsEnabled } = useIsACLPLogsEnabled();
+
return (
}>
- {aclpLogs?.enabled ? : }
+ {isACLPLogsEnabled ? : }
);
};