diff --git a/components/dashboard/src/teams/TeamUsage.tsx b/components/dashboard/src/teams/TeamUsage.tsx index 13fb8bf710565f..a81cdc3e81c571 100644 --- a/components/dashboard/src/teams/TeamUsage.tsx +++ b/components/dashboard/src/teams/TeamUsage.tsx @@ -8,7 +8,7 @@ import { useContext, useEffect, useState } from "react"; import { Redirect, useLocation } from "react-router"; import { getCurrentTeam, TeamsContext } from "./teams-context"; import { getGitpodService, gitpodHostUrl } from "../service/service"; -import { BillableSession, BillableWorkspaceType } from "@gitpod/gitpod-protocol/lib/usage"; +import { BillableSession, BillableWorkspaceType, SortOrder } from "@gitpod/gitpod-protocol/lib/usage"; import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution"; import { Item, ItemField, ItemsList } from "../components/ItemsList"; import moment from "moment"; @@ -34,6 +34,7 @@ function TeamUsage() { const [startDateOfBillMonth, setStartDateOfBillMonth] = useState(timestampStartOfCurrentMonth); const [endDateOfBillMonth, setEndDateOfBillMonth] = useState(Date.now()); const [isLoading, setIsLoading] = useState(true); + const [startedTimeOrder] = useState(SortOrder.Descending); useEffect(() => { if (!team) { @@ -41,12 +42,14 @@ function TeamUsage() { } (async () => { const attributionId = AttributionId.render({ kind: "team", teamId: team.id }); + const request = { + attributionId, + startedTimeOrder, + startDateOfBillMonth, + endDateOfBillMonth, + }; try { - const billedUsageResult = await getGitpodService().server.listBilledUsage( - attributionId, - startDateOfBillMonth, - endDateOfBillMonth, - ); + const billedUsageResult = await getGitpodService().server.listBilledUsage(request); setBilledUsage(billedUsageResult); } catch (error) { if (error.code === ErrorCodes.PERMISSION_DENIED) { @@ -56,7 +59,7 @@ function TeamUsage() { setIsLoading(false); } })(); - }, [team, startDateOfBillMonth, endDateOfBillMonth]); + }, [team, startDateOfBillMonth, endDateOfBillMonth, startedTimeOrder]); if (!showUsageBasedPricingUI) { return ; diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts index 201032cd502487..244a48c42c0bd4 100644 --- a/components/gitpod-protocol/src/gitpod-service.ts +++ b/components/gitpod-protocol/src/gitpod-service.ts @@ -61,7 +61,7 @@ import { RemotePageMessage, RemoteTrackMessage, RemoteIdentifyMessage } from "./ import { IDEServer } from "./ide-protocol"; import { InstallationAdminSettings, TelemetryData } from "./installation-admin-protocol"; import { Currency } from "./plans"; -import { BillableSession } from "./usage"; +import { BillableSession, BillableSessionRequest } from "./usage"; import { SupportedWorkspaceClass } from "./workspace-class"; export interface GitpodClient { @@ -294,7 +294,8 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, getSpendingLimitForTeam(teamId: string): Promise; setSpendingLimitForTeam(teamId: string, spendingLimit: number): Promise; - listBilledUsage(attributionId: string, from?: number, to?: number): Promise; + listBilledUsage(req: BillableSessionRequest): Promise; + setUsageAttribution(usageAttribution: string): Promise; /** @@ -308,7 +309,7 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, * Frontend notifications */ getNotifications(): Promise; - + getSupportedWorkspaceClasses(): Promise; } diff --git a/components/gitpod-protocol/src/usage.ts b/components/gitpod-protocol/src/usage.ts index c7199018dcfc13..777a8bcef1777b 100644 --- a/components/gitpod-protocol/src/usage.ts +++ b/components/gitpod-protocol/src/usage.ts @@ -35,4 +35,16 @@ export interface BillableSession { projectId?: string; } +export interface BillableSessionRequest { + attributionId: string; + startedTimeOrder: SortOrder; + from?: number; + to?: number; +} + export type BillableWorkspaceType = WorkspaceType; + +export enum SortOrder { + Descending = 0, + Ascending = 1, +} diff --git a/components/server/ee/src/workspace/gitpod-server-impl.ts b/components/server/ee/src/workspace/gitpod-server-impl.ts index d516faf328141b..3e820131ac6b1c 100644 --- a/components/server/ee/src/workspace/gitpod-server-impl.ts +++ b/components/server/ee/src/workspace/gitpod-server-impl.ts @@ -72,7 +72,7 @@ import { BlockedRepository } from "@gitpod/gitpod-protocol/lib/blocked-repositor import { EligibilityService } from "../user/eligibility-service"; import { AccountStatementProvider } from "../user/account-statement-provider"; import { GithubUpgradeURL, PlanCoupon } from "@gitpod/gitpod-protocol/lib/payment-protocol"; -import { BillableSession } from "@gitpod/gitpod-protocol/lib/usage"; +import { BillableSession, BillableSessionRequest, SortOrder } from "@gitpod/gitpod-protocol/lib/usage"; import { AssigneeIdentityIdentifier, TeamSubscription, @@ -2080,7 +2080,13 @@ export class GitpodServerEEImpl extends GitpodServerImpl { const result = await super.getNotifications(ctx); const user = this.checkAndBlockUser("getNotifications"); if (user.usageAttributionId) { - const allSessions = await this.listBilledUsage(ctx, user.usageAttributionId); + // This change doesn't matter much because the listBilledUsage() call + // will be removed anyway in https://github.com/gitpod-io/gitpod/issues/11692 + const request = { + attributionId: user.usageAttributionId, + startedTimeOrder: SortOrder.Descending, + }; + const allSessions = await this.listBilledUsage(ctx, request); const totalUsage = allSessions.map((s) => s.credits).reduce((a, b) => a + b, 0); const costCenter = await this.costCenterDB.findById(user.usageAttributionId); if (costCenter) { @@ -2096,12 +2102,8 @@ export class GitpodServerEEImpl extends GitpodServerImpl { return result; } - async listBilledUsage( - ctx: TraceContext, - attributionId: string, - from?: number, - to?: number, - ): Promise { + async listBilledUsage(ctx: TraceContext, req: BillableSessionRequest): Promise { + const { attributionId, startedTimeOrder, from, to } = req; traceAPIParams(ctx, { attributionId }); let timestampFrom; let timestampTo; @@ -2116,7 +2118,13 @@ export class GitpodServerEEImpl extends GitpodServerImpl { timestampTo = Timestamp.fromDate(new Date(to)); } const usageClient = this.usageServiceClientProvider.getDefault(); - const response = await usageClient.listBilledUsage(ctx, attributionId, timestampFrom, timestampTo); + const response = await usageClient.listBilledUsage( + ctx, + attributionId, + startedTimeOrder as number, + timestampFrom, + timestampTo, + ); const sessions = response.getSessionsList().map((s) => this.mapBilledSession(s)); return sessions; diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index 04822579a69804..4af08820eecd04 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -169,7 +169,7 @@ import { LicenseEvaluator } from "@gitpod/licensor/lib"; import { Feature } from "@gitpod/licensor/lib/api"; import { Currency } from "@gitpod/gitpod-protocol/lib/plans"; import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server"; -import { BillableSession } from "@gitpod/gitpod-protocol/lib/usage"; +import { BillableSession, BillableSessionRequest } from "@gitpod/gitpod-protocol/lib/usage"; import { WorkspaceClusterImagebuilderClientProvider } from "./workspace-cluster-imagebuilder-client-provider"; // shortcut @@ -3207,12 +3207,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`); } - async listBilledUsage( - ctx: TraceContext, - attributionId: string, - from?: number, - to?: number, - ): Promise { + async listBilledUsage(ctx: TraceContext, req: BillableSessionRequest): Promise { throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`); } async getSpendingLimitForTeam(ctx: TraceContext, teamId: string): Promise { diff --git a/components/usage-api/typescript/src/usage/v1/sugar.ts b/components/usage-api/typescript/src/usage/v1/sugar.ts index 3e897c136d8864..8be8301b8848f7 100644 --- a/components/usage-api/typescript/src/usage/v1/sugar.ts +++ b/components/usage-api/typescript/src/usage/v1/sugar.ts @@ -91,7 +91,7 @@ export class PromisifiedUsageServiceClient { ); } - public async listBilledUsage(_ctx: TraceContext, attributionId: string, from?: Timestamp, to?: Timestamp): Promise { + public async listBilledUsage(_ctx: TraceContext, attributionId: string, order: ListBilledUsageRequest.Ordering, from?: Timestamp, to?: Timestamp): Promise { const ctx = TraceContext.childContext(`/usage-service/listBilledUsage`, _ctx); try { @@ -99,6 +99,7 @@ export class PromisifiedUsageServiceClient { req.setAttributionId(attributionId); req.setFrom(from); req.setTo(to); + req.setOrder(order); const response = await new Promise((resolve, reject) => { this.client.listBilledUsage(