From 4dc1f8219525821b0310556b6f49cc3b22fb3411 Mon Sep 17 00:00:00 2001 From: Darcy Ye Date: Sat, 12 Oct 2024 12:19:07 +0800 Subject: [PATCH] refactor: move has{Reach,Surpass}Limit method to subscription context (#6664) * refactor: move has{Reach,Surpass}Limit method to subscription context * chore: update code --- .../CreateConnectorForm/Footer/index.tsx | 9 +--- .../src/containers/AppContent/index.tsx | 27 +++++++++- .../SubscriptionDataProvider/index.tsx | 8 +-- .../SubscriptionDataProvider/types.ts | 22 +++++++- .../SubscriptionDataProvider/utils.ts | 43 +++++++++++++++ .../src/hooks/use-api-resources-usage.ts | 28 ++-------- .../src/hooks/use-applications-usage.ts | 54 ++++--------------- .../CreatePermissionModal/index.tsx | 11 ++-- .../AssignPermissionsModal/index.tsx | 11 ++-- .../components/CreateRoleForm/Footer.tsx | 32 +++++------ .../Roles/components/CreateRoleForm/index.tsx | 7 ++- .../TenantMembers/InviteMemberModal/index.tsx | 10 +--- .../TenantSettings/TenantMembers/hooks.ts | 32 +++-------- .../Webhooks/CreateFormModal/CreateForm.tsx | 9 +--- packages/console/src/utils/quota.ts | 39 -------------- 15 files changed, 152 insertions(+), 190 deletions(-) create mode 100644 packages/console/src/contexts/SubscriptionDataProvider/utils.ts delete mode 100644 packages/console/src/utils/quota.ts diff --git a/packages/console/src/components/CreateConnectorForm/Footer/index.tsx b/packages/console/src/components/CreateConnectorForm/Footer/index.tsx index e5e28b7f6..5598f005d 100644 --- a/packages/console/src/components/CreateConnectorForm/Footer/index.tsx +++ b/packages/console/src/components/CreateConnectorForm/Footer/index.tsx @@ -6,7 +6,6 @@ import QuotaGuardFooter from '@/components/QuotaGuardFooter'; import SkuName from '@/components/SkuName'; import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; import Button from '@/ds-components/Button'; -import { hasReachedSubscriptionQuotaLimit } from '@/utils/quota'; type Props = { readonly isCreatingSocialConnector: boolean; @@ -18,15 +17,11 @@ function Footer({ isCreatingSocialConnector, isCreateButtonDisabled, onClickCrea const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.upsell.paywall' }); const { currentSubscription: { planId, isEnterprisePlan }, - currentSubscriptionUsage, currentSubscriptionQuota, + hasReachedSubscriptionQuotaLimit, } = useContext(SubscriptionDataContext); - const isSocialConnectorsReachLimit = hasReachedSubscriptionQuotaLimit({ - quotaKey: 'socialConnectorsLimit', - usage: currentSubscriptionUsage.socialConnectorsLimit, - quota: currentSubscriptionQuota, - }); + const isSocialConnectorsReachLimit = hasReachedSubscriptionQuotaLimit('socialConnectorsLimit'); if (isCreatingSocialConnector && isSocialConnectorsReachLimit) { return ( diff --git a/packages/console/src/containers/AppContent/index.tsx b/packages/console/src/containers/AppContent/index.tsx index d6ece9696..3a0106261 100644 --- a/packages/console/src/containers/AppContent/index.tsx +++ b/packages/console/src/containers/AppContent/index.tsx @@ -2,12 +2,17 @@ import { conditional, joinPath } from '@silverhand/essentials'; import { useContext, useRef } from 'react'; import { Navigate, Outlet, useParams } from 'react-router-dom'; +import { type NewSubscriptionCountBasedUsage } from '@/cloud/types/router'; import AppLoading from '@/components/AppLoading'; import Topbar from '@/components/Topbar'; import { isCloud } from '@/consts/env'; import SubscriptionDataProvider from '@/contexts/SubscriptionDataProvider'; import useNewSubscriptionData from '@/contexts/SubscriptionDataProvider/use-new-subscription-data'; import useSubscriptionData from '@/contexts/SubscriptionDataProvider/use-subscription-data'; +import { + hasSurpassedSubscriptionQuotaLimit, + hasReachedSubscriptionQuotaLimit, +} from '@/contexts/SubscriptionDataProvider/utils'; import { TenantsContext } from '@/contexts/TenantsProvider'; import useScroll from '@/hooks/use-scroll'; import useUserPreferences from '@/hooks/use-user-preferences'; @@ -42,9 +47,29 @@ export default function AppContent() { return ( ( + quotaKey: T, + usage?: NewSubscriptionCountBasedUsage[T] + ) => + hasSurpassedSubscriptionQuotaLimit({ + quotaKey, + usage, + subscriptionUsage: newSubscriptionData.currentSubscriptionUsage, + subscriptionQuota: newSubscriptionData.currentSubscriptionQuota, + }), + hasReachedSubscriptionQuotaLimit: ( + quotaKey: T, + usage?: NewSubscriptionCountBasedUsage[T] + ) => + hasReachedSubscriptionQuotaLimit({ + quotaKey, + usage, + subscriptionUsage: newSubscriptionData.currentSubscriptionUsage, + subscriptionQuota: newSubscriptionData.currentSubscriptionQuota, + }), }} >
diff --git a/packages/console/src/contexts/SubscriptionDataProvider/index.tsx b/packages/console/src/contexts/SubscriptionDataProvider/index.tsx index d0536c729..b99af40d7 100644 --- a/packages/console/src/contexts/SubscriptionDataProvider/index.tsx +++ b/packages/console/src/contexts/SubscriptionDataProvider/index.tsx @@ -32,16 +32,18 @@ export const SubscriptionDataContext = createContext({ currentSubscriptionRoleScopeUsage: {}, mutateSubscriptionQuotaAndUsages: noop, /* ==== For new pricing model ==== */ + hasSurpassedSubscriptionQuotaLimit: () => false, + hasReachedSubscriptionQuotaLimit: () => false, }); type Props = { - readonly subscriptionData: FullContext; + readonly subscriptionDataAndUtils: FullContext; readonly children: ReactNode; }; -function SubscriptionDataProvider({ children, subscriptionData }: Props) { +function SubscriptionDataProvider({ children, subscriptionDataAndUtils }: Props) { return ( - + {children} ); diff --git a/packages/console/src/contexts/SubscriptionDataProvider/types.ts b/packages/console/src/contexts/SubscriptionDataProvider/types.ts index ccb427449..33793bb48 100644 --- a/packages/console/src/contexts/SubscriptionDataProvider/types.ts +++ b/packages/console/src/contexts/SubscriptionDataProvider/types.ts @@ -12,6 +12,13 @@ export type Context = { onCurrentSubscriptionUpdated: (subscription?: Subscription) => void; }; +export type SubscriptionUsageOptions = { + quotaKey: T; + subscriptionUsage: NewSubscriptionCountBasedUsage; + subscriptionQuota: NewSubscriptionQuota; + usage?: NewSubscriptionCountBasedUsage[T]; +}; + type NewSubscriptionSupplementContext = { logtoSkus: LogtoSkuResponse[]; currentSku: LogtoSkuResponse; @@ -23,6 +30,19 @@ type NewSubscriptionSupplementContext = { mutateSubscriptionQuotaAndUsages: () => void; }; +type NewSubscriptionResourceStatus = { + hasSurpassedSubscriptionQuotaLimit: ( + quotaKey: T, + usage?: NewSubscriptionCountBasedUsage[T] + ) => boolean; + hasReachedSubscriptionQuotaLimit: ( + quotaKey: T, + usage?: NewSubscriptionCountBasedUsage[T] + ) => boolean; +}; + export type NewSubscriptionContext = Context & NewSubscriptionSupplementContext; -export type FullContext = Context & NewSubscriptionSupplementContext; +export type FullContext = Context & + NewSubscriptionSupplementContext & + NewSubscriptionResourceStatus; diff --git a/packages/console/src/contexts/SubscriptionDataProvider/utils.ts b/packages/console/src/contexts/SubscriptionDataProvider/utils.ts new file mode 100644 index 000000000..1d60cb1a8 --- /dev/null +++ b/packages/console/src/contexts/SubscriptionDataProvider/utils.ts @@ -0,0 +1,43 @@ +import { type NewSubscriptionCountBasedUsage } from '@/cloud/types/router'; +import { isCloud } from '@/consts/env'; + +import { type SubscriptionUsageOptions } from './types'; + +/* === For new pricing model === */ +const isSubscriptionUsageWithInLimit = ( + { quotaKey, subscriptionUsage, subscriptionQuota, usage }: SubscriptionUsageOptions, + inclusive = true +) => { + // No limitations for OSS version + if (!isCloud) { + return true; + } + + /** + * Sometimes we need to manually retrieve usage to overwrite the usage in subscriptionUsage. + * For example, for the usage of `scopesPerResourceLimit`, `subscriptionUsage.scopesPerResourceLimit` records the largest value among all resource scopes. + * However, when operating on resources in practice, we need to know the specific usage of scopes for the current resource. In this case, we need to manually calculate the value of scopes for the current resource before calling the function. + */ + const usageValue = usage ?? subscriptionUsage[quotaKey]; + const quotaValue = subscriptionQuota[quotaKey]; + + // Unlimited + if (quotaValue === null) { + return true; + } + + if (typeof quotaValue === 'boolean') { + return quotaValue; + } + + return inclusive ? usageValue <= quotaValue : usageValue < quotaValue; +}; + +export const hasSurpassedSubscriptionQuotaLimit = ( + options: SubscriptionUsageOptions +) => !isSubscriptionUsageWithInLimit(options); + +export const hasReachedSubscriptionQuotaLimit = ( + options: SubscriptionUsageOptions +) => !isSubscriptionUsageWithInLimit(options, false); +/* === For new pricing model === */ diff --git a/packages/console/src/hooks/use-api-resources-usage.ts b/packages/console/src/hooks/use-api-resources-usage.ts index 15c4621b7..ec8ec70ac 100644 --- a/packages/console/src/hooks/use-api-resources-usage.ts +++ b/packages/console/src/hooks/use-api-resources-usage.ts @@ -1,34 +1,14 @@ -import { useContext, useMemo } from 'react'; +import { useContext } from 'react'; import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; -import { - hasReachedSubscriptionQuotaLimit, - hasSurpassedSubscriptionQuotaLimit, -} from '@/utils/quota'; const useApiResourcesUsage = () => { - const { currentSubscriptionQuota, currentSubscriptionUsage } = + const { hasReachedSubscriptionQuotaLimit, hasSurpassedSubscriptionQuotaLimit } = useContext(SubscriptionDataContext); - const hasReachedLimit = useMemo( - () => - hasReachedSubscriptionQuotaLimit({ - quotaKey: 'resourcesLimit', - usage: currentSubscriptionUsage.resourcesLimit, - quota: currentSubscriptionQuota, - }), - [currentSubscriptionQuota, currentSubscriptionUsage.resourcesLimit] - ); + const hasReachedLimit = hasReachedSubscriptionQuotaLimit('resourcesLimit'); - const hasSurpassedLimit = useMemo( - () => - hasSurpassedSubscriptionQuotaLimit({ - quotaKey: 'resourcesLimit', - usage: currentSubscriptionUsage.resourcesLimit, - quota: currentSubscriptionQuota, - }), - [currentSubscriptionQuota, currentSubscriptionUsage.resourcesLimit] - ); + const hasSurpassedLimit = hasSurpassedSubscriptionQuotaLimit('resourcesLimit'); return { hasReachedLimit, diff --git a/packages/console/src/hooks/use-applications-usage.ts b/packages/console/src/hooks/use-applications-usage.ts index 343cdf103..4344216a7 100644 --- a/packages/console/src/hooks/use-applications-usage.ts +++ b/packages/console/src/hooks/use-applications-usage.ts @@ -1,54 +1,22 @@ -import { useContext, useMemo } from 'react'; +import { useContext } from 'react'; import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; -import { - hasReachedSubscriptionQuotaLimit, - hasSurpassedSubscriptionQuotaLimit, -} from '@/utils/quota'; const useApplicationsUsage = () => { - const { currentSubscriptionQuota, currentSubscriptionUsage } = + const { hasReachedSubscriptionQuotaLimit, hasSurpassedSubscriptionQuotaLimit } = useContext(SubscriptionDataContext); - const hasMachineToMachineAppsReachedLimit = useMemo( - () => - hasReachedSubscriptionQuotaLimit({ - quotaKey: 'machineToMachineLimit', - usage: currentSubscriptionUsage.machineToMachineLimit, - quota: currentSubscriptionQuota, - }), - [currentSubscriptionUsage.machineToMachineLimit, currentSubscriptionQuota] + const hasMachineToMachineAppsReachedLimit = + hasReachedSubscriptionQuotaLimit('machineToMachineLimit'); + + const hasMachineToMachineAppsSurpassedLimit = + hasSurpassedSubscriptionQuotaLimit('machineToMachineLimit'); + + const hasThirdPartyAppsReachedLimit = hasReachedSubscriptionQuotaLimit( + 'thirdPartyApplicationsLimit' ); - const hasMachineToMachineAppsSurpassedLimit = useMemo( - () => - hasSurpassedSubscriptionQuotaLimit({ - quotaKey: 'machineToMachineLimit', - usage: currentSubscriptionUsage.machineToMachineLimit, - quota: currentSubscriptionQuota, - }), - [currentSubscriptionUsage.machineToMachineLimit, currentSubscriptionQuota] - ); - - const hasThirdPartyAppsReachedLimit = useMemo( - () => - hasReachedSubscriptionQuotaLimit({ - quotaKey: 'thirdPartyApplicationsLimit', - usage: currentSubscriptionUsage.thirdPartyApplicationsLimit, - quota: currentSubscriptionQuota, - }), - [currentSubscriptionUsage.thirdPartyApplicationsLimit, currentSubscriptionQuota] - ); - - const hasAppsReachedLimit = useMemo( - () => - hasReachedSubscriptionQuotaLimit({ - quotaKey: 'applicationsLimit', - usage: currentSubscriptionUsage.applicationsLimit, - quota: currentSubscriptionQuota, - }), - [currentSubscriptionUsage.applicationsLimit, currentSubscriptionQuota] - ); + const hasAppsReachedLimit = hasReachedSubscriptionQuotaLimit('applicationsLimit'); return { hasMachineToMachineAppsReachedLimit, diff --git a/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/components/CreatePermissionModal/index.tsx b/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/components/CreatePermissionModal/index.tsx index 4cfa7b0f1..037883a2a 100644 --- a/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/components/CreatePermissionModal/index.tsx +++ b/packages/console/src/pages/ApiResourceDetails/ApiResourcePermissions/components/CreatePermissionModal/index.tsx @@ -16,7 +16,6 @@ import TextInput from '@/ds-components/TextInput'; import useApi from '@/hooks/use-api'; import modalStyles from '@/scss/modal.module.scss'; import { trySubmitSafe } from '@/utils/form'; -import { hasReachedSubscriptionQuotaLimit } from '@/utils/quota'; type Props = { readonly resourceId: string; @@ -32,6 +31,7 @@ function CreatePermissionModal({ resourceId, totalResourceCount, onClose }: Prop currentSubscriptionQuota, currentSubscriptionResourceScopeUsage, currentSubscription: { planId, isEnterprisePlan }, + hasReachedSubscriptionQuotaLimit, } = useContext(SubscriptionDataContext); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); @@ -57,11 +57,10 @@ function CreatePermissionModal({ resourceId, totalResourceCount, onClose }: Prop }) ); - const isScopesPerResourceReachLimit = hasReachedSubscriptionQuotaLimit({ - quotaKey: 'scopesPerResourceLimit', - usage: currentSubscriptionResourceScopeUsage[resourceId] ?? 0, - quota: currentSubscriptionQuota, - }); + const isScopesPerResourceReachLimit = hasReachedSubscriptionQuotaLimit( + 'scopesPerResourceLimit', + currentSubscriptionResourceScopeUsage[resourceId] ?? 0 + ); return ( ([]); @@ -52,11 +52,10 @@ function AssignPermissionsModal({ roleId, roleType, onClose }: Props) { } }; - const shouldBlockScopeAssignment = hasSurpassedSubscriptionQuotaLimit({ - quotaKey: 'scopesPerRoleLimit', - usage: (currentSubscriptionRoleScopeUsage[roleId] ?? 0) + scopes.length, - quota: currentSubscriptionQuota, - }); + const shouldBlockScopeAssignment = hasSurpassedSubscriptionQuotaLimit( + 'scopesPerRoleLimit', + (currentSubscriptionRoleScopeUsage[roleId] ?? 0) + scopes.length + ); return ( void; }; -function Footer({ roleType, isCreating, onClickCreate }: Props) { +function Footer({ roleType, scopes, isCreating, onClickCreate }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { currentSubscription: { planId, isEnterprisePlan }, currentSubscriptionQuota, - currentSubscriptionUsage, + hasReachedSubscriptionQuotaLimit, + hasSurpassedSubscriptionQuotaLimit, } = useContext(SubscriptionDataContext); - const hasRoleReachedLimit = hasReachedSubscriptionQuotaLimit({ - quotaKey: roleType === RoleType.User ? 'userRolesLimit' : 'machineToMachineRolesLimit', - usage: - roleType === RoleType.User - ? currentSubscriptionUsage.userRolesLimit - : currentSubscriptionUsage.machineToMachineRolesLimit, - quota: currentSubscriptionQuota, - }); + const hasRoleReachedLimit = hasReachedSubscriptionQuotaLimit( + roleType === RoleType.User ? 'userRolesLimit' : 'machineToMachineRolesLimit' + ); - const hasScopesPerRoleSurpassedLimit = hasSurpassedSubscriptionQuotaLimit({ - quotaKey: 'scopesPerRoleLimit', - usage: currentSubscriptionUsage.scopesPerRoleLimit, - quota: currentSubscriptionQuota, - }); + const hasScopesPerRoleSurpassedLimit = hasSurpassedSubscriptionQuotaLimit( + 'scopesPerRoleLimit', + scopes?.length ?? 0 + ); if (hasRoleReachedLimit || hasScopesPerRoleSurpassedLimit) { return ( diff --git a/packages/console/src/pages/Roles/components/CreateRoleForm/index.tsx b/packages/console/src/pages/Roles/components/CreateRoleForm/index.tsx index 721996e7b..5c33d011b 100644 --- a/packages/console/src/pages/Roles/components/CreateRoleForm/index.tsx +++ b/packages/console/src/pages/Roles/components/CreateRoleForm/index.tsx @@ -77,7 +77,12 @@ function CreateRoleForm({ onClose }: Props) { }} size="large" footer={ -