Skip to content

Commit d07e90d

Browse files
authored
feat(core): enable new add-on SKU real-time usage report (#7698)
* feat(core): enable third-party app usage report enable third-party app usage report * feat(core): enable SAML app usage report enable SAML app usage report * feat(core): support roles usage report support roles usage report * fix(core): fix unit test fix unit test
1 parent 90c0d69 commit d07e90d

File tree

10 files changed

+101
-42
lines changed

10 files changed

+101
-42
lines changed

packages/connectors/connector-logto-email/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"access": "public"
5353
},
5454
"devDependencies": {
55-
"@logto/cloud": "0.2.5-103b436",
55+
"@logto/cloud": "0.2.5-8a3dff0",
5656
"@silverhand/eslint-config": "6.0.1",
5757
"@silverhand/ts-config": "6.0.0",
5858
"@types/node": "^22.14.0",

packages/console/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@fontsource/roboto-mono": "^5.0.0",
2929
"@inkeep/cxkit-react": "^0.5.66",
3030
"@jest/types": "^29.5.0",
31-
"@logto/cloud": "0.2.5-103b436",
31+
"@logto/cloud": "0.2.5-8a3dff0",
3232
"@logto/connector-kit": "workspace:^",
3333
"@logto/core-kit": "workspace:^",
3434
"@logto/language-kit": "workspace:^",
@@ -52,13 +52,13 @@
5252
"@types/debug": "^4.1.7",
5353
"@types/jest": "^29.4.0",
5454
"@types/mdx": "^2.0.13",
55+
"@types/psl": "^1.1.3",
5556
"@types/react": "^18.3.3",
5657
"@types/react-color": "^3.0.6",
5758
"@types/react-dom": "^18.3.0",
5859
"@types/react-helmet": "^6.1.6",
5960
"@types/react-modal": "^3.13.1",
6061
"@types/react-syntax-highlighter": "^15.5.1",
61-
"@types/psl": "^1.1.3",
6262
"@vitejs/plugin-react": "^4.3.1",
6363
"@withtyped/client": "^0.8.8",
6464
"classnames": "^2.3.1",

packages/console/src/consts/subscriptions.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { ReservedPlanId } from '@logto/schemas';
22

3+
import { isDevFeaturesEnabled } from './env';
4+
35
/**
46
* Shared quota limits between the featured plan content in the `CreateTenantModal` and the `PlanComparisonTable`.
57
*/
@@ -24,15 +26,6 @@ export const hooksAddOnUnitPrice = 2;
2426
export const securityFeaturesAddOnUnitPrice = 48;
2527
/* === Add-on unit price (in USD) === */
2628

27-
/**
28-
* In console, only featured plans are shown in the plan selection component.
29-
* we will this to filter out the public visible featured plans.
30-
*/
31-
export const featuredPlanIds: readonly string[] = Object.freeze([
32-
ReservedPlanId.Free,
33-
ReservedPlanId.Pro202411,
34-
]);
35-
3629
/**
3730
* The order of plans in the plan selection content component.
3831
* Unlike the `featuredPlanIds`, include both grandfathered plans and public visible featured plans.
@@ -48,4 +41,15 @@ export const planIdOrder: Record<string, number> = Object.freeze({
4841
export const checkoutStateQueryKey = 'checkout-state';
4942

5043
/** The latest pro plan id we are using. */
51-
export const latestProPlanId = ReservedPlanId.Pro202411;
44+
export const latestProPlanId = isDevFeaturesEnabled
45+
? ReservedPlanId.Pro202509
46+
: ReservedPlanId.Pro202411;
47+
48+
/**
49+
* In console, only featured plans are shown in the plan selection component.
50+
* we will this to filter out the public visible featured plans.
51+
*/
52+
export const featuredPlanIds: readonly string[] = Object.freeze([
53+
ReservedPlanId.Free,
54+
latestProPlanId,
55+
]);

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
"zod": "3.24.3"
101101
},
102102
"devDependencies": {
103-
"@logto/cloud": "0.2.5-103b436",
103+
"@logto/cloud": "0.2.5-8a3dff0",
104104
"@silverhand/eslint-config": "6.0.1",
105105
"@silverhand/ts-config": "6.0.0",
106106
"@types/adm-zip": "^0.5.5",

packages/core/src/routes/applications/application.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { buildOidcClientMetadata } from '#src/oidc/utils.js';
2020
import assertThat from '#src/utils/assert-that.js';
2121
import { parseSearchParamsForSearch } from '#src/utils/search.js';
2222

23+
import { EnvSet } from '../../env-set/index.js';
2324
import type { ManagementApiRouter, RouterInitArgs } from '../types.js';
2425

2526
import applicationCustomDataRoutes from './application-custom-data.js';
@@ -161,7 +162,7 @@ export default function applicationRoutes<T extends ManagementApiRouter>(
161162
koaGuard({
162163
body: applicationCreateGuard,
163164
response: Applications.guard,
164-
status: [200, 400, 422, 500],
165+
status: [200, 400, 422, 403, 500],
165166
}),
166167
// eslint-disable-next-line complexity
167168
async (ctx, next) => {
@@ -226,6 +227,11 @@ export default function applicationRoutes<T extends ManagementApiRouter>(
226227
void quota.reportSubscriptionUpdatesUsage('machineToMachineLimit');
227228
}
228229

230+
// TODO: remove this dev feature guard when new pro plan and add-on skus are ready.
231+
if (EnvSet.values.isDevFeaturesEnabled && rest.isThirdParty) {
232+
void quota.reportSubscriptionUpdatesUsage('thirdPartyApplicationsLimit');
233+
}
234+
229235
return next();
230236
}
231237
);
@@ -361,7 +367,8 @@ export default function applicationRoutes<T extends ManagementApiRouter>(
361367
}),
362368
async (ctx, next) => {
363369
const { id } = ctx.guard.params;
364-
const { type, protectedAppMetadata } = await queries.applications.findApplicationById(id);
370+
const { type, protectedAppMetadata, isThirdParty } =
371+
await queries.applications.findApplicationById(id);
365372

366373
if (type === ApplicationType.SAML) {
367374
throw new RequestError('application.saml.use_saml_app_api');
@@ -383,6 +390,11 @@ export default function applicationRoutes<T extends ManagementApiRouter>(
383390
void quota.reportSubscriptionUpdatesUsage('machineToMachineLimit');
384391
}
385392

393+
// TODO: remove this dev feature guard when new pro plan and add-on skus are ready.
394+
if (EnvSet.values.isDevFeaturesEnabled && isThirdParty) {
395+
void quota.reportSubscriptionUpdatesUsage('thirdPartyApplicationsLimit');
396+
}
397+
386398
return next();
387399
}
388400
);

packages/core/src/routes/role.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ describe('role routes', () => {
169169
});
170170

171171
it('DELETE /roles/:id', async () => {
172+
findRoleById.mockResolvedValueOnce(mockAdminUserRole);
172173
const response = await roleRequester.delete(`/roles/${mockAdminUserRole.id}`);
173174
expect(response.status).toEqual(204);
174175
expect(deleteRoleById).toHaveBeenCalledWith(mockAdminUserRole.id);

packages/core/src/routes/role.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import koaRoleRlsErrorHandler from '#src/middleware/koa-role-rls-error-handler.j
1212
import assertThat from '#src/utils/assert-that.js';
1313
import { parseSearchParamsForSearch } from '#src/utils/search.js';
1414

15+
import { EnvSet } from '../env-set/index.js';
16+
1517
import roleApplicationRoutes from './role.application.js';
1618
import roleUserRoutes from './role.user.js';
1719
import type { ManagementApiRouter, RouterInitArgs } from './types.js';
@@ -179,6 +181,13 @@ export default function roleRoutes<T extends ManagementApiRouter>(
179181
);
180182
}
181183

184+
// TODO: remove this dev feature guard when new pro plan and add-on skus are ready.
185+
if (EnvSet.values.isDevFeaturesEnabled) {
186+
void quota.reportSubscriptionUpdatesUsage(
187+
role.type === RoleType.MachineToMachine ? 'machineToMachineRolesLimit' : 'userRolesLimit'
188+
);
189+
}
190+
182191
ctx.body = role;
183192

184193
// Hook context must be triggered after the response is set.
@@ -258,7 +267,18 @@ export default function roleRoutes<T extends ManagementApiRouter>(
258267
const {
259268
params: { id },
260269
} = ctx.guard;
270+
271+
// Check if role is available, and get role type before deleting the role
272+
const role = await findRoleById(id);
273+
261274
await deleteRoleById(id);
275+
276+
if (EnvSet.values.isDevFeaturesEnabled) {
277+
void quota.reportSubscriptionUpdatesUsage(
278+
role.type === RoleType.MachineToMachine ? 'machineToMachineRolesLimit' : 'userRolesLimit'
279+
);
280+
}
281+
262282
ctx.status = 204;
263283

264284
return next();

packages/core/src/routes/saml-application/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ export default function samlApplicationRoutes<T extends ManagementApiRouter>(
128128
}),
129129
]);
130130

131+
// TODO: remove this dev feature guard when new pro plan and add-on skus are ready.
132+
if (EnvSet.values.isDevFeaturesEnabled) {
133+
void quota.reportSubscriptionUpdatesUsage('samlApplicationsLimit');
134+
}
135+
131136
ctx.status = 201;
132137
ctx.body = ensembleSamlApplication({ application, samlConfig });
133138
} catch (error) {
@@ -200,6 +205,11 @@ export default function samlApplicationRoutes<T extends ManagementApiRouter>(
200205

201206
await deleteApplicationById(id);
202207

208+
// TODO: remove this dev feature guard when new pro plan and add-on skus are ready.
209+
if (EnvSet.values.isDevFeaturesEnabled) {
210+
void quota.reportSubscriptionUpdatesUsage('samlApplicationsLimit');
211+
}
212+
203213
ctx.status = 204;
204214

205215
return next();

packages/core/src/utils/subscription/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import type router from '@logto/cloud/routes';
22
import { type ToZodObject } from '@logto/connector-kit';
3+
import { conditional } from '@silverhand/essentials';
34
import { type RouterRoutes } from '@withtyped/client';
45
import { z, type ZodType } from 'zod';
56

7+
import { EnvSet } from '../../env-set/index.js';
8+
69
type GetRoutes = RouterRoutes<typeof router>['get'];
710
type PostRoutes = RouterRoutes<typeof router>['post'];
811

@@ -48,6 +51,13 @@ export type ReportSubscriptionUpdatesUsageKey = Exclude<
4851
'organizationsEnabled'
4952
>;
5053

54+
const newAddedReportableUsageKeys = Object.freeze([
55+
'thirdPartyApplicationsLimit',
56+
'userRolesLimit',
57+
'machineToMachineRolesLimit',
58+
'samlApplicationsLimit',
59+
] satisfies Array<keyof SubscriptionQuota>);
60+
5161
// Have to manually define this variable since we can only get the literal union from the @logto/cloud/routes module.
5262
export const allReportSubscriptionUpdatesUsageKeys = Object.freeze([
5363
'machineToMachineLimit',
@@ -58,6 +68,8 @@ export const allReportSubscriptionUpdatesUsageKeys = Object.freeze([
5868
'enterpriseSsoLimit',
5969
'hooksLimit',
6070
'securityFeaturesEnabled',
71+
// TODO: Remove this dev feature guard once new pro plan is ready for production
72+
...(conditional(EnvSet.values.isDevFeaturesEnabled && newAddedReportableUsageKeys) ?? []),
6173
]) satisfies readonly ReportSubscriptionUpdatesUsageKey[];
6274

6375
const subscriptionStatusGuard = z.enum([

0 commit comments

Comments
 (0)