Skip to content

Commit 89d0b7d

Browse files
svenefftingeroboquat
authored andcommitted
[admin] add admin endpoints for usage
1 parent b030e46 commit 89d0b7d

File tree

9 files changed

+552
-138
lines changed

9 files changed

+552
-138
lines changed

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

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

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

Lines changed: 38 additions & 0 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: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,16 @@ export interface ResetUsageRequest {
249249
export interface ResetUsageResponse {
250250
}
251251

252+
export interface AddUsageCreditNoteRequest {
253+
attributionId: string;
254+
credits: number;
255+
note: string;
256+
userId: string;
257+
}
258+
259+
export interface AddUsageCreditNoteResponse {
260+
}
261+
252262
function createBaseReconcileUsageRequest(): ReconcileUsageRequest {
253263
return { from: undefined, to: undefined };
254264
}
@@ -1244,6 +1254,121 @@ export const ResetUsageResponse = {
12441254
},
12451255
};
12461256

1257+
function createBaseAddUsageCreditNoteRequest(): AddUsageCreditNoteRequest {
1258+
return { attributionId: "", credits: 0, note: "", userId: "" };
1259+
}
1260+
1261+
export const AddUsageCreditNoteRequest = {
1262+
encode(message: AddUsageCreditNoteRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
1263+
if (message.attributionId !== "") {
1264+
writer.uint32(10).string(message.attributionId);
1265+
}
1266+
if (message.credits !== 0) {
1267+
writer.uint32(16).int32(message.credits);
1268+
}
1269+
if (message.note !== "") {
1270+
writer.uint32(26).string(message.note);
1271+
}
1272+
if (message.userId !== "") {
1273+
writer.uint32(34).string(message.userId);
1274+
}
1275+
return writer;
1276+
},
1277+
1278+
decode(input: _m0.Reader | Uint8Array, length?: number): AddUsageCreditNoteRequest {
1279+
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
1280+
let end = length === undefined ? reader.len : reader.pos + length;
1281+
const message = createBaseAddUsageCreditNoteRequest();
1282+
while (reader.pos < end) {
1283+
const tag = reader.uint32();
1284+
switch (tag >>> 3) {
1285+
case 1:
1286+
message.attributionId = reader.string();
1287+
break;
1288+
case 2:
1289+
message.credits = reader.int32();
1290+
break;
1291+
case 3:
1292+
message.note = reader.string();
1293+
break;
1294+
case 4:
1295+
message.userId = reader.string();
1296+
break;
1297+
default:
1298+
reader.skipType(tag & 7);
1299+
break;
1300+
}
1301+
}
1302+
return message;
1303+
},
1304+
1305+
fromJSON(object: any): AddUsageCreditNoteRequest {
1306+
return {
1307+
attributionId: isSet(object.attributionId) ? String(object.attributionId) : "",
1308+
credits: isSet(object.credits) ? Number(object.credits) : 0,
1309+
note: isSet(object.note) ? String(object.note) : "",
1310+
userId: isSet(object.userId) ? String(object.userId) : "",
1311+
};
1312+
},
1313+
1314+
toJSON(message: AddUsageCreditNoteRequest): unknown {
1315+
const obj: any = {};
1316+
message.attributionId !== undefined && (obj.attributionId = message.attributionId);
1317+
message.credits !== undefined && (obj.credits = Math.round(message.credits));
1318+
message.note !== undefined && (obj.note = message.note);
1319+
message.userId !== undefined && (obj.userId = message.userId);
1320+
return obj;
1321+
},
1322+
1323+
fromPartial(object: DeepPartial<AddUsageCreditNoteRequest>): AddUsageCreditNoteRequest {
1324+
const message = createBaseAddUsageCreditNoteRequest();
1325+
message.attributionId = object.attributionId ?? "";
1326+
message.credits = object.credits ?? 0;
1327+
message.note = object.note ?? "";
1328+
message.userId = object.userId ?? "";
1329+
return message;
1330+
},
1331+
};
1332+
1333+
function createBaseAddUsageCreditNoteResponse(): AddUsageCreditNoteResponse {
1334+
return {};
1335+
}
1336+
1337+
export const AddUsageCreditNoteResponse = {
1338+
encode(_: AddUsageCreditNoteResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
1339+
return writer;
1340+
},
1341+
1342+
decode(input: _m0.Reader | Uint8Array, length?: number): AddUsageCreditNoteResponse {
1343+
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
1344+
let end = length === undefined ? reader.len : reader.pos + length;
1345+
const message = createBaseAddUsageCreditNoteResponse();
1346+
while (reader.pos < end) {
1347+
const tag = reader.uint32();
1348+
switch (tag >>> 3) {
1349+
default:
1350+
reader.skipType(tag & 7);
1351+
break;
1352+
}
1353+
}
1354+
return message;
1355+
},
1356+
1357+
fromJSON(_: any): AddUsageCreditNoteResponse {
1358+
return {};
1359+
},
1360+
1361+
toJSON(_: AddUsageCreditNoteResponse): unknown {
1362+
const obj: any = {};
1363+
return obj;
1364+
},
1365+
1366+
fromPartial(_: DeepPartial<AddUsageCreditNoteResponse>): AddUsageCreditNoteResponse {
1367+
const message = createBaseAddUsageCreditNoteResponse();
1368+
return message;
1369+
},
1370+
};
1371+
12471372
export type UsageServiceDefinition = typeof UsageServiceDefinition;
12481373
export const UsageServiceDefinition = {
12491374
name: "UsageService",
@@ -1303,6 +1428,15 @@ export const UsageServiceDefinition = {
13031428
responseStream: false,
13041429
options: {},
13051430
},
1431+
/** AddUsageCreditNote adds a usage credit note to the given cost center with the effective date of now */
1432+
addUsageCreditNote: {
1433+
name: "AddUsageCreditNote",
1434+
requestType: AddUsageCreditNoteRequest,
1435+
requestStream: false,
1436+
responseType: AddUsageCreditNoteResponse,
1437+
responseStream: false,
1438+
options: {},
1439+
},
13061440
},
13071441
} as const;
13081442

@@ -1334,6 +1468,11 @@ export interface UsageServiceServiceImplementation<CallContextExt = {}> {
13341468
request: GetBalanceRequest,
13351469
context: CallContext & CallContextExt,
13361470
): Promise<DeepPartial<GetBalanceResponse>>;
1471+
/** AddUsageCreditNote adds a usage credit note to the given cost center with the effective date of now */
1472+
addUsageCreditNote(
1473+
request: AddUsageCreditNoteRequest,
1474+
context: CallContext & CallContextExt,
1475+
): Promise<DeepPartial<AddUsageCreditNoteResponse>>;
13371476
}
13381477

13391478
export interface UsageServiceClient<CallOptionsExt = {}> {
@@ -1364,6 +1503,11 @@ export interface UsageServiceClient<CallOptionsExt = {}> {
13641503
request: DeepPartial<GetBalanceRequest>,
13651504
options?: CallOptions & CallOptionsExt,
13661505
): Promise<GetBalanceResponse>;
1506+
/** AddUsageCreditNote adds a usage credit note to the given cost center with the effective date of now */
1507+
addUsageCreditNote(
1508+
request: DeepPartial<AddUsageCreditNoteRequest>,
1509+
options?: CallOptions & CallOptionsExt,
1510+
): Promise<AddUsageCreditNoteResponse>;
13671511
}
13681512

13691513
export interface DataLoaderOptions {

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ service UsageService {
2525

2626
// GetBalance returns the current credits balance for the given attributionId
2727
rpc GetBalance(GetBalanceRequest) returns (GetBalanceResponse) {}
28+
29+
// AddUsageCreditNote adds a usage credit note to the given cost center with the effective date of now
30+
rpc AddUsageCreditNote(AddUsageCreditNoteRequest) returns (AddUsageCreditNoteResponse) {}
2831
}
2932

3033
message ReconcileUsageRequest {
@@ -135,3 +138,15 @@ message CostCenter {
135138
message ResetUsageRequest {}
136139

137140
message ResetUsageResponse {}
141+
142+
message AddUsageCreditNoteRequest {
143+
string attribution_id = 1;
144+
// the amount of credits to add to the given account
145+
int32 credits = 2;
146+
// a human readable note for the reason this credit note exists
147+
string note = 3;
148+
// the id of the user (admin) who created the note
149+
string user_id = 4;
150+
}
151+
152+
message AddUsageCreditNoteResponse {}

components/usage/pkg/apiv1/usage.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"fmt"
1010
"math"
11+
"strings"
1112
"time"
1213

1314
"github.com/google/uuid"
@@ -453,3 +454,48 @@ func NewUsageService(conn *gorm.DB, pricer *WorkspacePricer, costCenterManager *
453454
pricer: pricer,
454455
}
455456
}
457+
458+
func (s *UsageService) AddUsageCreditNote(ctx context.Context, req *v1.AddUsageCreditNoteRequest) (*v1.AddUsageCreditNoteResponse, error) {
459+
log.Log.
460+
WithField("attribution_id", req.AttributionId).
461+
WithField("credits", req.Credits).
462+
WithField("user", req.UserId).
463+
WithField("note", req.Note).
464+
Info("Adding usage credit note.")
465+
466+
attributionId, err := db.ParseAttributionID(req.AttributionId)
467+
if err != nil {
468+
return nil, status.Errorf(codes.InvalidArgument, "AttributionID '%s' couldn't be parsed (error: %s).", req.AttributionId, err)
469+
}
470+
471+
note := strings.TrimSpace(req.Note)
472+
if note == "" {
473+
return nil, status.Error(codes.InvalidArgument, "The note must not be empty.")
474+
}
475+
476+
userId, err := uuid.Parse(req.UserId)
477+
if err != nil {
478+
return nil, fmt.Errorf("The user id is not a valid UUID. %w", err)
479+
}
480+
481+
usage := db.Usage{
482+
ID: uuid.New(),
483+
AttributionID: attributionId,
484+
Description: note,
485+
CreditCents: db.NewCreditCents(float64(req.Credits * -1)),
486+
EffectiveTime: common_db.NewVarCharTime(time.Now()),
487+
Kind: db.CreditNoteKind,
488+
Draft: false,
489+
}
490+
491+
err = usage.SetCreditNoteMetaData(db.CreditNoteMetaData{UserId: userId.String()})
492+
if err != nil {
493+
return nil, err
494+
}
495+
496+
err = db.InsertUsage(ctx, s.conn, usage)
497+
if err != nil {
498+
return nil, err
499+
}
500+
return &v1.AddUsageCreditNoteResponse{}, nil
501+
}

components/usage/pkg/apiv1/usage_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,45 @@ func TestListUsage(t *testing.T) {
341341
}
342342

343343
}
344+
345+
func TestAddUSageCreditNote(t *testing.T) {
346+
conn := dbtest.ConnectForTests(t)
347+
348+
attributionID := db.NewTeamAttributionID(uuid.New().String())
349+
350+
usageService := newUsageService(t, conn)
351+
352+
tests := []struct {
353+
credits int32
354+
userId string
355+
note string
356+
// expectations
357+
expectedError bool
358+
}{
359+
{300, uuid.New().String(), "Something", false},
360+
{300, "bad-userid", "Something", true},
361+
{300, uuid.New().String(), " " /* no note */, true},
362+
{-300, uuid.New().String(), "Negative Balance", false},
363+
}
364+
365+
for i, test := range tests {
366+
t.Run(fmt.Sprintf("Running test no %d", i+1), func(t *testing.T) {
367+
_, err := usageService.AddUsageCreditNote(context.Background(), &v1.AddUsageCreditNoteRequest{
368+
AttributionId: string(attributionID),
369+
Credits: test.credits,
370+
Note: test.note,
371+
UserId: test.userId,
372+
})
373+
if test.expectedError {
374+
require.Error(t, err)
375+
} else {
376+
require.NoError(t, err)
377+
balance, err := db.GetBalance(context.Background(), conn, attributionID)
378+
require.NoError(t, err)
379+
require.Equal(t, int32(balance.ToCredits()), test.credits*-1)
380+
}
381+
require.NoError(t, conn.Where("attributionId = ?", attributionID).Delete(&db.Usage{}).Error)
382+
})
383+
}
384+
385+
}

0 commit comments

Comments
 (0)