diff --git a/components/dashboard/src/Pagination/Pagination.tsx b/components/dashboard/src/Pagination/Pagination.tsx
index ed0659f1a9abb6..b9a86d891cf083 100644
--- a/components/dashboard/src/Pagination/Pagination.tsx
+++ b/components/dashboard/src/Pagination/Pagination.tsx
@@ -8,20 +8,19 @@ import { getPaginationNumbers } from "./getPagination";
import Arrow from "../components/Arrow";
interface PaginationProps {
- totalResults: number;
totalNumberOfPages: number;
currentPage: number;
- setCurrentPage: any;
+ setPage: (page: number) => void;
}
-function Pagination({ totalNumberOfPages, currentPage, setCurrentPage }: PaginationProps) {
+function Pagination({ totalNumberOfPages, currentPage, setPage }: PaginationProps) {
const calculatedPagination = getPaginationNumbers(totalNumberOfPages, currentPage);
const nextPage = () => {
- if (currentPage !== totalNumberOfPages) setCurrentPage(currentPage + 1);
+ if (currentPage !== totalNumberOfPages) setPage(currentPage + 1);
};
const prevPage = () => {
- if (currentPage !== 1) setCurrentPage(currentPage - 1);
+ if (currentPage !== 1) setPage(currentPage - 1);
};
const getClassnames = (pageNumber: string | number) => {
if (pageNumber === currentPage) {
@@ -47,8 +46,8 @@ function Pagination({ totalNumberOfPages, currentPage, setCurrentPage }: Paginat
return
…;
}
return (
-
- setCurrentPage(pn)}>{pn}
+ typeof pn === "number" && setPage(pn)}>
+ {pn}
);
})}
diff --git a/components/dashboard/src/teams/TeamUsage.tsx b/components/dashboard/src/teams/TeamUsage.tsx
index 05429d90c3981e..0ab9c1367ac470 100644
--- a/components/dashboard/src/teams/TeamUsage.tsx
+++ b/components/dashboard/src/teams/TeamUsage.tsx
@@ -9,10 +9,10 @@ import { useLocation } from "react-router";
import { getCurrentTeam, TeamsContext } from "./teams-context";
import { getGitpodService, gitpodHostUrl } from "../service/service";
import {
- BillableSessionRequest,
+ ListBilledUsageRequest,
BillableWorkspaceType,
ExtendedBillableSession,
- SortOrder,
+ ListBilledUsageResponse,
} from "@gitpod/gitpod-protocol/lib/usage";
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
import { Item, ItemField, ItemsList } from "../components/ItemsList";
@@ -21,7 +21,6 @@ import Header from "../components/Header";
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import { ReactComponent as CreditsSvg } from "../images/credits.svg";
import { ReactComponent as Spinner } from "../icons/Spinner.svg";
-import { ReactComponent as SortArrow } from "../images/sort-arrow.svg";
import { ReactComponent as UsageIcon } from "../images/usage-default.svg";
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
import { toRemoteURL } from "../projects/render-utils";
@@ -31,17 +30,15 @@ function TeamUsage() {
const location = useLocation();
const team = getCurrentTeam(location, teams);
const [teamBillingMode, setTeamBillingMode] = useState(undefined);
- const [billedUsage, setBilledUsage] = useState([]);
- const [currentPage, setCurrentPage] = useState(1);
- const [resultsPerPage] = useState(50);
+ const [usagePage, setUsagePage] = useState(undefined);
const [errorMessage, setErrorMessage] = useState("");
const today = new Date();
const startOfCurrentMonth = new Date(today.getFullYear(), today.getMonth(), 1);
const timestampStartOfCurrentMonth = startOfCurrentMonth.getTime();
const [startDateOfBillMonth, setStartDateOfBillMonth] = useState(timestampStartOfCurrentMonth);
const [endDateOfBillMonth, setEndDateOfBillMonth] = useState(Date.now());
+ const [totalCreditsUsed, setTotalCreditsUsed] = useState(0);
const [isLoading, setIsLoading] = useState(true);
- const [isStartedTimeDescending, setIsStartedTimeDescending] = useState(true);
useEffect(() => {
if (!team) {
@@ -57,30 +54,8 @@ function TeamUsage() {
if (!team) {
return;
}
- if (billedUsage.length === 0) {
- setIsLoading(true);
- }
- (async () => {
- const attributionId = AttributionId.render({ kind: "team", teamId: team.id });
- const request: BillableSessionRequest = {
- attributionId,
- startedTimeOrder: isStartedTimeDescending ? SortOrder.Descending : SortOrder.Ascending,
- from: startDateOfBillMonth,
- to: endDateOfBillMonth,
- };
- try {
- const { server } = getGitpodService();
- const billedUsageResult = await server.listBilledUsage(request);
- setBilledUsage(billedUsageResult);
- } catch (error) {
- if (error.code === ErrorCodes.PERMISSION_DENIED) {
- setErrorMessage("Access to usage details is restricted to team owners.");
- }
- } finally {
- setIsLoading(false);
- }
- })();
- }, [team, startDateOfBillMonth, endDateOfBillMonth, isStartedTimeDescending]);
+ loadPage(1);
+ }, [team, startDateOfBillMonth, endDateOfBillMonth]);
useEffect(() => {
if (!teamBillingMode) {
@@ -91,6 +66,37 @@ function TeamUsage() {
}
}, [teamBillingMode]);
+ const loadPage = async (page: number = 1) => {
+ if (!team) {
+ return;
+ }
+ if (usagePage === undefined) {
+ setIsLoading(true);
+ setTotalCreditsUsed(0);
+ }
+ const attributionId = AttributionId.render({ kind: "team", teamId: team.id });
+ const request: ListBilledUsageRequest = {
+ attributionId,
+ fromDate: startDateOfBillMonth,
+ toDate: endDateOfBillMonth,
+ perPage: 50,
+ page,
+ };
+ try {
+ const page = await getGitpodService().server.listBilledUsage(request);
+ setUsagePage(page);
+ setTotalCreditsUsed(Math.ceil(page.totalCreditsUsed));
+ } catch (error) {
+ if (error.code === ErrorCodes.PERMISSION_DENIED) {
+ setErrorMessage("Access to usage details is restricted to team owners.");
+ } else {
+ setErrorMessage(`Error: ${error?.message}`);
+ }
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
const getType = (type: BillableWorkspaceType) => {
if (type === "regular") {
return "Workspace";
@@ -111,12 +117,6 @@ function TeamUsage() {
return inMinutes + " min";
};
- const calculateTotalUsage = () => {
- let totalCredits = 0;
- billedUsage.forEach((session) => (totalCredits += session.credits));
- return totalCredits.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
- };
-
const handleMonthClick = (start: any, end: any) => {
setStartDateOfBillMonth(start);
setEndDateOfBillMonth(end);
@@ -155,10 +155,7 @@ function TeamUsage() {
return new Date(time).toLocaleDateString(undefined, options).replace("at ", "");
};
- const lastResultOnCurrentPage = currentPage * resultsPerPage;
- const firstResultOnCurrentPage = lastResultOnCurrentPage - resultsPerPage;
- const totalNumberOfPages = Math.ceil(billedUsage.length / resultsPerPage);
- const currentPaginatedResults = billedUsage.slice(firstResultOnCurrentPage, lastResultOnCurrentPage);
+ const currentPaginatedResults = usagePage?.sessions ?? [];
return (
<>
@@ -181,16 +178,18 @@ function TeamUsage() {
Previous Months
{getBillingHistory()}
-
-
Total usage
-
-
-
{calculateTotalUsage()} Credits
+ {!isLoading && (
+
+
Total usage
+
+
+ {totalCreditsUsed} Credits
+
-
+ )}
- {!isLoading && billedUsage.length === 0 && !errorMessage && (
+ {!isLoading && usagePage === undefined && !errorMessage && (
No sessions found.
@@ -215,7 +214,7 @@ function TeamUsage() {
)}
- {billedUsage.length > 0 && !isLoading && (
+ {!isLoading && currentPaginatedResults.length > 0 && (
-
- setIsStartedTimeDescending(!isStartedTimeDescending)}
- >
- Timestamp
-
-
+ Timestamp
{currentPaginatedResults &&
@@ -310,12 +299,11 @@ function TeamUsage() {
);
})}
- {billedUsage.length > resultsPerPage && (
+ {usagePage && usagePage.totalPages > 1 && (
loadPage(page)}
+ totalNumberOfPages={usagePage.totalPages}
/>
)}
diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts
index b5535b54b5cdae..f46c49b99b23f7 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, BillableSessionRequest } from "./usage";
+import { ListBilledUsageResponse, ListBilledUsageRequest } from "./usage";
import { SupportedWorkspaceClass } from "./workspace-class";
import { BillingMode } from "./billing-mode";
@@ -297,7 +297,7 @@ export interface GitpodServer extends JsonRpcServer, AdminServer,
getSpendingLimitForTeam(teamId: string): Promise;
setSpendingLimitForTeam(teamId: string, spendingLimit: number): Promise;
- listBilledUsage(req: BillableSessionRequest): Promise;
+ listBilledUsage(req: ListBilledUsageRequest): Promise;
setUsageAttribution(usageAttribution: string): Promise;
diff --git a/components/gitpod-protocol/src/usage.ts b/components/gitpod-protocol/src/usage.ts
index 78d69a87035e6f..079b5aea0e913b 100644
--- a/components/gitpod-protocol/src/usage.ts
+++ b/components/gitpod-protocol/src/usage.ts
@@ -40,16 +40,24 @@ export interface ExtendedBillableSession extends BillableSession {
user?: Pick;
}
-export interface BillableSessionRequest {
+/**
+ * This is a paginated request
+ */
+export interface ListBilledUsageRequest {
attributionId: string;
- startedTimeOrder: SortOrder;
- from?: number;
- to?: number;
+ fromDate?: number;
+ toDate?: number;
+ perPage: number;
+ page: number;
}
-export type BillableWorkspaceType = WorkspaceType;
-
-export enum SortOrder {
- Descending = 0,
- Ascending = 1,
+export interface ListBilledUsageResponse {
+ sessions: ExtendedBillableSession[];
+ totalCreditsUsed: number;
+ totalPages: number;
+ totalSessions: number;
+ perPage: number;
+ page: number;
}
+
+export type BillableWorkspaceType = WorkspaceType;
diff --git a/components/server/ee/src/workspace/gitpod-server-impl.ts b/components/server/ee/src/workspace/gitpod-server-impl.ts
index 301bb7ba413899..53c56af4b53018 100644
--- a/components/server/ee/src/workspace/gitpod-server-impl.ts
+++ b/components/server/ee/src/workspace/gitpod-server-impl.ts
@@ -71,7 +71,8 @@ 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 { ExtendedBillableSession, BillableSessionRequest } from "@gitpod/gitpod-protocol/lib/usage";
+import { ListBilledUsageRequest, ListBilledUsageResponse } from "@gitpod/gitpod-protocol/lib/usage";
+import { ListBilledUsageRequest as ListBilledUsage } from "@gitpod/usage-api/lib/usage/v1/usage_pb";
import {
AssigneeIdentityIdentifier,
TeamSubscription,
@@ -2149,8 +2150,8 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
return result;
}
- async listBilledUsage(ctx: TraceContext, req: BillableSessionRequest): Promise {
- const { attributionId, startedTimeOrder, from, to } = req;
+ async listBilledUsage(ctx: TraceContext, req: ListBilledUsageRequest): Promise {
+ const { attributionId, fromDate, toDate, perPage, page } = req;
traceAPIParams(ctx, { attributionId });
let timestampFrom;
let timestampTo;
@@ -2158,39 +2159,52 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
await this.guardCostCenterAccess(ctx, user.id, attributionId, "get");
- if (from) {
- timestampFrom = Timestamp.fromDate(new Date(from));
+ if (fromDate) {
+ timestampFrom = Timestamp.fromDate(new Date(fromDate));
}
- if (to) {
- timestampTo = Timestamp.fromDate(new Date(to));
+ if (toDate) {
+ timestampTo = Timestamp.fromDate(new Date(toDate));
}
const usageClient = this.usageServiceClientProvider.getDefault();
const response = await usageClient.listBilledUsage(
ctx,
attributionId,
- startedTimeOrder as number,
+ ListBilledUsage.Ordering.ORDERING_DESCENDING,
+ perPage,
+ page,
timestampFrom,
timestampTo,
);
- const sessions = response.getSessionsList().map((s) => UsageService.mapBilledSession(s));
- const extendedSessions = await Promise.all(
- sessions.map(async (session) => {
- const ws = await this.workspaceDb.trace(ctx).findWorkspaceAndInstance(session.workspaceId);
- let profile: User.Profile | undefined = undefined;
- if (session.workspaceType === "regular" && session.userId) {
- const user = await this.userDB.findUserById(session.userId);
- if (user) {
- profile = User.getProfile(user);
+ const sessions = await Promise.all(
+ response
+ .getSessionsList()
+ .map((s) => UsageService.mapBilledSession(s))
+ .map(async (session) => {
+ const ws = await this.workspaceDb.trace(ctx).findWorkspaceAndInstance(session.workspaceId);
+ let profile: User.Profile | undefined = undefined;
+ if (session.workspaceType === "regular" && session.userId) {
+ // TODO add caching to void repeated loading of same profile details here
+ const user = await this.userDB.findUserById(session.userId);
+ if (user) {
+ profile = User.getProfile(user);
+ }
}
- }
- return {
- ...session,
- contextURL: ws?.contextURL,
- user: profile ? { name: profile.name, avatarURL: profile.avatarURL } : undefined,
- };
- }),
+ return {
+ ...session,
+ contextURL: ws?.contextURL,
+ user: profile,
+ };
+ }),
);
- return extendedSessions;
+ const pagination = response.getPagination();
+ return {
+ sessions,
+ totalSessions: pagination?.getTotal() || 0,
+ totalPages: pagination?.getTotalPages() || 0,
+ page: pagination?.getPage() || 0,
+ perPage: pagination?.getPerPage() || 0,
+ totalCreditsUsed: response.getTotalCreditsUsed(),
+ };
}
protected async guardCostCenterAccess(
diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts
index 5564a540582d5d..462106eddb109d 100644
--- a/components/server/src/workspace/gitpod-server-impl.ts
+++ b/components/server/src/workspace/gitpod-server-impl.ts
@@ -173,7 +173,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, BillableSessionRequest } from "@gitpod/gitpod-protocol/lib/usage";
+import { ListBilledUsageRequest, ListBilledUsageResponse } from "@gitpod/gitpod-protocol/lib/usage";
import { WorkspaceClusterImagebuilderClientProvider } from "./workspace-cluster-imagebuilder-client-provider";
import { VerificationService } from "../auth/verification-service";
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
@@ -3227,7 +3227,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
}
- async listBilledUsage(ctx: TraceContext, req: BillableSessionRequest): Promise {
+ async listBilledUsage(ctx: TraceContext, req: ListBilledUsageRequest): Promise {
throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`);
}
diff --git a/components/usage-api/go/v1/usage.pb.go b/components/usage-api/go/v1/usage.pb.go
index 8b4b640bf7d4ef..4d784645719c9e 100644
--- a/components/usage-api/go/v1/usage.pb.go
+++ b/components/usage-api/go/v1/usage.pb.go
@@ -82,8 +82,9 @@ type ListBilledUsageRequest struct {
From *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=from,proto3" json:"from,omitempty"`
// to specifies the end time range for this request.
// All instances which existed ending at to will be returned.
- To *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=to,proto3" json:"to,omitempty"`
- Order ListBilledUsageRequest_Ordering `protobuf:"varint,4,opt,name=order,proto3,enum=usage.v1.ListBilledUsageRequest_Ordering" json:"order,omitempty"`
+ To *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=to,proto3" json:"to,omitempty"`
+ Order ListBilledUsageRequest_Ordering `protobuf:"varint,4,opt,name=order,proto3,enum=usage.v1.ListBilledUsageRequest_Ordering" json:"order,omitempty"`
+ Pagination *PaginatedRequest `protobuf:"bytes,5,opt,name=pagination,proto3" json:"pagination,omitempty"`
}
func (x *ListBilledUsageRequest) Reset() {
@@ -146,18 +147,82 @@ func (x *ListBilledUsageRequest) GetOrder() ListBilledUsageRequest_Ordering {
return ListBilledUsageRequest_ORDERING_DESCENDING
}
+func (x *ListBilledUsageRequest) GetPagination() *PaginatedRequest {
+ if x != nil {
+ return x.Pagination
+ }
+ return nil
+}
+
+type PaginatedRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ PerPage int64 `protobuf:"varint,1,opt,name=per_page,json=perPage,proto3" json:"per_page,omitempty"`
+ Page int64 `protobuf:"varint,2,opt,name=page,proto3" json:"page,omitempty"`
+}
+
+func (x *PaginatedRequest) Reset() {
+ *x = PaginatedRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_usage_v1_usage_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *PaginatedRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PaginatedRequest) ProtoMessage() {}
+
+func (x *PaginatedRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_usage_v1_usage_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use PaginatedRequest.ProtoReflect.Descriptor instead.
+func (*PaginatedRequest) Descriptor() ([]byte, []int) {
+ return file_usage_v1_usage_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *PaginatedRequest) GetPerPage() int64 {
+ if x != nil {
+ return x.PerPage
+ }
+ return 0
+}
+
+func (x *PaginatedRequest) GetPage() int64 {
+ if x != nil {
+ return x.Page
+ }
+ return 0
+}
+
type ListBilledUsageResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- Sessions []*BilledSession `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"`
+ Sessions []*BilledSession `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"`
+ TotalCreditsUsed float64 `protobuf:"fixed64,2,opt,name=total_credits_used,json=totalCreditsUsed,proto3" json:"total_credits_used,omitempty"`
+ Pagination *PaginatedResponse `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"`
}
func (x *ListBilledUsageResponse) Reset() {
*x = ListBilledUsageResponse{}
if protoimpl.UnsafeEnabled {
- mi := &file_usage_v1_usage_proto_msgTypes[1]
+ mi := &file_usage_v1_usage_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -170,7 +235,7 @@ func (x *ListBilledUsageResponse) String() string {
func (*ListBilledUsageResponse) ProtoMessage() {}
func (x *ListBilledUsageResponse) ProtoReflect() protoreflect.Message {
- mi := &file_usage_v1_usage_proto_msgTypes[1]
+ mi := &file_usage_v1_usage_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -183,7 +248,7 @@ func (x *ListBilledUsageResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListBilledUsageResponse.ProtoReflect.Descriptor instead.
func (*ListBilledUsageResponse) Descriptor() ([]byte, []int) {
- return file_usage_v1_usage_proto_rawDescGZIP(), []int{1}
+ return file_usage_v1_usage_proto_rawDescGZIP(), []int{2}
}
func (x *ListBilledUsageResponse) GetSessions() []*BilledSession {
@@ -193,6 +258,91 @@ func (x *ListBilledUsageResponse) GetSessions() []*BilledSession {
return nil
}
+func (x *ListBilledUsageResponse) GetTotalCreditsUsed() float64 {
+ if x != nil {
+ return x.TotalCreditsUsed
+ }
+ return 0
+}
+
+func (x *ListBilledUsageResponse) GetPagination() *PaginatedResponse {
+ if x != nil {
+ return x.Pagination
+ }
+ return nil
+}
+
+type PaginatedResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ PerPage int64 `protobuf:"varint,2,opt,name=per_page,json=perPage,proto3" json:"per_page,omitempty"`
+ TotalPages int64 `protobuf:"varint,3,opt,name=total_pages,json=totalPages,proto3" json:"total_pages,omitempty"`
+ Total int64 `protobuf:"varint,4,opt,name=total,proto3" json:"total,omitempty"`
+ Page int64 `protobuf:"varint,5,opt,name=page,proto3" json:"page,omitempty"`
+}
+
+func (x *PaginatedResponse) Reset() {
+ *x = PaginatedResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_usage_v1_usage_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *PaginatedResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PaginatedResponse) ProtoMessage() {}
+
+func (x *PaginatedResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_usage_v1_usage_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use PaginatedResponse.ProtoReflect.Descriptor instead.
+func (*PaginatedResponse) Descriptor() ([]byte, []int) {
+ return file_usage_v1_usage_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *PaginatedResponse) GetPerPage() int64 {
+ if x != nil {
+ return x.PerPage
+ }
+ return 0
+}
+
+func (x *PaginatedResponse) GetTotalPages() int64 {
+ if x != nil {
+ return x.TotalPages
+ }
+ return 0
+}
+
+func (x *PaginatedResponse) GetTotal() int64 {
+ if x != nil {
+ return x.Total
+ }
+ return 0
+}
+
+func (x *PaginatedResponse) GetPage() int64 {
+ if x != nil {
+ return x.Page
+ }
+ return 0
+}
+
type BilledSession struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -216,7 +366,7 @@ type BilledSession struct {
func (x *BilledSession) Reset() {
*x = BilledSession{}
if protoimpl.UnsafeEnabled {
- mi := &file_usage_v1_usage_proto_msgTypes[2]
+ mi := &file_usage_v1_usage_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -229,7 +379,7 @@ func (x *BilledSession) String() string {
func (*BilledSession) ProtoMessage() {}
func (x *BilledSession) ProtoReflect() protoreflect.Message {
- mi := &file_usage_v1_usage_proto_msgTypes[2]
+ mi := &file_usage_v1_usage_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -242,7 +392,7 @@ func (x *BilledSession) ProtoReflect() protoreflect.Message {
// Deprecated: Use BilledSession.ProtoReflect.Descriptor instead.
func (*BilledSession) Descriptor() ([]byte, []int) {
- return file_usage_v1_usage_proto_rawDescGZIP(), []int{2}
+ return file_usage_v1_usage_proto_rawDescGZIP(), []int{4}
}
func (x *BilledSession) GetAttributionId() string {
@@ -342,7 +492,7 @@ type ReconcileUsageRequest struct {
func (x *ReconcileUsageRequest) Reset() {
*x = ReconcileUsageRequest{}
if protoimpl.UnsafeEnabled {
- mi := &file_usage_v1_usage_proto_msgTypes[3]
+ mi := &file_usage_v1_usage_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -355,7 +505,7 @@ func (x *ReconcileUsageRequest) String() string {
func (*ReconcileUsageRequest) ProtoMessage() {}
func (x *ReconcileUsageRequest) ProtoReflect() protoreflect.Message {
- mi := &file_usage_v1_usage_proto_msgTypes[3]
+ mi := &file_usage_v1_usage_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -368,7 +518,7 @@ func (x *ReconcileUsageRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ReconcileUsageRequest.ProtoReflect.Descriptor instead.
func (*ReconcileUsageRequest) Descriptor() ([]byte, []int) {
- return file_usage_v1_usage_proto_rawDescGZIP(), []int{3}
+ return file_usage_v1_usage_proto_rawDescGZIP(), []int{5}
}
func (x *ReconcileUsageRequest) GetStartTime() *timestamppb.Timestamp {
@@ -399,7 +549,7 @@ type ReconcileUsageResponse struct {
func (x *ReconcileUsageResponse) Reset() {
*x = ReconcileUsageResponse{}
if protoimpl.UnsafeEnabled {
- mi := &file_usage_v1_usage_proto_msgTypes[4]
+ mi := &file_usage_v1_usage_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -412,7 +562,7 @@ func (x *ReconcileUsageResponse) String() string {
func (*ReconcileUsageResponse) ProtoMessage() {}
func (x *ReconcileUsageResponse) ProtoReflect() protoreflect.Message {
- mi := &file_usage_v1_usage_proto_msgTypes[4]
+ mi := &file_usage_v1_usage_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -425,7 +575,7 @@ func (x *ReconcileUsageResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ReconcileUsageResponse.ProtoReflect.Descriptor instead.
func (*ReconcileUsageResponse) Descriptor() ([]byte, []int) {
- return file_usage_v1_usage_proto_rawDescGZIP(), []int{4}
+ return file_usage_v1_usage_proto_rawDescGZIP(), []int{6}
}
// Deprecated: Do not use.
@@ -454,7 +604,7 @@ type GetCostCenterRequest struct {
func (x *GetCostCenterRequest) Reset() {
*x = GetCostCenterRequest{}
if protoimpl.UnsafeEnabled {
- mi := &file_usage_v1_usage_proto_msgTypes[5]
+ mi := &file_usage_v1_usage_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -467,7 +617,7 @@ func (x *GetCostCenterRequest) String() string {
func (*GetCostCenterRequest) ProtoMessage() {}
func (x *GetCostCenterRequest) ProtoReflect() protoreflect.Message {
- mi := &file_usage_v1_usage_proto_msgTypes[5]
+ mi := &file_usage_v1_usage_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -480,7 +630,7 @@ func (x *GetCostCenterRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetCostCenterRequest.ProtoReflect.Descriptor instead.
func (*GetCostCenterRequest) Descriptor() ([]byte, []int) {
- return file_usage_v1_usage_proto_rawDescGZIP(), []int{5}
+ return file_usage_v1_usage_proto_rawDescGZIP(), []int{7}
}
func (x *GetCostCenterRequest) GetAttributionId() string {
@@ -501,7 +651,7 @@ type GetCostCenterResponse struct {
func (x *GetCostCenterResponse) Reset() {
*x = GetCostCenterResponse{}
if protoimpl.UnsafeEnabled {
- mi := &file_usage_v1_usage_proto_msgTypes[6]
+ mi := &file_usage_v1_usage_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -514,7 +664,7 @@ func (x *GetCostCenterResponse) String() string {
func (*GetCostCenterResponse) ProtoMessage() {}
func (x *GetCostCenterResponse) ProtoReflect() protoreflect.Message {
- mi := &file_usage_v1_usage_proto_msgTypes[6]
+ mi := &file_usage_v1_usage_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -527,7 +677,7 @@ func (x *GetCostCenterResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetCostCenterResponse.ProtoReflect.Descriptor instead.
func (*GetCostCenterResponse) Descriptor() ([]byte, []int) {
- return file_usage_v1_usage_proto_rawDescGZIP(), []int{6}
+ return file_usage_v1_usage_proto_rawDescGZIP(), []int{8}
}
func (x *GetCostCenterResponse) GetCostCenter() *CostCenter {
@@ -549,7 +699,7 @@ type CostCenter struct {
func (x *CostCenter) Reset() {
*x = CostCenter{}
if protoimpl.UnsafeEnabled {
- mi := &file_usage_v1_usage_proto_msgTypes[7]
+ mi := &file_usage_v1_usage_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -562,7 +712,7 @@ func (x *CostCenter) String() string {
func (*CostCenter) ProtoMessage() {}
func (x *CostCenter) ProtoReflect() protoreflect.Message {
- mi := &file_usage_v1_usage_proto_msgTypes[7]
+ mi := &file_usage_v1_usage_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -575,7 +725,7 @@ func (x *CostCenter) ProtoReflect() protoreflect.Message {
// Deprecated: Use CostCenter.ProtoReflect.Descriptor instead.
func (*CostCenter) Descriptor() ([]byte, []int) {
- return file_usage_v1_usage_proto_rawDescGZIP(), []int{7}
+ return file_usage_v1_usage_proto_rawDescGZIP(), []int{9}
}
func (x *CostCenter) GetAttributionId() string {
@@ -599,7 +749,7 @@ var file_usage_v1_usage_proto_rawDesc = []byte{
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31,
0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74,
- 0x6f, 0x22, 0x99, 0x02, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64,
+ 0x6f, 0x22, 0xd5, 0x02, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64,
0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e,
0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f,
@@ -613,96 +763,119 @@ var file_usage_v1_usage_proto_rawDesc = []byte{
0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x69,
0x6c, 0x6c, 0x65, 0x64, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72,
- 0x22, 0x3b, 0x0a, 0x08, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x13,
- 0x4f, 0x52, 0x44, 0x45, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x44, 0x45, 0x53, 0x43, 0x45, 0x4e, 0x44,
- 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x49, 0x4e,
- 0x47, 0x5f, 0x41, 0x53, 0x43, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x22, 0x4e, 0x0a,
+ 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05,
+ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e,
+ 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+ 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3b, 0x0a, 0x08,
+ 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x13, 0x4f, 0x52, 0x44, 0x45,
+ 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x44, 0x45, 0x53, 0x43, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10,
+ 0x00, 0x12, 0x16, 0x0a, 0x12, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x41, 0x53,
+ 0x43, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x22, 0x41, 0x0a, 0x10, 0x50, 0x61, 0x67,
+ 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a,
+ 0x08, 0x70, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
+ 0x07, 0x70, 0x65, 0x72, 0x50, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x22, 0xb9, 0x01, 0x0a,
0x17, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x55, 0x73, 0x61, 0x67, 0x65,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x75, 0x73, 0x61,
0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73,
- 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xda, 0x03,
- 0x0a, 0x0d, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12,
- 0x25, 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69,
- 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
- 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
- 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12,
- 0x17, 0x0a, 0x07, 0x74, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
- 0x52, 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b,
- 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
- 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x77,
- 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x79,
- 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64,
- 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49,
- 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64,
- 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
- 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f,
- 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72,
- 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x73,
- 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32,
- 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
- 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61,
- 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69,
- 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
- 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
- 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x31, 0x0a,
- 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x5f, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61,
- 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x11, 0x63,
- 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64,
- 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28,
- 0x01, 0x52, 0x07, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x22, 0x89, 0x01, 0x0a, 0x15, 0x52,
- 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71,
- 0x75, 0x65, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69,
- 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
- 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
- 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12,
- 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
- 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
- 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65,
- 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x6e, 0x0a, 0x16, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63,
- 0x69, 0x6c, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
- 0x12, 0x37, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03,
- 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x69,
- 0x6c, 0x6c, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x02, 0x18, 0x01, 0x52,
- 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70,
- 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65,
- 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x22, 0x3d, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x73,
- 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25,
- 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64,
- 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
- 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x4e, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74,
- 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35,
- 0x0a, 0x0b, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20,
- 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43,
- 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x63, 0x6f, 0x73, 0x74, 0x43,
- 0x65, 0x6e, 0x74, 0x65, 0x72, 0x22, 0x5a, 0x0a, 0x0a, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e,
- 0x74, 0x65, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69,
- 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x74, 0x74,
- 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x70,
- 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01,
- 0x28, 0x05, 0x52, 0x0d, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x6d, 0x69,
- 0x74, 0x32, 0x93, 0x02, 0x0a, 0x0c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69,
- 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64,
- 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x20, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31,
- 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x55, 0x73, 0x61, 0x67, 0x65,
- 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e,
- 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x55, 0x73, 0x61,
- 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x55, 0x0a, 0x0e,
- 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f,
- 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63,
- 0x69, 0x6c, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
- 0x20, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x6e,
- 0x63, 0x69, 0x6c, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
- 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65,
- 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e,
+ 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2c, 0x0a,
+ 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x5f, 0x75,
+ 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c,
+ 0x43, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x3b, 0x0a, 0x0a, 0x70,
+ 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
+ 0x1b, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x67, 0x69, 0x6e,
+ 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61,
+ 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x79, 0x0a, 0x11, 0x50, 0x61, 0x67, 0x69,
+ 0x6e, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a,
+ 0x08, 0x70, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
+ 0x07, 0x70, 0x65, 0x72, 0x50, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61,
+ 0x6c, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74,
+ 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x61, 0x67, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74,
+ 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12,
+ 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x70,
+ 0x61, 0x67, 0x65, 0x22, 0xda, 0x03, 0x0a, 0x0d, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x53, 0x65,
+ 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
+ 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61,
+ 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07,
+ 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75,
+ 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64,
+ 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x21,
+ 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49,
+ 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74,
+ 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73,
+ 0x70, 0x61, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a,
+ 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72,
+ 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61,
+ 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e,
+ 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b,
+ 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6c, 0x61, 0x73,
+ 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
+ 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
+ 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x08,
+ 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
+ 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+ 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54,
+ 0x69, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x5f, 0x64,
+ 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x42,
+ 0x02, 0x18, 0x01, 0x52, 0x11, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x44, 0x65, 0x70, 0x72,
+ 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74,
+ 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73,
+ 0x22, 0x89, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55, 0x73,
+ 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74,
+ 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
+ 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+ 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72,
+ 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d,
+ 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+ 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
+ 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x6e, 0x0a, 0x16,
+ 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65,
+ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
+ 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65,
+ 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
+ 0x6e, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12,
+ 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x22, 0x3d, 0x0a, 0x14,
0x47, 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71,
- 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e,
- 0x47, 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73,
- 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75,
- 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, 0x6f, 0x2f,
- 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2d, 0x61, 0x70, 0x69,
- 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
+ 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x74,
+ 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x4e, 0x0a, 0x15, 0x47,
+ 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70,
+ 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x65, 0x6e,
+ 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x75, 0x73, 0x61, 0x67,
+ 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x52,
+ 0x0a, 0x63, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x22, 0x5a, 0x0a, 0x0a, 0x43,
+ 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x74, 0x74,
+ 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64,
+ 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x69, 0x6d,
+ 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69,
+ 0x6e, 0x67, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x32, 0x93, 0x02, 0x0a, 0x0c, 0x55, 0x73, 0x61, 0x67,
+ 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74,
+ 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x20, 0x2e, 0x75, 0x73,
+ 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65,
+ 0x64, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e,
+ 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x69, 0x6c,
+ 0x6c, 0x65, 0x64, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+ 0x22, 0x00, 0x12, 0x55, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55,
+ 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e,
+ 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x75, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x76, 0x31,
+ 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52,
+ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x0d, 0x47, 0x65, 0x74,
+ 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x75, 0x73, 0x61,
+ 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e,
+ 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x75, 0x73, 0x61,
+ 0x67, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x73, 0x74, 0x43, 0x65, 0x6e,
+ 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2a, 0x5a,
+ 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70,
+ 0x6f, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x75, 0x73, 0x61,
+ 0x67, 0x65, 0x2d, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x33,
}
var (
@@ -718,41 +891,45 @@ func file_usage_v1_usage_proto_rawDescGZIP() []byte {
}
var file_usage_v1_usage_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_usage_v1_usage_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
+var file_usage_v1_usage_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_usage_v1_usage_proto_goTypes = []interface{}{
(ListBilledUsageRequest_Ordering)(0), // 0: usage.v1.ListBilledUsageRequest.Ordering
(*ListBilledUsageRequest)(nil), // 1: usage.v1.ListBilledUsageRequest
- (*ListBilledUsageResponse)(nil), // 2: usage.v1.ListBilledUsageResponse
- (*BilledSession)(nil), // 3: usage.v1.BilledSession
- (*ReconcileUsageRequest)(nil), // 4: usage.v1.ReconcileUsageRequest
- (*ReconcileUsageResponse)(nil), // 5: usage.v1.ReconcileUsageResponse
- (*GetCostCenterRequest)(nil), // 6: usage.v1.GetCostCenterRequest
- (*GetCostCenterResponse)(nil), // 7: usage.v1.GetCostCenterResponse
- (*CostCenter)(nil), // 8: usage.v1.CostCenter
- (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp
+ (*PaginatedRequest)(nil), // 2: usage.v1.PaginatedRequest
+ (*ListBilledUsageResponse)(nil), // 3: usage.v1.ListBilledUsageResponse
+ (*PaginatedResponse)(nil), // 4: usage.v1.PaginatedResponse
+ (*BilledSession)(nil), // 5: usage.v1.BilledSession
+ (*ReconcileUsageRequest)(nil), // 6: usage.v1.ReconcileUsageRequest
+ (*ReconcileUsageResponse)(nil), // 7: usage.v1.ReconcileUsageResponse
+ (*GetCostCenterRequest)(nil), // 8: usage.v1.GetCostCenterRequest
+ (*GetCostCenterResponse)(nil), // 9: usage.v1.GetCostCenterResponse
+ (*CostCenter)(nil), // 10: usage.v1.CostCenter
+ (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp
}
var file_usage_v1_usage_proto_depIdxs = []int32{
- 9, // 0: usage.v1.ListBilledUsageRequest.from:type_name -> google.protobuf.Timestamp
- 9, // 1: usage.v1.ListBilledUsageRequest.to:type_name -> google.protobuf.Timestamp
+ 11, // 0: usage.v1.ListBilledUsageRequest.from:type_name -> google.protobuf.Timestamp
+ 11, // 1: usage.v1.ListBilledUsageRequest.to:type_name -> google.protobuf.Timestamp
0, // 2: usage.v1.ListBilledUsageRequest.order:type_name -> usage.v1.ListBilledUsageRequest.Ordering
- 3, // 3: usage.v1.ListBilledUsageResponse.sessions:type_name -> usage.v1.BilledSession
- 9, // 4: usage.v1.BilledSession.start_time:type_name -> google.protobuf.Timestamp
- 9, // 5: usage.v1.BilledSession.end_time:type_name -> google.protobuf.Timestamp
- 9, // 6: usage.v1.ReconcileUsageRequest.start_time:type_name -> google.protobuf.Timestamp
- 9, // 7: usage.v1.ReconcileUsageRequest.end_time:type_name -> google.protobuf.Timestamp
- 3, // 8: usage.v1.ReconcileUsageResponse.sessions:type_name -> usage.v1.BilledSession
- 8, // 9: usage.v1.GetCostCenterResponse.cost_center:type_name -> usage.v1.CostCenter
- 1, // 10: usage.v1.UsageService.ListBilledUsage:input_type -> usage.v1.ListBilledUsageRequest
- 4, // 11: usage.v1.UsageService.ReconcileUsage:input_type -> usage.v1.ReconcileUsageRequest
- 6, // 12: usage.v1.UsageService.GetCostCenter:input_type -> usage.v1.GetCostCenterRequest
- 2, // 13: usage.v1.UsageService.ListBilledUsage:output_type -> usage.v1.ListBilledUsageResponse
- 5, // 14: usage.v1.UsageService.ReconcileUsage:output_type -> usage.v1.ReconcileUsageResponse
- 7, // 15: usage.v1.UsageService.GetCostCenter:output_type -> usage.v1.GetCostCenterResponse
- 13, // [13:16] is the sub-list for method output_type
- 10, // [10:13] is the sub-list for method input_type
- 10, // [10:10] is the sub-list for extension type_name
- 10, // [10:10] is the sub-list for extension extendee
- 0, // [0:10] is the sub-list for field type_name
+ 2, // 3: usage.v1.ListBilledUsageRequest.pagination:type_name -> usage.v1.PaginatedRequest
+ 5, // 4: usage.v1.ListBilledUsageResponse.sessions:type_name -> usage.v1.BilledSession
+ 4, // 5: usage.v1.ListBilledUsageResponse.pagination:type_name -> usage.v1.PaginatedResponse
+ 11, // 6: usage.v1.BilledSession.start_time:type_name -> google.protobuf.Timestamp
+ 11, // 7: usage.v1.BilledSession.end_time:type_name -> google.protobuf.Timestamp
+ 11, // 8: usage.v1.ReconcileUsageRequest.start_time:type_name -> google.protobuf.Timestamp
+ 11, // 9: usage.v1.ReconcileUsageRequest.end_time:type_name -> google.protobuf.Timestamp
+ 5, // 10: usage.v1.ReconcileUsageResponse.sessions:type_name -> usage.v1.BilledSession
+ 10, // 11: usage.v1.GetCostCenterResponse.cost_center:type_name -> usage.v1.CostCenter
+ 1, // 12: usage.v1.UsageService.ListBilledUsage:input_type -> usage.v1.ListBilledUsageRequest
+ 6, // 13: usage.v1.UsageService.ReconcileUsage:input_type -> usage.v1.ReconcileUsageRequest
+ 8, // 14: usage.v1.UsageService.GetCostCenter:input_type -> usage.v1.GetCostCenterRequest
+ 3, // 15: usage.v1.UsageService.ListBilledUsage:output_type -> usage.v1.ListBilledUsageResponse
+ 7, // 16: usage.v1.UsageService.ReconcileUsage:output_type -> usage.v1.ReconcileUsageResponse
+ 9, // 17: usage.v1.UsageService.GetCostCenter:output_type -> usage.v1.GetCostCenterResponse
+ 15, // [15:18] is the sub-list for method output_type
+ 12, // [12:15] is the sub-list for method input_type
+ 12, // [12:12] is the sub-list for extension type_name
+ 12, // [12:12] is the sub-list for extension extendee
+ 0, // [0:12] is the sub-list for field type_name
}
func init() { file_usage_v1_usage_proto_init() }
@@ -774,7 +951,7 @@ func file_usage_v1_usage_proto_init() {
}
}
file_usage_v1_usage_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*ListBilledUsageResponse); i {
+ switch v := v.(*PaginatedRequest); i {
case 0:
return &v.state
case 1:
@@ -786,7 +963,7 @@ func file_usage_v1_usage_proto_init() {
}
}
file_usage_v1_usage_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*BilledSession); i {
+ switch v := v.(*ListBilledUsageResponse); i {
case 0:
return &v.state
case 1:
@@ -798,7 +975,7 @@ func file_usage_v1_usage_proto_init() {
}
}
file_usage_v1_usage_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*ReconcileUsageRequest); i {
+ switch v := v.(*PaginatedResponse); i {
case 0:
return &v.state
case 1:
@@ -810,7 +987,7 @@ func file_usage_v1_usage_proto_init() {
}
}
file_usage_v1_usage_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*ReconcileUsageResponse); i {
+ switch v := v.(*BilledSession); i {
case 0:
return &v.state
case 1:
@@ -822,7 +999,7 @@ func file_usage_v1_usage_proto_init() {
}
}
file_usage_v1_usage_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*GetCostCenterRequest); i {
+ switch v := v.(*ReconcileUsageRequest); i {
case 0:
return &v.state
case 1:
@@ -834,7 +1011,7 @@ func file_usage_v1_usage_proto_init() {
}
}
file_usage_v1_usage_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*GetCostCenterResponse); i {
+ switch v := v.(*ReconcileUsageResponse); i {
case 0:
return &v.state
case 1:
@@ -846,6 +1023,30 @@ func file_usage_v1_usage_proto_init() {
}
}
file_usage_v1_usage_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GetCostCenterRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_usage_v1_usage_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GetCostCenterResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_usage_v1_usage_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CostCenter); i {
case 0:
return &v.state
@@ -864,7 +1065,7 @@ func file_usage_v1_usage_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_usage_v1_usage_proto_rawDesc,
NumEnums: 1,
- NumMessages: 8,
+ NumMessages: 10,
NumExtensions: 0,
NumServices: 1,
},
diff --git a/components/usage-api/typescript/src/usage/v1/sugar.ts b/components/usage-api/typescript/src/usage/v1/sugar.ts
index d3fc76f280a78b..4baa9216bd6b20 100644
--- a/components/usage-api/typescript/src/usage/v1/sugar.ts
+++ b/components/usage-api/typescript/src/usage/v1/sugar.ts
@@ -9,7 +9,7 @@ import { BillingServiceClient } from "./billing_grpc_pb";
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
import * as opentracing from "opentracing";
import { Metadata } from "@grpc/grpc-js";
-import { BilledSession, ListBilledUsageRequest, ListBilledUsageResponse } from "./usage_pb";
+import { BilledSession, ListBilledUsageRequest, ListBilledUsageResponse, PaginatedRequest } from "./usage_pb";
import {
GetUpcomingInvoiceRequest,
GetUpcomingInvoiceResponse,
@@ -153,14 +153,21 @@ export class PromisifiedUsageServiceClient {
_ctx: TraceContext,
attributionId: string,
order: ListBilledUsageRequest.Ordering,
+ perPage: number,
+ page: number,
from?: Timestamp,
to?: Timestamp,
): Promise {
const ctx = TraceContext.childContext(`/usage-service/listBilledUsage`, _ctx);
try {
+ const pagination = new PaginatedRequest();
+ pagination.setPerPage(perPage);
+ pagination.setPage(page);
+
const req = new ListBilledUsageRequest();
req.setAttributionId(attributionId);
+ req.setPagination(pagination);
req.setFrom(from);
req.setTo(to);
req.setOrder(order);
diff --git a/components/usage-api/typescript/src/usage/v1/usage_pb.d.ts b/components/usage-api/typescript/src/usage/v1/usage_pb.d.ts
index 45aa384db4e75e..28dd34ada8db40 100644
--- a/components/usage-api/typescript/src/usage/v1/usage_pb.d.ts
+++ b/components/usage-api/typescript/src/usage/v1/usage_pb.d.ts
@@ -29,6 +29,11 @@ export class ListBilledUsageRequest extends jspb.Message {
getOrder(): ListBilledUsageRequest.Ordering;
setOrder(value: ListBilledUsageRequest.Ordering): ListBilledUsageRequest;
+ hasPagination(): boolean;
+ clearPagination(): void;
+ getPagination(): PaginatedRequest | undefined;
+ setPagination(value?: PaginatedRequest): ListBilledUsageRequest;
+
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): ListBilledUsageRequest.AsObject;
static toObject(includeInstance: boolean, msg: ListBilledUsageRequest): ListBilledUsageRequest.AsObject;
@@ -45,6 +50,7 @@ export namespace ListBilledUsageRequest {
from?: google_protobuf_timestamp_pb.Timestamp.AsObject,
to?: google_protobuf_timestamp_pb.Timestamp.AsObject,
order: ListBilledUsageRequest.Ordering,
+ pagination?: PaginatedRequest.AsObject,
}
export enum Ordering {
@@ -54,11 +60,41 @@ export namespace ListBilledUsageRequest {
}
+export class PaginatedRequest extends jspb.Message {
+ getPerPage(): number;
+ setPerPage(value: number): PaginatedRequest;
+ getPage(): number;
+ setPage(value: number): PaginatedRequest;
+
+ serializeBinary(): Uint8Array;
+ toObject(includeInstance?: boolean): PaginatedRequest.AsObject;
+ static toObject(includeInstance: boolean, msg: PaginatedRequest): PaginatedRequest.AsObject;
+ static extensions: {[key: number]: jspb.ExtensionFieldInfo};
+ static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo};
+ static serializeBinaryToWriter(message: PaginatedRequest, writer: jspb.BinaryWriter): void;
+ static deserializeBinary(bytes: Uint8Array): PaginatedRequest;
+ static deserializeBinaryFromReader(message: PaginatedRequest, reader: jspb.BinaryReader): PaginatedRequest;
+}
+
+export namespace PaginatedRequest {
+ export type AsObject = {
+ perPage: number,
+ page: number,
+ }
+}
+
export class ListBilledUsageResponse extends jspb.Message {
clearSessionsList(): void;
getSessionsList(): Array;
setSessionsList(value: Array): ListBilledUsageResponse;
addSessions(value?: BilledSession, index?: number): BilledSession;
+ getTotalCreditsUsed(): number;
+ setTotalCreditsUsed(value: number): ListBilledUsageResponse;
+
+ hasPagination(): boolean;
+ clearPagination(): void;
+ getPagination(): PaginatedResponse | undefined;
+ setPagination(value?: PaginatedResponse): ListBilledUsageResponse;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): ListBilledUsageResponse.AsObject;
@@ -73,6 +109,37 @@ export class ListBilledUsageResponse extends jspb.Message {
export namespace ListBilledUsageResponse {
export type AsObject = {
sessionsList: Array,
+ totalCreditsUsed: number,
+ pagination?: PaginatedResponse.AsObject,
+ }
+}
+
+export class PaginatedResponse extends jspb.Message {
+ getPerPage(): number;
+ setPerPage(value: number): PaginatedResponse;
+ getTotalPages(): number;
+ setTotalPages(value: number): PaginatedResponse;
+ getTotal(): number;
+ setTotal(value: number): PaginatedResponse;
+ getPage(): number;
+ setPage(value: number): PaginatedResponse;
+
+ serializeBinary(): Uint8Array;
+ toObject(includeInstance?: boolean): PaginatedResponse.AsObject;
+ static toObject(includeInstance: boolean, msg: PaginatedResponse): PaginatedResponse.AsObject;
+ static extensions: {[key: number]: jspb.ExtensionFieldInfo};
+ static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo};
+ static serializeBinaryToWriter(message: PaginatedResponse, writer: jspb.BinaryWriter): void;
+ static deserializeBinary(bytes: Uint8Array): PaginatedResponse;
+ static deserializeBinaryFromReader(message: PaginatedResponse, reader: jspb.BinaryReader): PaginatedResponse;
+}
+
+export namespace PaginatedResponse {
+ export type AsObject = {
+ perPage: number,
+ totalPages: number,
+ total: number,
+ page: number,
}
}
diff --git a/components/usage-api/typescript/src/usage/v1/usage_pb.js b/components/usage-api/typescript/src/usage/v1/usage_pb.js
index 6421d812413ebb..066105cc0e8bc1 100644
--- a/components/usage-api/typescript/src/usage/v1/usage_pb.js
+++ b/components/usage-api/typescript/src/usage/v1/usage_pb.js
@@ -30,6 +30,8 @@ goog.exportSymbol('proto.usage.v1.GetCostCenterResponse', null, global);
goog.exportSymbol('proto.usage.v1.ListBilledUsageRequest', null, global);
goog.exportSymbol('proto.usage.v1.ListBilledUsageRequest.Ordering', null, global);
goog.exportSymbol('proto.usage.v1.ListBilledUsageResponse', null, global);
+goog.exportSymbol('proto.usage.v1.PaginatedRequest', null, global);
+goog.exportSymbol('proto.usage.v1.PaginatedResponse', null, global);
goog.exportSymbol('proto.usage.v1.ReconcileUsageRequest', null, global);
goog.exportSymbol('proto.usage.v1.ReconcileUsageResponse', null, global);
/**
@@ -53,6 +55,27 @@ if (goog.DEBUG && !COMPILED) {
*/
proto.usage.v1.ListBilledUsageRequest.displayName = 'proto.usage.v1.ListBilledUsageRequest';
}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.usage.v1.PaginatedRequest = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.usage.v1.PaginatedRequest, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.usage.v1.PaginatedRequest.displayName = 'proto.usage.v1.PaginatedRequest';
+}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
@@ -74,6 +97,27 @@ if (goog.DEBUG && !COMPILED) {
*/
proto.usage.v1.ListBilledUsageResponse.displayName = 'proto.usage.v1.ListBilledUsageResponse';
}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.usage.v1.PaginatedResponse = function(opt_data) {
+ jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.usage.v1.PaginatedResponse, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+ /**
+ * @public
+ * @override
+ */
+ proto.usage.v1.PaginatedResponse.displayName = 'proto.usage.v1.PaginatedResponse';
+}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
@@ -235,7 +279,8 @@ proto.usage.v1.ListBilledUsageRequest.toObject = function(includeInstance, msg)
attributionId: jspb.Message.getFieldWithDefault(msg, 1, ""),
from: (f = msg.getFrom()) && google_protobuf_timestamp_pb.Timestamp.toObject(includeInstance, f),
to: (f = msg.getTo()) && google_protobuf_timestamp_pb.Timestamp.toObject(includeInstance, f),
- order: jspb.Message.getFieldWithDefault(msg, 4, 0)
+ order: jspb.Message.getFieldWithDefault(msg, 4, 0),
+ pagination: (f = msg.getPagination()) && proto.usage.v1.PaginatedRequest.toObject(includeInstance, f)
};
if (includeInstance) {
@@ -290,6 +335,11 @@ proto.usage.v1.ListBilledUsageRequest.deserializeBinaryFromReader = function(msg
var value = /** @type {!proto.usage.v1.ListBilledUsageRequest.Ordering} */ (reader.readEnum());
msg.setOrder(value);
break;
+ case 5:
+ var value = new proto.usage.v1.PaginatedRequest;
+ reader.readMessage(value,proto.usage.v1.PaginatedRequest.deserializeBinaryFromReader);
+ msg.setPagination(value);
+ break;
default:
reader.skipField();
break;
@@ -349,6 +399,14 @@ proto.usage.v1.ListBilledUsageRequest.serializeBinaryToWriter = function(message
f
);
}
+ f = message.getPagination();
+ if (f != null) {
+ writer.writeMessage(
+ 5,
+ f,
+ proto.usage.v1.PaginatedRequest.serializeBinaryToWriter
+ );
+ }
};
@@ -470,6 +528,203 @@ proto.usage.v1.ListBilledUsageRequest.prototype.setOrder = function(value) {
};
+/**
+ * optional PaginatedRequest pagination = 5;
+ * @return {?proto.usage.v1.PaginatedRequest}
+ */
+proto.usage.v1.ListBilledUsageRequest.prototype.getPagination = function() {
+ return /** @type{?proto.usage.v1.PaginatedRequest} */ (
+ jspb.Message.getWrapperField(this, proto.usage.v1.PaginatedRequest, 5));
+};
+
+
+/**
+ * @param {?proto.usage.v1.PaginatedRequest|undefined} value
+ * @return {!proto.usage.v1.ListBilledUsageRequest} returns this
+*/
+proto.usage.v1.ListBilledUsageRequest.prototype.setPagination = function(value) {
+ return jspb.Message.setWrapperField(this, 5, value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.usage.v1.ListBilledUsageRequest} returns this
+ */
+proto.usage.v1.ListBilledUsageRequest.prototype.clearPagination = function() {
+ return this.setPagination(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.usage.v1.ListBilledUsageRequest.prototype.hasPagination = function() {
+ return jspb.Message.getField(this, 5) != null;
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.usage.v1.PaginatedRequest.prototype.toObject = function(opt_includeInstance) {
+ return proto.usage.v1.PaginatedRequest.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.usage.v1.PaginatedRequest} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.usage.v1.PaginatedRequest.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ perPage: jspb.Message.getFieldWithDefault(msg, 1, 0),
+ page: jspb.Message.getFieldWithDefault(msg, 2, 0)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.usage.v1.PaginatedRequest}
+ */
+proto.usage.v1.PaginatedRequest.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.usage.v1.PaginatedRequest;
+ return proto.usage.v1.PaginatedRequest.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.usage.v1.PaginatedRequest} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.usage.v1.PaginatedRequest}
+ */
+proto.usage.v1.PaginatedRequest.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 1:
+ var value = /** @type {number} */ (reader.readInt64());
+ msg.setPerPage(value);
+ break;
+ case 2:
+ var value = /** @type {number} */ (reader.readInt64());
+ msg.setPage(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.usage.v1.PaginatedRequest.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.usage.v1.PaginatedRequest.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.usage.v1.PaginatedRequest} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.usage.v1.PaginatedRequest.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getPerPage();
+ if (f !== 0) {
+ writer.writeInt64(
+ 1,
+ f
+ );
+ }
+ f = message.getPage();
+ if (f !== 0) {
+ writer.writeInt64(
+ 2,
+ f
+ );
+ }
+};
+
+
+/**
+ * optional int64 per_page = 1;
+ * @return {number}
+ */
+proto.usage.v1.PaginatedRequest.prototype.getPerPage = function() {
+ return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.usage.v1.PaginatedRequest} returns this
+ */
+proto.usage.v1.PaginatedRequest.prototype.setPerPage = function(value) {
+ return jspb.Message.setProto3IntField(this, 1, value);
+};
+
+
+/**
+ * optional int64 page = 2;
+ * @return {number}
+ */
+proto.usage.v1.PaginatedRequest.prototype.getPage = function() {
+ return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.usage.v1.PaginatedRequest} returns this
+ */
+proto.usage.v1.PaginatedRequest.prototype.setPage = function(value) {
+ return jspb.Message.setProto3IntField(this, 2, value);
+};
+
+
/**
* List of repeated fields within this message type.
@@ -510,7 +765,9 @@ proto.usage.v1.ListBilledUsageResponse.prototype.toObject = function(opt_include
proto.usage.v1.ListBilledUsageResponse.toObject = function(includeInstance, msg) {
var f, obj = {
sessionsList: jspb.Message.toObjectList(msg.getSessionsList(),
- proto.usage.v1.BilledSession.toObject, includeInstance)
+ proto.usage.v1.BilledSession.toObject, includeInstance),
+ totalCreditsUsed: jspb.Message.getFloatingPointFieldWithDefault(msg, 2, 0.0),
+ pagination: (f = msg.getPagination()) && proto.usage.v1.PaginatedResponse.toObject(includeInstance, f)
};
if (includeInstance) {
@@ -552,6 +809,15 @@ proto.usage.v1.ListBilledUsageResponse.deserializeBinaryFromReader = function(ms
reader.readMessage(value,proto.usage.v1.BilledSession.deserializeBinaryFromReader);
msg.addSessions(value);
break;
+ case 2:
+ var value = /** @type {number} */ (reader.readDouble());
+ msg.setTotalCreditsUsed(value);
+ break;
+ case 3:
+ var value = new proto.usage.v1.PaginatedResponse;
+ reader.readMessage(value,proto.usage.v1.PaginatedResponse.deserializeBinaryFromReader);
+ msg.setPagination(value);
+ break;
default:
reader.skipField();
break;
@@ -589,6 +855,21 @@ proto.usage.v1.ListBilledUsageResponse.serializeBinaryToWriter = function(messag
proto.usage.v1.BilledSession.serializeBinaryToWriter
);
}
+ f = message.getTotalCreditsUsed();
+ if (f !== 0.0) {
+ writer.writeDouble(
+ 2,
+ f
+ );
+ }
+ f = message.getPagination();
+ if (f != null) {
+ writer.writeMessage(
+ 3,
+ f,
+ proto.usage.v1.PaginatedResponse.serializeBinaryToWriter
+ );
+ }
};
@@ -630,6 +911,281 @@ proto.usage.v1.ListBilledUsageResponse.prototype.clearSessionsList = function()
};
+/**
+ * optional double total_credits_used = 2;
+ * @return {number}
+ */
+proto.usage.v1.ListBilledUsageResponse.prototype.getTotalCreditsUsed = function() {
+ return /** @type {number} */ (jspb.Message.getFloatingPointFieldWithDefault(this, 2, 0.0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.usage.v1.ListBilledUsageResponse} returns this
+ */
+proto.usage.v1.ListBilledUsageResponse.prototype.setTotalCreditsUsed = function(value) {
+ return jspb.Message.setProto3FloatField(this, 2, value);
+};
+
+
+/**
+ * optional PaginatedResponse pagination = 3;
+ * @return {?proto.usage.v1.PaginatedResponse}
+ */
+proto.usage.v1.ListBilledUsageResponse.prototype.getPagination = function() {
+ return /** @type{?proto.usage.v1.PaginatedResponse} */ (
+ jspb.Message.getWrapperField(this, proto.usage.v1.PaginatedResponse, 3));
+};
+
+
+/**
+ * @param {?proto.usage.v1.PaginatedResponse|undefined} value
+ * @return {!proto.usage.v1.ListBilledUsageResponse} returns this
+*/
+proto.usage.v1.ListBilledUsageResponse.prototype.setPagination = function(value) {
+ return jspb.Message.setWrapperField(this, 3, value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.usage.v1.ListBilledUsageResponse} returns this
+ */
+proto.usage.v1.ListBilledUsageResponse.prototype.clearPagination = function() {
+ return this.setPagination(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.usage.v1.ListBilledUsageResponse.prototype.hasPagination = function() {
+ return jspb.Message.getField(this, 3) != null;
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ * net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ * JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.usage.v1.PaginatedResponse.prototype.toObject = function(opt_includeInstance) {
+ return proto.usage.v1.PaginatedResponse.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ * the JSPB instance for transitional soy proto support:
+ * http://goto/soy-param-migration
+ * @param {!proto.usage.v1.PaginatedResponse} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.usage.v1.PaginatedResponse.toObject = function(includeInstance, msg) {
+ var f, obj = {
+ perPage: jspb.Message.getFieldWithDefault(msg, 2, 0),
+ totalPages: jspb.Message.getFieldWithDefault(msg, 3, 0),
+ total: jspb.Message.getFieldWithDefault(msg, 4, 0),
+ page: jspb.Message.getFieldWithDefault(msg, 5, 0)
+ };
+
+ if (includeInstance) {
+ obj.$jspbMessageInstance = msg;
+ }
+ return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.usage.v1.PaginatedResponse}
+ */
+proto.usage.v1.PaginatedResponse.deserializeBinary = function(bytes) {
+ var reader = new jspb.BinaryReader(bytes);
+ var msg = new proto.usage.v1.PaginatedResponse;
+ return proto.usage.v1.PaginatedResponse.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.usage.v1.PaginatedResponse} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.usage.v1.PaginatedResponse}
+ */
+proto.usage.v1.PaginatedResponse.deserializeBinaryFromReader = function(msg, reader) {
+ while (reader.nextField()) {
+ if (reader.isEndGroup()) {
+ break;
+ }
+ var field = reader.getFieldNumber();
+ switch (field) {
+ case 2:
+ var value = /** @type {number} */ (reader.readInt64());
+ msg.setPerPage(value);
+ break;
+ case 3:
+ var value = /** @type {number} */ (reader.readInt64());
+ msg.setTotalPages(value);
+ break;
+ case 4:
+ var value = /** @type {number} */ (reader.readInt64());
+ msg.setTotal(value);
+ break;
+ case 5:
+ var value = /** @type {number} */ (reader.readInt64());
+ msg.setPage(value);
+ break;
+ default:
+ reader.skipField();
+ break;
+ }
+ }
+ return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.usage.v1.PaginatedResponse.prototype.serializeBinary = function() {
+ var writer = new jspb.BinaryWriter();
+ proto.usage.v1.PaginatedResponse.serializeBinaryToWriter(this, writer);
+ return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.usage.v1.PaginatedResponse} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.usage.v1.PaginatedResponse.serializeBinaryToWriter = function(message, writer) {
+ var f = undefined;
+ f = message.getPerPage();
+ if (f !== 0) {
+ writer.writeInt64(
+ 2,
+ f
+ );
+ }
+ f = message.getTotalPages();
+ if (f !== 0) {
+ writer.writeInt64(
+ 3,
+ f
+ );
+ }
+ f = message.getTotal();
+ if (f !== 0) {
+ writer.writeInt64(
+ 4,
+ f
+ );
+ }
+ f = message.getPage();
+ if (f !== 0) {
+ writer.writeInt64(
+ 5,
+ f
+ );
+ }
+};
+
+
+/**
+ * optional int64 per_page = 2;
+ * @return {number}
+ */
+proto.usage.v1.PaginatedResponse.prototype.getPerPage = function() {
+ return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.usage.v1.PaginatedResponse} returns this
+ */
+proto.usage.v1.PaginatedResponse.prototype.setPerPage = function(value) {
+ return jspb.Message.setProto3IntField(this, 2, value);
+};
+
+
+/**
+ * optional int64 total_pages = 3;
+ * @return {number}
+ */
+proto.usage.v1.PaginatedResponse.prototype.getTotalPages = function() {
+ return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.usage.v1.PaginatedResponse} returns this
+ */
+proto.usage.v1.PaginatedResponse.prototype.setTotalPages = function(value) {
+ return jspb.Message.setProto3IntField(this, 3, value);
+};
+
+
+/**
+ * optional int64 total = 4;
+ * @return {number}
+ */
+proto.usage.v1.PaginatedResponse.prototype.getTotal = function() {
+ return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 4, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.usage.v1.PaginatedResponse} returns this
+ */
+proto.usage.v1.PaginatedResponse.prototype.setTotal = function(value) {
+ return jspb.Message.setProto3IntField(this, 4, value);
+};
+
+
+/**
+ * optional int64 page = 5;
+ * @return {number}
+ */
+proto.usage.v1.PaginatedResponse.prototype.getPage = function() {
+ return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 5, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.usage.v1.PaginatedResponse} returns this
+ */
+proto.usage.v1.PaginatedResponse.prototype.setPage = function(value) {
+ return jspb.Message.setProto3IntField(this, 5, value);
+};
+
+
diff --git a/components/usage-api/usage/v1/usage.proto b/components/usage-api/usage/v1/usage.proto
index 55214f0d491339..09e0db9db9908d 100644
--- a/components/usage-api/usage/v1/usage.proto
+++ b/components/usage-api/usage/v1/usage.proto
@@ -34,10 +34,26 @@ message ListBilledUsageRequest {
}
Ordering order = 4;
+
+ PaginatedRequest pagination = 5;
+}
+
+message PaginatedRequest {
+ int64 per_page = 1;
+ int64 page = 2;
}
message ListBilledUsageResponse {
repeated BilledSession sessions = 1;
+ double total_credits_used = 2;
+ PaginatedResponse pagination = 3;
+}
+
+message PaginatedResponse {
+ int64 per_page = 2;
+ int64 total_pages = 3;
+ int64 total = 4;
+ int64 page = 5;
}
message BilledSession {
diff --git a/components/usage/pkg/apiv1/usage.go b/components/usage/pkg/apiv1/usage.go
index f0f37897df55d4..402e2289f76f1b 100644
--- a/components/usage/pkg/apiv1/usage.go
+++ b/components/usage/pkg/apiv1/usage.go
@@ -9,6 +9,7 @@ import (
"database/sql"
"errors"
"fmt"
+ "math"
"time"
"github.com/gitpod-io/gitpod/common-go/log"
@@ -54,18 +55,26 @@ func (s *UsageService) ListBilledUsage(ctx context.Context, in *v1.ListBilledUsa
return nil, status.Errorf(codes.InvalidArgument, "Maximum range exceeded. Range specified can be at most %s", maxQuerySize.String())
}
- var order db.Order
- switch in.Order {
- case v1.ListBilledUsageRequest_ORDERING_ASCENDING:
+ var order db.Order = db.DescendingOrder
+ if in.Order == v1.ListBilledUsageRequest_ORDERING_ASCENDING {
order = db.AscendingOrder
- default:
- order = db.DescendingOrder
}
- usageRecords, err := db.ListUsage(ctx, s.conn, db.AttributionID(in.GetAttributionId()), from, to, order)
+ var limit int64 = 1000
+ var page int64 = 0
+ var offset int64 = 0
+ if in.Pagination != nil {
+ limit = in.Pagination.PerPage
+ page = in.Pagination.Page
+ offset = limit * (int64(math.Max(0, float64(page-1))))
+ }
+
+ listUsageResult, err := db.ListUsage(ctx, s.conn, db.AttributionID(in.GetAttributionId()), from, to, order, offset, limit)
if err != nil {
log.Log.
WithField("attribution_id", in.AttributionId).
+ WithField("perPage", limit).
+ WithField("page", page).
WithField("from", from).
WithField("to", to).
WithError(err).Error("Failed to list usage.")
@@ -73,7 +82,7 @@ func (s *UsageService) ListBilledUsage(ctx context.Context, in *v1.ListBilledUsa
}
var billedSessions []*v1.BilledSession
- for _, usageRecord := range usageRecords {
+ for _, usageRecord := range listUsageResult.UsageRecords {
var endTime *timestamppb.Timestamp
if usageRecord.StoppedAt.Valid {
endTime = timestamppb.New(usageRecord.StoppedAt.Time)
@@ -93,8 +102,19 @@ func (s *UsageService) ListBilledUsage(ctx context.Context, in *v1.ListBilledUsa
billedSessions = append(billedSessions, billedSession)
}
+ var totalPages = int64(math.Ceil(float64(listUsageResult.Count) / float64(limit)))
+
+ var pagination = v1.PaginatedResponse{
+ PerPage: limit,
+ Page: page,
+ TotalPages: totalPages,
+ Total: listUsageResult.Count,
+ }
+
return &v1.ListBilledUsageResponse{
- Sessions: billedSessions,
+ Sessions: billedSessions,
+ TotalCreditsUsed: listUsageResult.TotalCreditsUsed,
+ Pagination: &pagination,
}, nil
}
diff --git a/components/usage/pkg/apiv1/usage_test.go b/components/usage/pkg/apiv1/usage_test.go
index 31dc7e2cbdadd0..3445f029e738f6 100644
--- a/components/usage/pkg/apiv1/usage_test.go
+++ b/components/usage/pkg/apiv1/usage_test.go
@@ -170,6 +170,158 @@ func TestUsageService_ListBilledUsage(t *testing.T) {
}
}
+func TestUsageService_ListBilledUsage_Pagination(t *testing.T) {
+ ctx := context.Background()
+
+ type Expectation struct {
+ count int64
+ total int64
+ totalPages int64
+ page int64
+ perPage int64
+ }
+
+ type Scenario struct {
+ name string
+ Request *v1.ListBilledUsageRequest
+ Expect Expectation
+ }
+
+ start := time.Date(2022, 07, 1, 13, 0, 0, 0, time.UTC)
+ attrID := db.NewTeamAttributionID(uuid.New().String())
+ var instances []db.WorkspaceInstanceUsage
+ for i := 1; i <= 14; i++ {
+ instance := dbtest.NewWorkspaceInstanceUsage(t, db.WorkspaceInstanceUsage{
+ AttributionID: attrID,
+ StartedAt: start.Add(time.Duration(i) * time.Minute),
+ StoppedAt: sql.NullTime{
+ Time: start.Add(time.Duration(i)*time.Minute + time.Hour),
+ Valid: true,
+ },
+ })
+ instances = append(instances, instance)
+ }
+
+ scenarios := []Scenario{
+ (func() Scenario {
+
+ return Scenario{
+ name: "first page",
+ Request: &v1.ListBilledUsageRequest{
+ AttributionId: string(attrID),
+ From: timestamppb.New(start),
+ To: timestamppb.New(start.Add(20*time.Minute + 2*time.Hour)),
+ Pagination: &v1.PaginatedRequest{
+ PerPage: int64(5),
+ Page: int64(1),
+ },
+ },
+ Expect: Expectation{
+ count: int64(5),
+ total: int64(14),
+ totalPages: int64(3),
+ page: int64(1),
+ perPage: int64(5),
+ },
+ }
+ })(),
+ (func() Scenario {
+
+ return Scenario{
+ name: "second page",
+ Request: &v1.ListBilledUsageRequest{
+ AttributionId: string(attrID),
+ From: timestamppb.New(start),
+ To: timestamppb.New(start.Add(20*time.Minute + 2*time.Hour)),
+ Pagination: &v1.PaginatedRequest{
+ PerPage: int64(5),
+ Page: int64(2),
+ },
+ },
+ Expect: Expectation{
+ count: int64(5),
+ total: int64(14),
+ totalPages: int64(3),
+ page: int64(2),
+ perPage: int64(5),
+ },
+ }
+ })(),
+ (func() Scenario {
+
+ return Scenario{
+ name: "third page",
+ Request: &v1.ListBilledUsageRequest{
+ AttributionId: string(attrID),
+ From: timestamppb.New(start),
+ To: timestamppb.New(start.Add(20*time.Minute + 2*time.Hour)),
+ Pagination: &v1.PaginatedRequest{
+ PerPage: int64(5),
+ Page: int64(3),
+ },
+ },
+ Expect: Expectation{
+ count: int64(4),
+ total: int64(14),
+ totalPages: int64(3),
+ page: int64(3),
+ perPage: int64(5),
+ },
+ }
+ })(),
+ (func() Scenario {
+
+ return Scenario{
+ name: "fourth page",
+ Request: &v1.ListBilledUsageRequest{
+ AttributionId: string(attrID),
+ From: timestamppb.New(start),
+ To: timestamppb.New(start.Add(20*time.Minute + 2*time.Hour)),
+ Pagination: &v1.PaginatedRequest{
+ PerPage: int64(5),
+ Page: int64(4),
+ },
+ },
+ Expect: Expectation{
+ count: int64(0),
+ total: int64(14),
+ totalPages: int64(3),
+ page: int64(4),
+ perPage: int64(5),
+ },
+ }
+ })(),
+ }
+
+ dbconn := dbtest.ConnectForTests(t)
+ dbtest.CreateWorkspaceInstanceUsageRecords(t, dbconn, instances...)
+
+ srv := baseserver.NewForTests(t,
+ baseserver.WithGRPC(baseserver.MustUseRandomLocalAddress(t)),
+ )
+
+ generator := NewReportGenerator(dbconn, DefaultWorkspacePricer)
+ v1.RegisterUsageServiceServer(srv.GRPC(), NewUsageService(dbconn, generator, nil))
+ baseserver.StartServerForTests(t, srv)
+
+ conn, err := grpc.Dial(srv.GRPCAddress(), grpc.WithTransportCredentials(insecure.NewCredentials()))
+ require.NoError(t, err)
+
+ for _, scenario := range scenarios {
+ t.Run(scenario.name, func(t *testing.T) {
+
+ client := v1.NewUsageServiceClient(conn)
+
+ resp, err := client.ListBilledUsage(ctx, scenario.Request)
+ require.NoError(t, err)
+ require.NotNil(t, resp.Pagination)
+
+ require.Equal(t, scenario.Expect.total, resp.Pagination.Total)
+ require.Equal(t, scenario.Expect.totalPages, resp.Pagination.TotalPages)
+ })
+ }
+}
+
func TestInstanceToUsageRecords(t *testing.T) {
maxStopTime := time.Date(2022, 05, 31, 23, 00, 00, 00, time.UTC)
teamID, ownerID, projectID := uuid.New().String(), uuid.New(), uuid.New()
diff --git a/components/usage/pkg/db/workspace_instance_usage.go b/components/usage/pkg/db/workspace_instance_usage.go
index 1d7e81074ee138..d9975616e09df3 100644
--- a/components/usage/pkg/db/workspace_instance_usage.go
+++ b/components/usage/pkg/db/workspace_instance_usage.go
@@ -36,6 +36,12 @@ type WorkspaceInstanceUsage struct {
Deleted bool `gorm:"column:deleted;type:tinyint;default:0;" json:"deleted"`
}
+type ListUsageResult struct {
+ UsageRecords []WorkspaceInstanceUsage
+ Count int64
+ TotalCreditsUsed float64
+}
+
// TableName sets the insert table name for this struct type
func (u *WorkspaceInstanceUsage) TableName() string {
return "d_b_workspace_instance_usage"
@@ -65,12 +71,47 @@ const (
AscendingOrder
)
-func ListUsage(ctx context.Context, conn *gorm.DB, attributionId AttributionID, from, to time.Time, sort Order) ([]WorkspaceInstanceUsage, error) {
+func ListUsage(ctx context.Context, conn *gorm.DB, attributionId AttributionID, from, to time.Time, sort Order, offset int64, limit int64) (*ListUsageResult, error) {
+ var listUsageResult = new(ListUsageResult)
db := conn.WithContext(ctx)
+ var totalCreditsUsed sql.NullFloat64
+ var count sql.NullInt64
+ countResult, err := db.
+ WithContext(ctx).
+ Table((&WorkspaceInstanceUsage{}).TableName()).
+ Select("sum(creditsUsed) as totalCreditsUsed", "count(*) as count").
+ Order(fmt.Sprintf("startedAt %s", sort.ToSQL())).
+ Where("attributionId = ?", attributionId).
+ Where(
+ // started before, finished inside query range
+ conn.Where("? <= stoppedAt AND stoppedAt < ?", from, to).
+ // started inside query range, finished inside
+ Or("startedAt >= ? AND stoppedAt < ?", from, to).
+ // started inside query range, finished outside
+ Or("? <= startedAt AND startedAt < ?", from, to).
+ // started before query range, still running
+ Or("startedAt <= ? AND (stoppedAt > ? OR stoppedAt IS NULL)", from, to),
+ ).
+ Rows()
+ if err != nil || !countResult.Next() {
+ return nil, fmt.Errorf("failed to get count of usage records: %s", err)
+ }
+ err = countResult.Scan(&totalCreditsUsed, &count)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get count of usage records: %s", err)
+ }
+ if totalCreditsUsed.Valid {
+ listUsageResult.TotalCreditsUsed = totalCreditsUsed.Float64
+ }
+ if count.Valid {
+ listUsageResult.Count = count.Int64
+ }
+
var usageRecords []WorkspaceInstanceUsage
result := db.
WithContext(ctx).
+ Table((&WorkspaceInstanceUsage{}).TableName()).
Order(fmt.Sprintf("startedAt %s", sort.ToSQL())).
Where("attributionId = ?", attributionId).
Where(
@@ -83,9 +124,13 @@ func ListUsage(ctx context.Context, conn *gorm.DB, attributionId AttributionID,
// started before query range, still running
Or("startedAt <= ? AND (stoppedAt > ? OR stoppedAt IS NULL)", from, to),
).
+ Offset(int(offset)).
+ Limit(int(limit)).
Find(&usageRecords)
if result.Error != nil {
return nil, fmt.Errorf("failed to get usage records: %s", result.Error)
}
- return usageRecords, nil
+ listUsageResult.UsageRecords = usageRecords
+
+ return listUsageResult, nil
}
diff --git a/components/usage/pkg/db/workspace_instance_usage_test.go b/components/usage/pkg/db/workspace_instance_usage_test.go
index 8b07ec2e078c10..264bc02755ecd0 100644
--- a/components/usage/pkg/db/workspace_instance_usage_test.go
+++ b/components/usage/pkg/db/workspace_instance_usage_test.go
@@ -54,16 +54,19 @@ func TestCreateUsageRecords_Updates(t *testing.T) {
Valid: true,
},
})
-
require.NoError(t, db.CreateUsageRecords(context.Background(), conn, []db.WorkspaceInstanceUsage{update}))
t.Cleanup(func() {
conn.Model(&db.WorkspaceInstanceUsage{}).Delete(update)
})
- list, err := db.ListUsage(context.Background(), conn, teamAttributionID, time.Date(2022, 7, 1, 0, 0, 0, 0, time.UTC), time.Date(2022, 8, 1, 0, 0, 0, 0, time.UTC), db.DescendingOrder)
+ listResult, err := db.ListUsage(context.Background(), conn, teamAttributionID, time.Date(2022, 7, 1, 0, 0, 0, 0, time.UTC), time.Date(2022, 8, 1, 0, 0, 0, 0, time.UTC), db.DescendingOrder, int64(0), int64(100))
require.NoError(t, err)
- require.Len(t, list, 1)
- require.Equal(t, update, list[0])
+ if err == nil {
+
+ }
+ require.Len(t, listResult.UsageRecords, 1)
+ require.Equal(t, int64(1), listResult.Count)
+ require.Equal(t, update, listResult.UsageRecords[0])
}
func TestListUsage_Ordering(t *testing.T) {
@@ -91,10 +94,10 @@ func TestListUsage_Ordering(t *testing.T) {
conn.Model(&db.WorkspaceInstanceUsage{}).Delete(instances)
})
- listed, err := db.ListUsage(context.Background(), conn, teamAttributionID, time.Date(2022, 7, 1, 0, 0, 0, 0, time.UTC), time.Date(2022, 8, 1, 0, 0, 0, 0, time.UTC), db.AscendingOrder)
+ listResult, err := db.ListUsage(context.Background(), conn, teamAttributionID, time.Date(2022, 7, 1, 0, 0, 0, 0, time.UTC), time.Date(2022, 8, 1, 0, 0, 0, 0, time.UTC), db.AscendingOrder, int64(0), int64(100))
require.NoError(t, err)
- require.Equal(t, []db.WorkspaceInstanceUsage{oldest, newest}, listed)
+ require.Equal(t, []db.WorkspaceInstanceUsage{oldest, newest}, listResult.UsageRecords)
}
func TestListUsageInRange(t *testing.T) {
@@ -178,11 +181,45 @@ func TestListUsageInRange(t *testing.T) {
instances := []db.WorkspaceInstanceUsage{startBeforeFinishBefore, startBeforeFinishInside, startInsideFinishInside, startInsideFinishInsideButDifferentAttributionID, startedInsideFinishedOutside, startedOutsideFinishedOutside, startedBeforeAndStillRunning, startedInsideAndStillRunning}
dbtest.CreateWorkspaceInstanceUsageRecords(t, conn, instances...)
- results, err := db.ListUsage(context.Background(), conn, attributionID, start, end, db.DescendingOrder)
+ listResult, err := db.ListUsage(context.Background(), conn, attributionID, start, end, db.DescendingOrder, int64(0), int64(100))
require.NoError(t, err)
- require.Len(t, results, 5)
+ require.Len(t, listResult.UsageRecords, 5)
require.Equal(t, []db.WorkspaceInstanceUsage{
startedInsideFinishedOutside, startedInsideAndStillRunning, startInsideFinishInside, startBeforeFinishInside, startedBeforeAndStillRunning,
- }, results)
+ }, listResult.UsageRecords)
+}
+
+func TestListUsagePagination(t *testing.T) {
+ conn := dbtest.ConnectForTests(t)
+
+ start := time.Date(2022, 7, 1, 0, 0, 0, 0, time.UTC)
+ end := time.Date(2022, 8, 1, 0, 0, 0, 0, time.UTC)
+
+ attributionID := db.NewTeamAttributionID(uuid.New().String())
+
+ // started inside query range, and also finished inside query range
+ startInsideFinishInside := dbtest.NewWorkspaceInstanceUsage(t, db.WorkspaceInstanceUsage{
+ AttributionID: attributionID,
+ StartedAt: start.Add(3 * time.Hour),
+ StoppedAt: sql.NullTime{
+ Time: start.Add(5 * time.Hour),
+ Valid: true,
+ },
+ CreditsUsed: float64(2),
+ })
+
+ var instances []db.WorkspaceInstanceUsage
+ for i := 0; i < 122; i++ {
+ startInsideFinishInside.InstanceID = uuid.New()
+ instances = append(instances, startInsideFinishInside)
+ }
+ dbtest.CreateWorkspaceInstanceUsageRecords(t, conn, instances...)
+
+ listResult, err := db.ListUsage(context.Background(), conn, attributionID, start, end, db.DescendingOrder, int64(100), int64(50))
+ require.NoError(t, err)
+
+ require.Len(t, listResult.UsageRecords, 22)
+ require.Equal(t, float64(244), listResult.TotalCreditsUsed)
+ require.Equal(t, int64(122), listResult.Count)
}