Skip to content

Commit 5670a55

Browse files
svenefftingeroboquat
authored andcommitted
[usage] show sum of usage not balance
fixes #13067
1 parent f4cc974 commit 5670a55

File tree

11 files changed

+191
-216
lines changed

11 files changed

+191
-216
lines changed

components/dashboard/src/components/UsageView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function UsageView({ attributionId }: UsageViewProps) {
6262
try {
6363
const page = await getGitpodService().server.listUsage(request);
6464
setUsagePage(page);
65-
setTotalCreditsUsed(page.creditBalanceAtEnd);
65+
setTotalCreditsUsed(page.creditsUsed);
6666
} catch (error) {
6767
if (error.code === ErrorCodes.PERMISSION_DENIED) {
6868
setErrorMessage("Access to usage details is restricted to team owners.");
@@ -173,7 +173,7 @@ function UsageView({ attributionId }: UsageViewProps) {
173173
<div className="text-base text-gray-500">Total usage</div>
174174
<div className="flex text-lg text-gray-600 font-semibold">
175175
<CreditsSvg className="my-auto mr-1" />
176-
<span>{totalCreditsUsed} Credits</span>
176+
<span>{totalCreditsUsed.toLocaleString()} Credits</span>
177177
</div>
178178
</div>
179179
<div className="flex flex-col truncate mt-8 text-sm">

components/gitpod-protocol/src/usage.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ export interface PaginationRequest {
2828
export interface ListUsageResponse {
2929
usageEntriesList: Usage[];
3030
pagination?: PaginationResponse;
31-
creditBalanceAtStart: number;
32-
creditBalanceAtEnd: number;
31+
creditsUsed: number;
3332
}
3433

3534
export interface PaginationResponse {

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2329,8 +2329,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
23292329
totalPages: response.pagination.totalPages,
23302330
}
23312331
: undefined,
2332-
creditBalanceAtEnd: response.creditBalanceAtEnd,
2333-
creditBalanceAtStart: response.creditBalanceAtStart,
2332+
creditsUsed: response.creditsUsed,
23342333
};
23352334
}
23362335

components/usage-api/go/v1/usage.pb.go

Lines changed: 102 additions & 115 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/usage-api/typescript/src/usage/v1/usage.pb.ts

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,8 @@ export interface ListUsageResponse {
104104
pagination:
105105
| PaginatedResponse
106106
| undefined;
107-
/** the amount of credits the given account (attributionId) had at the beginning of the requested period */
108-
creditBalanceAtStart: number;
109-
/** the amount of credits the given account (attributionId) had at the end of the requested period */
110-
creditBalanceAtEnd: number;
107+
/** the amount of credits the given account (attributionId) has used during the requested period */
108+
creditsUsed: number;
111109
}
112110

113111
export interface Usage {
@@ -571,7 +569,7 @@ export const ListUsageRequest = {
571569
};
572570

573571
function createBaseListUsageResponse(): ListUsageResponse {
574-
return { usageEntries: [], pagination: undefined, creditBalanceAtStart: 0, creditBalanceAtEnd: 0 };
572+
return { usageEntries: [], pagination: undefined, creditsUsed: 0 };
575573
}
576574

577575
export const ListUsageResponse = {
@@ -582,11 +580,8 @@ export const ListUsageResponse = {
582580
if (message.pagination !== undefined) {
583581
PaginatedResponse.encode(message.pagination, writer.uint32(18).fork()).ldelim();
584582
}
585-
if (message.creditBalanceAtStart !== 0) {
586-
writer.uint32(25).double(message.creditBalanceAtStart);
587-
}
588-
if (message.creditBalanceAtEnd !== 0) {
589-
writer.uint32(33).double(message.creditBalanceAtEnd);
583+
if (message.creditsUsed !== 0) {
584+
writer.uint32(25).double(message.creditsUsed);
590585
}
591586
return writer;
592587
},
@@ -605,10 +600,7 @@ export const ListUsageResponse = {
605600
message.pagination = PaginatedResponse.decode(reader, reader.uint32());
606601
break;
607602
case 3:
608-
message.creditBalanceAtStart = reader.double();
609-
break;
610-
case 4:
611-
message.creditBalanceAtEnd = reader.double();
603+
message.creditsUsed = reader.double();
612604
break;
613605
default:
614606
reader.skipType(tag & 7);
@@ -622,8 +614,7 @@ export const ListUsageResponse = {
622614
return {
623615
usageEntries: Array.isArray(object?.usageEntries) ? object.usageEntries.map((e: any) => Usage.fromJSON(e)) : [],
624616
pagination: isSet(object.pagination) ? PaginatedResponse.fromJSON(object.pagination) : undefined,
625-
creditBalanceAtStart: isSet(object.creditBalanceAtStart) ? Number(object.creditBalanceAtStart) : 0,
626-
creditBalanceAtEnd: isSet(object.creditBalanceAtEnd) ? Number(object.creditBalanceAtEnd) : 0,
617+
creditsUsed: isSet(object.creditsUsed) ? Number(object.creditsUsed) : 0,
627618
};
628619
},
629620

@@ -636,8 +627,7 @@ export const ListUsageResponse = {
636627
}
637628
message.pagination !== undefined &&
638629
(obj.pagination = message.pagination ? PaginatedResponse.toJSON(message.pagination) : undefined);
639-
message.creditBalanceAtStart !== undefined && (obj.creditBalanceAtStart = message.creditBalanceAtStart);
640-
message.creditBalanceAtEnd !== undefined && (obj.creditBalanceAtEnd = message.creditBalanceAtEnd);
630+
message.creditsUsed !== undefined && (obj.creditsUsed = message.creditsUsed);
641631
return obj;
642632
},
643633

@@ -647,8 +637,7 @@ export const ListUsageResponse = {
647637
message.pagination = (object.pagination !== undefined && object.pagination !== null)
648638
? PaginatedResponse.fromPartial(object.pagination)
649639
: undefined;
650-
message.creditBalanceAtStart = object.creditBalanceAtStart ?? 0;
651-
message.creditBalanceAtEnd = object.creditBalanceAtEnd ?? 0;
640+
message.creditsUsed = object.creditsUsed ?? 0;
652641
return message;
653642
},
654643
};

components/usage-api/usage/v1/usage.proto

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,8 @@ message ListUsageRequest {
7070
message ListUsageResponse {
7171
repeated Usage usage_entries = 1;
7272
PaginatedResponse pagination = 2;
73-
// the amount of credits the given account (attributionId) had at the beginning of the requested period
74-
double credit_balance_at_start = 3;
75-
76-
// the amount of credits the given account (attributionId) had at the end of the requested period
77-
double credit_balance_at_end = 4;
73+
// the amount of credits the given account (attributionId) has used during the requested period
74+
double credits_used = 3;
7875
}
7976

8077
message Usage {

components/usage/pkg/apiv1/usage.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,30 +129,31 @@ func (s *UsageService) ListUsage(ctx context.Context, in *v1.ListUsageRequest) (
129129
}
130130

131131
usageSummary, err := db.GetUsageSummary(ctx, s.conn,
132-
attributionId,
133-
from,
134-
to,
135-
excludeDrafts,
132+
db.GetUsageSummaryParams{
133+
AttributionId: attributionId,
134+
From: from,
135+
To: to,
136+
ExcludeDrafts: excludeDrafts,
137+
},
136138
)
137139

138140
if err != nil {
139141
logger.WithError(err).Error("Failed to fetch usage metadata.")
140142
return nil, status.Error(codes.Internal, "unable to retrieve usage")
141143
}
142-
totalPages := int64(math.Ceil(float64(usageSummary.NumRecordsInRange) / float64(perPage)))
144+
totalPages := int64(math.Ceil(float64(usageSummary.NumberOfRecords) / float64(perPage)))
143145

144146
pagination := v1.PaginatedResponse{
145147
PerPage: perPage,
146148
Page: page,
147149
TotalPages: totalPages,
148-
Total: int64(usageSummary.NumRecordsInRange),
150+
Total: int64(usageSummary.NumberOfRecords),
149151
}
150152

151153
return &v1.ListUsageResponse{
152-
UsageEntries: usageData,
153-
CreditBalanceAtStart: usageSummary.CreditCentsBalanceAtStart.ToCredits(),
154-
CreditBalanceAtEnd: usageSummary.CreditCentsBalanceAtEnd.ToCredits(),
155-
Pagination: &pagination,
154+
UsageEntries: usageData,
155+
CreditsUsed: usageSummary.CreditCentsUsed.ToCredits(),
156+
Pagination: &pagination,
156157
}, nil
157158
}
158159

components/usage/pkg/apiv1/usage_test.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -313,15 +313,14 @@ func TestListUsage(t *testing.T) {
313313
tests := []struct {
314314
start, end time.Time
315315
// expectations
316-
creditsAtStart float64
317-
creditsAtEnd float64
316+
creditsUsed float64
318317
recordsInRange int64
319318
}{
320-
{start, end, 3, 10, 2},
321-
{end, end, 10, 10, 0},
322-
{start, start, 3, 3, 0},
323-
{start.Add(-200 * 24 * time.Hour), end, 0, 10, 4},
324-
{start.Add(-200 * 24 * time.Hour), end.Add(10 * 24 * time.Hour), 0, 20, 5},
319+
{start, end, 7, 2},
320+
{end, end, 0, 0},
321+
{start, start, 0, 0},
322+
{start.Add(-200 * 24 * time.Hour), end, 10, 4},
323+
{start.Add(-200 * 24 * time.Hour), end.Add(10 * 24 * time.Hour), 20, 5},
325324
}
326325

327326
for i, test := range tests {
@@ -338,8 +337,7 @@ func TestListUsage(t *testing.T) {
338337
})
339338
require.NoError(t, err)
340339

341-
require.Equal(t, test.creditsAtStart, metaData.CreditBalanceAtStart)
342-
require.Equal(t, test.creditsAtEnd, metaData.CreditBalanceAtEnd)
340+
require.Equal(t, test.creditsUsed, metaData.CreditsUsed)
343341
require.Equal(t, test.recordsInRange, metaData.Pagination.Total)
344342
})
345343
}

components/usage/pkg/db/cost_center.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,19 +158,19 @@ func (c *CostCenterManager) UpdateCostCenter(ctx context.Context, costCenter Cos
158158

159159
func (c *CostCenterManager) ComputeInvoiceUsageRecord(ctx context.Context, attributionID AttributionID) (*Usage, error) {
160160
now := time.Now()
161-
summary, err := GetUsageSummary(ctx, c.conn, attributionID, now, now, false)
161+
creditCents, err := GetBalance(ctx, c.conn, attributionID)
162162
if err != nil {
163163
return nil, err
164164
}
165-
if summary.CreditCentsBalanceAtEnd.ToCredits() <= 0 {
165+
if creditCents.ToCredits() <= 0 {
166166
// account has no debt, do nothing
167167
return nil, nil
168168
}
169169
return &Usage{
170170
ID: uuid.New(),
171171
AttributionID: attributionID,
172172
Description: "Credits",
173-
CreditCents: summary.CreditCentsBalanceAtEnd * -1,
173+
CreditCents: creditCents * -1,
174174
EffectiveTime: NewVarcharTime(now),
175175
Kind: InvoiceUsageKind,
176176
Draft: false,

components/usage/pkg/db/usage.go

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package db
66

77
import (
88
"context"
9-
"database/sql"
109
"encoding/json"
1110
"fmt"
1211
"math"
@@ -139,7 +138,8 @@ func FindUsage(ctx context.Context, conn *gorm.DB, params *FindUsageParams) ([]U
139138

140139
db := conn.WithContext(ctx).
141140
Where("attributionId = ?", params.AttributionId).
142-
Where("effectiveTime >= ? AND effectiveTime < ?", TimeToISO8601(params.From), TimeToISO8601(params.To))
141+
Where("effectiveTime >= ? AND effectiveTime < ?", TimeToISO8601(params.From), TimeToISO8601(params.To)).
142+
Where("kind = ?", WorkspaceInstanceUsageKind)
143143
if params.ExcludeDrafts {
144144
db = db.Where("draft = ?", false)
145145
}
@@ -158,48 +158,37 @@ func FindUsage(ctx context.Context, conn *gorm.DB, params *FindUsageParams) ([]U
158158
if result.Error != nil {
159159
return nil, fmt.Errorf("failed to get usage records: %s", result.Error)
160160
}
161+
161162
return usageRecords, nil
162163
}
163164

164-
type UsageSummary struct {
165-
NumRecordsInRange int
166-
CreditCentsBalanceAtStart CreditCents
167-
CreditCentsBalanceAtEnd CreditCents
165+
type GetUsageSummaryParams struct {
166+
AttributionId AttributionID
167+
From, To time.Time
168+
ExcludeDrafts bool
169+
}
170+
171+
type GetUsageSummaryResponse struct {
172+
CreditCentsUsed CreditCents
173+
NumberOfRecords int
168174
}
169175

170-
func GetUsageSummary(ctx context.Context, conn *gorm.DB, attributionId AttributionID, from, to time.Time, excludeDrafts bool) (*UsageSummary, error) {
176+
func GetUsageSummary(ctx context.Context, conn *gorm.DB, params GetUsageSummaryParams) (GetUsageSummaryResponse, error) {
171177
db := conn.WithContext(ctx)
172178
query1 := db.Table((&Usage{}).TableName()).
173-
Select("sum(creditCents) as creditCentsBalanceAtStart").
174-
Where("attributionId = ?", attributionId).
175-
Where("effectiveTime < ?", TimeToISO8601(from))
176-
if excludeDrafts {
179+
Select("sum(creditCents) as CreditCentsUsed, count(*) as NumberOfRecords").
180+
Where("attributionId = ?", params.AttributionId).
181+
Where("effectiveTime >= ? AND effectiveTime < ?", TimeToISO8601(params.From), TimeToISO8601(params.To)).
182+
Where("kind = ?", WorkspaceInstanceUsageKind)
183+
if params.ExcludeDrafts {
177184
query1 = query1.Where("draft = ?", false)
178185
}
179-
var creditCentsBalanceAtStart sql.NullInt64
180-
err := query1.Row().Scan(&creditCentsBalanceAtStart)
181-
if err != nil {
182-
return nil, fmt.Errorf("failed to get usage meta data: %s", err)
183-
}
184-
185-
query2 := db.Table((&Usage{}).TableName()).
186-
Select("sum(creditCents) as creditCentsBalanceInPeriod", "count(id) as numRecordsInRange").
187-
Where("attributionId = ?", attributionId).
188-
Where("? <= effectiveTime AND effectiveTime < ?", TimeToISO8601(from), TimeToISO8601(to))
189-
if excludeDrafts {
190-
query2 = query2.Where("draft = ?", false)
191-
}
192-
var creditCentsBalanceInPeriod sql.NullInt64
193-
var numRecordsInRange sql.NullInt32
194-
err = query2.Row().Scan(&creditCentsBalanceInPeriod, &numRecordsInRange)
186+
var result GetUsageSummaryResponse
187+
err := query1.Find(&result).Error
195188
if err != nil {
196-
return nil, fmt.Errorf("failed to get usage meta data: %s", err)
189+
return result, fmt.Errorf("failed to get usage meta data: %w", err)
197190
}
198-
return &UsageSummary{
199-
NumRecordsInRange: int(numRecordsInRange.Int32),
200-
CreditCentsBalanceAtStart: CreditCents(creditCentsBalanceAtStart.Int64),
201-
CreditCentsBalanceAtEnd: CreditCents(creditCentsBalanceAtStart.Int64 + creditCentsBalanceInPeriod.Int64),
202-
}, nil
191+
return result, nil
203192
}
204193

205194
type Balance struct {

components/usage/pkg/db/usage_test.go

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -93,33 +93,44 @@ func TestGetUsageSummary(t *testing.T) {
9393
CreditCents: 1000,
9494
})
9595

96-
dbtest.CreateUsageRecords(t, conn, draftBefore, nondraftBefore, draftInside, nonDraftInside, nonDraftAfter)
96+
invoice := dbtest.NewUsage(t, db.Usage{
97+
AttributionID: attributionID,
98+
Kind: db.InvoiceUsageKind,
99+
EffectiveTime: db.NewVarcharTime(start.Add(2 * time.Hour)),
100+
CreditCents: -400,
101+
Draft: false,
102+
})
103+
104+
dbtest.CreateUsageRecords(t, conn, draftBefore, nondraftBefore, draftInside, nonDraftInside, nonDraftAfter, invoice)
97105

98106
tests := []struct {
99107
start, end time.Time
100108
excludeDrafts bool
101109
// expectations
102-
creditsAtStart int64
103-
creditsAtEnd int64
104-
recordsInRange int
110+
creditCents db.CreditCents
111+
numberOfRecords int
105112
}{
106-
{start, end, false, 3, 10, 2},
107-
{start, end, true, 2, 6, 1},
108-
{end, end, false, 10, 10, 0},
109-
{end, end, true, 6, 6, 0},
110-
{start, start, false, 3, 3, 0},
111-
{start.Add(-500 * 24 * time.Hour), end, false, 0, 10, 4},
112-
{start.Add(-500 * 24 * time.Hour), end.Add(500 * 24 * time.Hour), false, 0, 20, 5},
113+
{start, end, false, 700, 2},
114+
{start, end, true, 400, 1},
115+
{end, end, false, 0, 0},
116+
{end, end, true, 0, 0},
117+
{start, start, false, 0, 0},
118+
{start.Add(-500 * 24 * time.Hour), end, false, 1000, 4},
119+
{start.Add(-500 * 24 * time.Hour), end.Add(500 * 24 * time.Hour), false, 2000, 5},
113120
}
114121

115122
for i, test := range tests {
116123
t.Run(fmt.Sprintf("Running test no %d", i+1), func(t *testing.T) {
117-
metaData, err := db.GetUsageSummary(context.Background(), conn, attributionID, test.start, test.end, test.excludeDrafts)
124+
usageSummary, err := db.GetUsageSummary(context.Background(), conn, db.GetUsageSummaryParams{
125+
AttributionId: attributionID,
126+
From: test.start,
127+
To: test.end,
128+
ExcludeDrafts: test.excludeDrafts,
129+
})
118130
require.NoError(t, err)
119131

120-
require.EqualValues(t, test.creditsAtStart, metaData.CreditCentsBalanceAtStart.ToCredits())
121-
require.EqualValues(t, test.creditsAtEnd, metaData.CreditCentsBalanceAtEnd.ToCredits())
122-
require.Equal(t, test.recordsInRange, metaData.NumRecordsInRange)
132+
require.EqualValues(t, test.creditCents, usageSummary.CreditCentsUsed)
133+
require.EqualValues(t, test.numberOfRecords, usageSummary.NumberOfRecords)
123134
})
124135
}
125136
}
@@ -326,6 +337,7 @@ func TestListBalance(t *testing.T) {
326337
func TestGetBalance(t *testing.T) {
327338
teamAttributionID := db.NewTeamAttributionID(uuid.New().String())
328339
userAttributionID := db.NewUserAttributionID(uuid.New().String())
340+
noUsageAttributionID := db.NewUserAttributionID(uuid.New().String())
329341

330342
conn := dbtest.ConnectForTests(t)
331343
dbtest.CreateUsageRecords(t, conn,
@@ -355,4 +367,8 @@ func TestGetBalance(t *testing.T) {
355367
userBalance, err := db.GetBalance(context.Background(), conn, userAttributionID)
356368
require.NoError(t, err)
357369
require.EqualValues(t, -50, int(userBalance))
370+
371+
noUsageBalance, err := db.GetBalance(context.Background(), conn, noUsageAttributionID)
372+
require.NoError(t, err)
373+
require.EqualValues(t, 0, int(noUsageBalance))
358374
}

0 commit comments

Comments
 (0)