Skip to content

Commit 4893567

Browse files
committed
[server][dashboard] Fetch and use the actual cost center billing cycle dates in usage-based Billing pages
1 parent aaee277 commit 4893567

File tree

6 files changed

+28
-30
lines changed

6 files changed

+28
-30
lines changed

components/dashboard/src/components/UsageBasedBillingConfig.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
4545

4646
const localStorageKey = `pendingStripeSubscriptionFor${attributionId}`;
4747
const now = dayjs().utc(true);
48-
const billingPeriodFrom = now.startOf("month");
49-
const billingPeriodTo = now.endOf("month");
48+
const [billingCycleFrom, setBillingCycleFrom] = useState<dayjs.Dayjs>(now.startOf("month"));
49+
const [billingCycleTo, setBillingCycleTo] = useState<dayjs.Dayjs>(now.endOf("month"));
5050

5151
useEffect(() => {
5252
if (!attributionId) {
@@ -56,12 +56,14 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
5656
setStripeSubscriptionId(undefined);
5757
setIsLoadingStripeSubscription(true);
5858
try {
59-
const [subscriptionId, limit] = await Promise.all([
59+
const [subscriptionId, costCenter] = await Promise.all([
6060
getGitpodService().server.findStripeSubscriptionId(attributionId),
61-
getGitpodService().server.getUsageLimit(attributionId),
61+
getGitpodService().server.getCostCenter(attributionId),
6262
]);
6363
setStripeSubscriptionId(subscriptionId);
64-
setUsageLimit(limit);
64+
setUsageLimit(costCenter?.spendingLimit);
65+
setBillingCycleFrom((dayjs(costCenter?.billingCycleStart) || now.startOf("month")).utc(true));
66+
setBillingCycleTo((dayjs(costCenter?.nextBillingTime) || now.endOf("month")).utc(true));
6567
} catch (error) {
6668
console.error("Could not get Stripe subscription details.", error);
6769
setErrorMessage(`Could not get Stripe subscription details. ${error?.message || String(error)}`);
@@ -178,12 +180,12 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
178180
const response = await getGitpodService().server.listUsage({
179181
attributionId,
180182
order: Ordering.ORDERING_DESCENDING,
181-
from: billingPeriodFrom.toDate().getTime(),
183+
from: billingCycleFrom.toDate().getTime(),
182184
to: Date.now(),
183185
});
184186
setCurrentUsage(response.creditsUsed);
185187
})();
186-
}, [attributionId]);
188+
}, [attributionId, billingCycleFrom]);
187189

188190
const showSpinner = !attributionId || isLoadingStripeSubscription || !!pendingStripeSubscription;
189191
const showBalance = !showSpinner && !(AttributionId.parse(attributionId)?.kind === "team" && !stripeSubscriptionId);
@@ -255,8 +257,15 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
255257
<div className="flex-grow">
256258
<div className="uppercase text-sm text-gray-400 dark:text-gray-500">Current Period</div>
257259
<div className="text-sm font-medium text-gray-500 dark:text-gray-400">
258-
<span className="font-semibold">{`${billingPeriodFrom.format("MMMM YYYY")}`}</span>{" "}
259-
{`(${billingPeriodFrom.format("MMM D")}` + ` - ${billingPeriodTo.format("MMM D")})`}
260+
<span className="font-semibold">{`${billingCycleFrom.format("MMMM YYYY")}`}</span> (
261+
<span title={billingCycleFrom.toDate().toUTCString().replace("GMT", "UTC")}>
262+
{billingCycleFrom.format("MMM D")}
263+
</span>{" "}
264+
-{" "}
265+
<span title={billingCycleTo.toDate().toUTCString().replace("GMT", "UTC")}>
266+
{billingCycleTo.format("MMM D")}
267+
</span>
268+
)
260269
</div>
261270
</div>
262271
<div>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import { InstallationAdminSettings, TelemetryData } from "./installation-admin-p
6262
import { ListUsageRequest, ListUsageResponse } from "./usage";
6363
import { SupportedWorkspaceClass } from "./workspace-class";
6464
import { BillingMode } from "./billing-mode";
65+
import { CostCenter } from "@gitpod/usage-api/lib/usage/v1/usage.pb";
6566

6667
export interface GitpodClient {
6768
onInstanceUpdate(instance: WorkspaceInstance): void;
@@ -280,7 +281,7 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
280281
createStripeCustomerIfNeeded(attributionId: string, currency: string): Promise<void>;
281282
subscribeToStripe(attributionId: string, setupIntentId: string, usageLimit: number): Promise<number | undefined>;
282283
getStripePortalUrl(attributionId: string): Promise<string>;
283-
getUsageLimit(attributionId: string): Promise<number | undefined>;
284+
getCostCenter(attributionId: string): Promise<CostCenter | undefined>;
284285
setUsageLimit(attributionId: string, usageLimit: number): Promise<void>;
285286

286287
listUsage(req: ListUsageRequest): Promise<ListUsageResponse>;

components/gitpod-protocol/src/protocol.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { WorkspaceInstance, PortVisibility } from "./workspace-instance";
88
import { RoleOrPermission } from "./permission";
99
import { Project } from "./teams-projects-protocol";
1010
import { createHash } from "crypto";
11-
import { AttributionId } from "./attribution";
1211

1312
export interface UserInfo {
1413
name?: string;
@@ -1518,13 +1517,3 @@ export interface StripeConfig {
15181517
individualUsagePriceIds: { [currency: string]: string };
15191518
teamUsagePriceIds: { [currency: string]: string };
15201519
}
1521-
1522-
export type BillingStrategy = "other" | "stripe";
1523-
export interface CostCenter {
1524-
readonly id: AttributionId;
1525-
/**
1526-
* Unit: credits
1527-
*/
1528-
spendingLimit: number;
1529-
billingStrategy: BillingStrategy;
1530-
}

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import { AccountStatementProvider } from "../user/account-statement-provider";
7373
import { GithubUpgradeURL, PlanCoupon } from "@gitpod/gitpod-protocol/lib/payment-protocol";
7474
import { ListUsageRequest, ListUsageResponse } from "@gitpod/gitpod-protocol/lib/usage";
7575
import {
76+
CostCenter,
7677
CostCenter_BillingStrategy,
7778
ListUsageRequest_Ordering,
7879
UsageServiceClient,
@@ -2261,21 +2262,18 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
22612262
return url;
22622263
}
22632264

2264-
async getUsageLimit(ctx: TraceContext, attributionId: string): Promise<number | undefined> {
2265+
async getCostCenter(ctx: TraceContext, attributionId: string): Promise<CostCenter | undefined> {
22652266
const attrId = AttributionId.parse(attributionId);
22662267
if (attrId === undefined) {
22672268
log.error(`Invalid attribution id: ${attributionId}`);
22682269
throw new ResponseError(ErrorCodes.BAD_REQUEST, `Invalid attibution id: ${attributionId}`);
22692270
}
22702271

2271-
const user = this.checkAndBlockUser("getUsageLimit");
2272+
const user = this.checkAndBlockUser("getCostCenter");
22722273
await this.guardCostCenterAccess(ctx, user.id, attrId, "get");
22732274

2274-
const costCenter = await this.usageService.getCostCenter({ attributionId });
2275-
if (costCenter?.costCenter) {
2276-
return costCenter.costCenter.spendingLimit;
2277-
}
2278-
return undefined;
2275+
const { costCenter } = await this.usageService.getCostCenter({ attributionId });
2276+
return costCenter;
22792277
}
22802278

22812279
async setUsageLimit(ctx: TraceContext, attributionId: string, usageLimit: number): Promise<void> {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ const defaultFunctions: FunctionsConfig = {
214214
getPrebuildEvents: { group: "default", points: 1 },
215215
setUsageAttribution: { group: "default", points: 1 },
216216
listAvailableUsageAttributionIds: { group: "default", points: 1 },
217-
getUsageLimit: { group: "default", points: 1 },
217+
getCostCenter: { group: "default", points: 1 },
218218
setUsageLimit: { group: "default", points: 1 },
219219
getNotifications: { group: "default", points: 1 },
220220
getSupportedWorkspaceClasses: { group: "default", points: 1 },

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ import { IDEService } from "../ide-service";
181181
import { MessageBusIntegration } from "./messagebus-integration";
182182
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
183183
import * as grpc from "@grpc/grpc-js";
184+
import { CostCenter } from "@gitpod/usage-api/lib/usage/v1/usage.pb";
184185

185186
// shortcut
186187
export const traceWI = (ctx: TraceContext, wi: Omit<LogContext, "userId">) => TraceContext.setOWI(ctx, wi); // userId is already taken care of in WebsocketConnectionManager
@@ -3157,7 +3158,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
31573158
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
31583159
}
31593160

3160-
async getUsageLimit(ctx: TraceContext, attributionId: string): Promise<number | undefined> {
3161+
async getCostCenter(ctx: TraceContext, attributionId: string): Promise<CostCenter | undefined> {
31613162
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
31623163
}
31633164

0 commit comments

Comments
 (0)