From 8a060f262bca1f704f9d6c69365d92c6591a0e36 Mon Sep 17 00:00:00 2001 From: TejasLamba2006 Date: Mon, 9 Nov 2026 22:04:37 +0530 Subject: [PATCH 1/2] feat(usage): restrict over-limit banner to admins --- frontend/web/__tests__/routePaths.test.ts | 23 ++++++++++++++++++++++- frontend/web/components/App.js | 11 +++++++++-- frontend/web/routePaths.ts | 5 +++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/frontend/web/__tests__/routePaths.test.ts b/frontend/web/__tests__/routePaths.test.ts index a1abf6151b82..2418a206c173 100644 --- a/frontend/web/__tests__/routePaths.test.ts +++ b/frontend/web/__tests__/routePaths.test.ts @@ -1,4 +1,8 @@ -import { isAllowedWhileBlocked, isOrganisationUsage } from 'web/routePaths' +import { + isAllowedWhileBlocked, + isOrganisationUsage, + shouldShowOrganisationLimit, +} from 'web/routePaths' // App renders wherever this is false, so a wrong answer either // locks a blocked organisation out, or lets it back in. @@ -27,6 +31,23 @@ describe('isAllowedWhileBlocked', () => { }) }) +describe('shouldShowOrganisationLimit', () => { + it.each` + isAdmin | hasOwnQuotaBanner | shouldShow + ${true} | ${false} | ${true} + ${true} | ${true} | ${false} + ${false} | ${false} | ${false} + ${false} | ${true} | ${false} + `( + 'admin=$isAdmin, own quota banner=$hasOwnQuotaBanner: $shouldShow', + ({ hasOwnQuotaBanner, isAdmin, shouldShow }) => { + expect(shouldShowOrganisationLimit(isAdmin, hasOwnQuotaBanner)).toBe( + shouldShow, + ) + }, + ) +}) + // App hides its app-wide quota banner here, where the page has its own. describe('isOrganisationUsage', () => { it.each` diff --git a/frontend/web/components/App.js b/frontend/web/components/App.js index bda5c996deca..4e6843f6f5da 100644 --- a/frontend/web/components/App.js +++ b/frontend/web/components/App.js @@ -30,7 +30,11 @@ import Announcement from './Announcement' import { getBuildVersion } from 'common/services/useBuildVersion' import AccountProvider from 'common/providers/AccountProvider' import Nav from './navigation/Nav' -import { isAllowedWhileBlocked, isOrganisationUsage } from 'web/routePaths' +import { + isAllowedWhileBlocked, + isOrganisationUsage, + shouldShowOrganisationLimit, +} from 'web/routePaths' import 'project/darkMode' const App = class extends Component { @@ -366,7 +370,10 @@ const App = class extends Component { /> {user && ( <> - {!hasOwnQuotaBanner && ( + {shouldShowOrganisationLimit( + AccountStore.isAdmin(), + hasOwnQuotaBanner, + ) && ( export const isOrganisationUsage = (pathname: string): boolean => matches(pathname, ORGANISATION_USAGE) + +export const shouldShowOrganisationLimit = ( + isAdmin: boolean, + hasOwnQuotaBanner: boolean, +): boolean => isAdmin && !hasOwnQuotaBanner From 2d77a1d0a5c47b9efbceab79acf0018fa1eb5a25 Mon Sep 17 00:00:00 2001 From: TejasLamba2006 Date: Mon, 9 Nov 2026 22:21:29 +0530 Subject: [PATCH 2/2] test(usage): cover admin banner visibility wiring --- frontend/web/__tests__/routePaths.test.ts | 30 +++++++++++++++++------ frontend/web/components/App.js | 11 ++------- frontend/web/routePaths.ts | 9 ++++--- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/frontend/web/__tests__/routePaths.test.ts b/frontend/web/__tests__/routePaths.test.ts index 2418a206c173..6f7f31696974 100644 --- a/frontend/web/__tests__/routePaths.test.ts +++ b/frontend/web/__tests__/routePaths.test.ts @@ -1,9 +1,21 @@ +import AccountStore from 'common/stores/account-store' import { isAllowedWhileBlocked, isOrganisationUsage, shouldShowOrganisationLimit, } from 'web/routePaths' +jest.mock('common/stores/account-store', () => ({ + __esModule: true, + default: { isAdmin: jest.fn() }, +})) + +const mockIsAdmin = AccountStore.isAdmin as jest.Mock + +beforeEach(() => { + mockIsAdmin.mockReset() +}) + // App renders wherever this is false, so a wrong answer either // locks a blocked organisation out, or lets it back in. describe('isAllowedWhileBlocked', () => { @@ -33,15 +45,17 @@ describe('isAllowedWhileBlocked', () => { describe('shouldShowOrganisationLimit', () => { it.each` - isAdmin | hasOwnQuotaBanner | shouldShow - ${true} | ${false} | ${true} - ${true} | ${true} | ${false} - ${false} | ${false} | ${false} - ${false} | ${true} | ${false} + isAdmin | pathname | usageDashboardEnabled | shouldShow + ${true} | ${'/organisation/7528/usage'} | ${false} | ${true} + ${true} | ${'/organisation/7528/usage'} | ${true} | ${false} + ${true} | ${'/organisation/7528/projects'} | ${true} | ${true} + ${false} | ${'/organisation/7528/usage'} | ${false} | ${false} + ${false} | ${'/organisation/7528/projects'} | ${true} | ${false} `( - 'admin=$isAdmin, own quota banner=$hasOwnQuotaBanner: $shouldShow', - ({ hasOwnQuotaBanner, isAdmin, shouldShow }) => { - expect(shouldShowOrganisationLimit(isAdmin, hasOwnQuotaBanner)).toBe( + 'admin=$isAdmin, pathname=$pathname, usage dashboard=$usageDashboardEnabled: $shouldShow', + ({ isAdmin, pathname, shouldShow, usageDashboardEnabled }) => { + mockIsAdmin.mockReturnValue(isAdmin) + expect(shouldShowOrganisationLimit(pathname, usageDashboardEnabled)).toBe( shouldShow, ) }, diff --git a/frontend/web/components/App.js b/frontend/web/components/App.js index 4e6843f6f5da..a8424a9cdc5e 100644 --- a/frontend/web/components/App.js +++ b/frontend/web/components/App.js @@ -32,7 +32,6 @@ import AccountProvider from 'common/providers/AccountProvider' import Nav from './navigation/Nav' import { isAllowedWhileBlocked, - isOrganisationUsage, shouldShowOrganisationLimit, } from 'web/routePaths' import 'project/darkMode' @@ -273,12 +272,6 @@ const App = class extends Component { pathname === '/getting-started' && getStoredOnboardingVariant() === 'single_page' - // The usage dashboard says the same thing in its own banner, with the - // detail this one cannot reach. - const hasOwnQuotaBanner = - isOrganisationUsage(pathname) && - Utils.getFlagsmithHasFeature('usage_dashboard') - const projectId = this.getProjectId(this.props) const environmentId = this.getEnvironmentId(this.props) @@ -371,8 +364,8 @@ const App = class extends Component { {user && ( <> {shouldShowOrganisationLimit( - AccountStore.isAdmin(), - hasOwnQuotaBanner, + pathname, + Utils.getFlagsmithHasFeature('usage_dashboard'), ) && ( matches(pathname, ORGANISATION_USAGE) export const shouldShowOrganisationLimit = ( - isAdmin: boolean, - hasOwnQuotaBanner: boolean, -): boolean => isAdmin && !hasOwnQuotaBanner + pathname: string, + usageDashboardEnabled: boolean, +): boolean => + Boolean(AccountStore.isAdmin()) && + !(isOrganisationUsage(pathname) && usageDashboardEnabled)