Skip to content

Commit b86fba1

Browse files
author
Andrew Farries
committed
Add getUsageLimitFor and setUsageLimitFor
Add two new methods to the server API for getting and setting usage limits. Both new functions take an attributionId and work for both users and teams. For backwards compatibility, leave the `getUsageLimitForTeam` and `setUsageLimitForTeam` methods as they are still used by the dashboard, but change them to be implemented in terms of the more general `get/set` methods.
1 parent 6635528 commit b86fba1

File tree

4 files changed

+64
-16
lines changed

4 files changed

+64
-16
lines changed

components/gitpod-protocol/src/gitpod-service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,8 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
295295
createOrUpdateStripeCustomerForUser(currency: string): Promise<void>;
296296
subscribeTeamToStripe(teamId: string, setupIntentId: string): Promise<void>;
297297
getStripePortalUrlForTeam(teamId: string): Promise<string>;
298+
getUsageLimit(attributionId: string): Promise<number | undefined>;
299+
setUsageLimit(attributionId: string, usageLimit: number): Promise<void>;
298300
getUsageLimitForTeam(teamId: string): Promise<number | undefined>;
299301
setUsageLimitForTeam(teamId: string, spendingLimit: number): Promise<void>;
300302

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2146,39 +2146,75 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
21462146
}
21472147
}
21482148

2149-
async getUsageLimitForTeam(ctx: TraceContext, teamId: string): Promise<number | undefined> {
2150-
const user = this.checkAndBlockUser("getUsageLimitForTeam");
2151-
const team = await this.guardTeamOperation(teamId, "get");
2152-
await this.ensureStripeApiIsAllowed({ team });
2149+
async getUsageLimit(ctx: TraceContext, attributionId: string): Promise<number | undefined> {
2150+
const attrId = AttributionId.parse(attributionId);
2151+
if (attrId === undefined) {
2152+
log.error(`Invalid attribution id: ${attributionId}`);
2153+
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Invalid attibution id: ${attributionId}`);
2154+
}
21532155

2154-
const attributionId: AttributionId = { kind: "team", teamId };
2155-
await this.guardCostCenterAccess(ctx, user.id, attributionId, "get");
2156+
const user = this.checkAndBlockUser("getUsageLimit");
2157+
if (attrId.kind === "team") {
2158+
const team = await this.guardTeamOperation(attrId.teamId, "get");
2159+
await this.ensureStripeApiIsAllowed({ team });
2160+
}
2161+
await this.guardCostCenterAccess(ctx, user.id, attrId, "get");
21562162

2157-
const costCenter = await this.usageServiceClientProvider.getDefault().getCostCenter(attributionId);
2163+
const costCenter = await this.usageServiceClientProvider.getDefault().getCostCenter(attrId);
21582164
if (costCenter) {
21592165
return costCenter.spendingLimit;
21602166
}
21612167
return undefined;
21622168
}
21632169

2164-
async setUsageLimitForTeam(ctx: TraceContext, teamId: string, usageLimit: number): Promise<void> {
2165-
const user = this.checkAndBlockUser("setUsageLimitForTeam");
2166-
const team = await this.guardTeamOperation(teamId, "update");
2167-
await this.ensureStripeApiIsAllowed({ team });
2170+
async setUsageLimit(ctx: TraceContext, attributionId: string, usageLimit: number): Promise<void> {
2171+
const attrId = AttributionId.parse(attributionId);
2172+
if (attrId === undefined) {
2173+
log.error(`Invalid attribution id: ${attributionId}`);
2174+
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Invalid attibution id: ${attributionId}`);
2175+
}
21682176
if (typeof usageLimit !== "number" || usageLimit < 0) {
2169-
throw new ResponseError(ErrorCodes.BAD_REQUEST, "Unexpected `usageLimit` value.");
2177+
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Unexpected usageLimit value: ${usageLimit}`);
2178+
}
2179+
2180+
const user = this.checkAndBlockUser("setUsageLimit");
2181+
switch (attrId.kind) {
2182+
case "team":
2183+
const team = await this.guardTeamOperation(attrId.teamId, "update");
2184+
await this.ensureStripeApiIsAllowed({ team });
2185+
break;
2186+
case "user":
2187+
await this.ensureStripeApiIsAllowed({ user });
2188+
break;
2189+
}
2190+
await this.guardCostCenterAccess(ctx, user.id, attrId, "update");
2191+
2192+
const costCenter = await this.usageServiceClientProvider.getDefault().getCostCenter(attrId);
2193+
if (costCenter?.billingStrategy !== "stripe") {
2194+
throw new ResponseError(
2195+
ErrorCodes.BAD_REQUEST,
2196+
`Setting a usage limit is not valid for non-Stripe billing strategies`,
2197+
);
21702198
}
2171-
const attributionId: AttributionId = { kind: "team", teamId };
2172-
await this.guardCostCenterAccess(ctx, user.id, attributionId, "update");
21732199

2174-
const costCenter = await this.usageServiceClientProvider.getDefault().getCostCenter(attributionId);
21752200
await this.usageServiceClientProvider.getDefault().setCostCenter({
2176-
id: attributionId,
2201+
id: attrId,
21772202
spendingLimit: usageLimit,
21782203
billingStrategy: costCenter?.billingStrategy || "other",
21792204
});
21802205
}
21812206

2207+
async getUsageLimitForTeam(ctx: TraceContext, teamId: string): Promise<number | undefined> {
2208+
const attributionId: AttributionId = { kind: "team", teamId: teamId };
2209+
2210+
return this.getUsageLimit(ctx, AttributionId.render(attributionId));
2211+
}
2212+
2213+
async setUsageLimitForTeam(ctx: TraceContext, teamId: string, usageLimit: number): Promise<void> {
2214+
const attributionId: AttributionId = { kind: "team", teamId: teamId };
2215+
return this.setUsageLimit(ctx, AttributionId.render(attributionId), usageLimit);
2216+
}
2217+
21822218
async getNotifications(ctx: TraceContext): Promise<string[]> {
21832219
const result = await super.getNotifications(ctx);
21842220
const user = this.checkAndBlockUser("getNotifications");

components/server/src/auth/rate-limiter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ const defaultFunctions: FunctionsConfig = {
221221
getIDEOptions: { group: "default", points: 1 },
222222
getPrebuildEvents: { group: "default", points: 1 },
223223
setUsageAttribution: { group: "default", points: 1 },
224+
getUsageLimit: { group: "default", points: 1 },
225+
setUsageLimit: { group: "default", points: 1 },
224226
getUsageLimitForTeam: { group: "default", points: 1 },
225227
setUsageLimitForTeam: { group: "default", points: 1 },
226228
getNotifications: { group: "default", points: 1 },

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3238,6 +3238,14 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
32383238
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
32393239
}
32403240

3241+
async getUsageLimit(ctx: TraceContext, attributionId: string): Promise<number | undefined> {
3242+
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
3243+
}
3244+
3245+
async setUsageLimit(ctx: TraceContext, attributionId: string, usageLimit: number): Promise<void> {
3246+
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
3247+
}
3248+
32413249
async getUsageLimitForTeam(ctx: TraceContext, teamId: string): Promise<number | undefined> {
32423250
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
32433251
}

0 commit comments

Comments
 (0)