diff --git a/platform/main/PlatformApp.test.tsx b/platform/main/PlatformApp.test.tsx
index e41e02e532..fa402c601d 100644
--- a/platform/main/PlatformApp.test.tsx
+++ b/platform/main/PlatformApp.test.tsx
@@ -6,6 +6,7 @@ import { MemoryRouter } from 'react-router-dom';
import { SWRConfig } from 'swr';
import { beforeAll, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest';
import * as GatewayUIAuth from './GatewayUIAuth';
+import * as PlatformActiveUserModule from './PlatformActiveUserProvider';
import { PlatformApp } from './PlatformApp';
// Mock fetch globally
@@ -42,6 +43,7 @@ interface ManagedCloudStub {
describe('Platform Subscription and Session Validation Tests', () => {
let useIsManagedCloudStub: MockInstance & ManagedCloudStub;
+ let usePlatformActiveUserStub: MockInstance;
let fetchMock: MockInstance;
beforeAll(async () => {
@@ -73,6 +75,11 @@ describe('Platform Subscription and Session Validation Tests', () => {
return useIsManagedCloudStub;
};
+ // Default to superuser so existing tests see banners
+ usePlatformActiveUserStub = vi
+ .spyOn(PlatformActiveUserModule, 'usePlatformActiveUser')
+ .mockReturnValue({ activePlatformUser: { is_superuser: true } as never });
+
// Default intercepts matching the original Cypress test
fetchMock.mockImplementation((url: string | URL) => {
const urlString = url.toString();
@@ -278,6 +285,145 @@ describe('Platform Subscription and Session Validation Tests', () => {
expect(screen.queryByRole('banner')).not.toBeInTheDocument();
});
});
+
+ it('should not display compliance banners for non-admin users', async () => {
+ // Arrange: Set user as non-superuser
+ usePlatformActiveUserStub.mockReturnValue({
+ activePlatformUser: { is_superuser: false },
+ });
+
+ // Override the config intercept to return non-compliant license
+ fetchMock.mockImplementation((url: string | URL) => {
+ const urlString = url.toString();
+
+ if (
+ urlString.includes('/config/') &&
+ (urlString.includes('/api/controller/v2/') || urlString.includes('/api/v2/'))
+ ) {
+ return Promise.resolve({
+ ok: true,
+ json: () =>
+ Promise.resolve({
+ license_info: {
+ compliant: false,
+ grace_period_remaining: 2 * 24 * 60 * 60,
+ },
+ }),
+ } as Response);
+ }
+
+ if (urlString.includes('/api/gateway/v1/session/')) {
+ return Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve({ expires_in_seconds: 3600 }),
+ } as Response);
+ }
+
+ return Promise.reject(new Error(`Unmocked URL: ${urlString}`));
+ });
+
+ // Act
+ mountPlatformApp();
+
+ // Assert: No compliance banner should be shown to non-admin users
+ await waitFor(() => {
+ expect(
+ screen.queryByText(/Your subscription is out of compliance/)
+ ).not.toBeInTheDocument();
+ });
+ });
+
+ it('should display compliance banners only for admin users', async () => {
+ // Arrange: Set user as superuser
+ usePlatformActiveUserStub.mockReturnValue({
+ activePlatformUser: { is_superuser: true },
+ });
+
+ // Override the config intercept to return non-compliant license
+ fetchMock.mockImplementation((url: string | URL) => {
+ const urlString = url.toString();
+
+ if (
+ urlString.includes('/config/') &&
+ (urlString.includes('/api/controller/v2/') || urlString.includes('/api/v2/'))
+ ) {
+ return Promise.resolve({
+ ok: true,
+ json: () =>
+ Promise.resolve({
+ license_info: {
+ compliant: false,
+ grace_period_remaining: 2 * 24 * 60 * 60,
+ },
+ }),
+ } as Response);
+ }
+
+ if (urlString.includes('/api/gateway/v1/session/')) {
+ return Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve({ expires_in_seconds: 3600 }),
+ } as Response);
+ }
+
+ return Promise.reject(new Error(`Unmocked URL: ${urlString}`));
+ });
+
+ // Act
+ mountPlatformApp();
+
+ // Assert: Compliance banner should be shown to admin users
+ await waitFor(() => {
+ expect(
+ screen.getByText('Your subscription is out of compliance. 2 days grace period remaining.')
+ ).toBeInTheDocument();
+ });
+ });
+
+ it('should not display subscription expiry banners for non-admin users', async () => {
+ // Arrange: Set user as non-superuser
+ usePlatformActiveUserStub.mockReturnValue({
+ activePlatformUser: { is_superuser: false },
+ });
+
+ // Override the config intercept to return expiring subscription
+ fetchMock.mockImplementation((url: string | URL) => {
+ const urlString = url.toString();
+
+ if (
+ urlString.includes('/config/') &&
+ (urlString.includes('/api/controller/v2/') || urlString.includes('/api/v2/'))
+ ) {
+ return Promise.resolve({
+ ok: true,
+ json: () =>
+ Promise.resolve({
+ license_info: {
+ compliant: true,
+ time_remaining: 14 * 24 * 60 * 60,
+ },
+ }),
+ } as Response);
+ }
+
+ if (urlString.includes('/api/gateway/v1/session/')) {
+ return Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve({ expires_in_seconds: 3600 }),
+ } as Response);
+ }
+
+ return Promise.reject(new Error(`Unmocked URL: ${urlString}`));
+ });
+
+ // Act
+ mountPlatformApp();
+
+ // Assert: No expiry banner should be shown to non-admin users
+ await waitFor(() => {
+ expect(screen.queryByText(/Your subscription will expire/)).not.toBeInTheDocument();
+ });
+ });
});
describe('Controller Down Banner', () => {
diff --git a/platform/main/PlatformApp.tsx b/platform/main/PlatformApp.tsx
index e3295701cb..61358c00bc 100644
--- a/platform/main/PlatformApp.tsx
+++ b/platform/main/PlatformApp.tsx
@@ -11,6 +11,7 @@ import { useUIFlag } from '../settings/ui-flags/useUIFlag';
import { gatewayAPI } from '../utils/gateway-api-utils';
import { useIsManagedCloudInstall } from './GatewayUIAuth';
import { PersonaViewSwitcher } from './persona-view/PersonaViewSwitcher';
+import { usePlatformActiveUser } from './PlatformActiveUserProvider';
import { PlatformMasthead } from './PlatformMasthead';
import { usePlatformNavigation } from './usePlatformNavigation';
import { PageTitleProvider } from '@ansible/ansible-ui-framework/PageTitle/PageTitle';
@@ -68,7 +69,10 @@ export function PlatformApp() {
const { awxConfig, serviceDown, serviceDownStatusCode } = useAwxConfigState();
const managedCloudInstall = useIsManagedCloudInstall() ?? false;
+ const { activePlatformUser } = usePlatformActiveUser();
const subscriptionBanner = useMemo(() => {
+ // Hide compliance banners for non-admin users - they cannot act on compliance issues
+ if (!activePlatformUser?.is_superuser) return null;
if (!awxConfig?.license_info || managedCloudInstall) return null;
if (!awxConfig.license_info.compliant) {
if (awxConfig.license_info.grace_period_remaining) {
@@ -109,7 +113,7 @@ export function PlatformApp() {
);
}
return null;
- }, [awxConfig, managedCloudInstall]);
+ }, [activePlatformUser?.is_superuser, awxConfig, managedCloudInstall]);
const controllerDownBanner = useMemo(() => {
if (!serviceDown) return null;