Skip to content

Commit c2767e6

Browse files
committed
Added Usage table
1 parent ad355c4 commit c2767e6

File tree

4 files changed

+217
-0
lines changed

4 files changed

+217
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { MigrationInterface, QueryRunner } from "typeorm";
8+
9+
export class AddUsageTable1662040283793 implements MigrationInterface {
10+
public async up(queryRunner: QueryRunner): Promise<void> {
11+
await queryRunner.query(
12+
`CREATE TABLE \`d_b_usage\` (
13+
\`id\` char(36) NOT NULL,
14+
\`attributionId\` varchar(255) NOT NULL,
15+
\`description\` varchar(255) NOT NULL,
16+
\`credits\` double NOT NULL,
17+
\`effectiveTime\` varchar(255) NOT NULL,
18+
\`kind\` varchar(255) NOT NULL,
19+
\`workspaceInstanceId\` char(36) NULL,
20+
\`status\` varchar(255) NOT NULL DEFAULT 'finalized',
21+
\`metadata\` text NULL,
22+
\`_lastModified\` timestamp(6) NOT NULL,
23+
24+
INDEX \`IDX_usage__attribution_id\` (\`attributionId\`),
25+
INDEX \`IDX_usage_workspaceInstanceId\` (\`workspaceInstanceId\`),
26+
INDEX \`IDX_usage_status\` (\`status\`),
27+
PRIMARY KEY (\`id\`)
28+
) ENGINE=InnoDB`,
29+
);
30+
}
31+
32+
public async down(queryRunner: QueryRunner): Promise<void> {
33+
await queryRunner.query(`DROP INDEX \`IDX_usage__attribution_id\` ON \`d_b_usage\``);
34+
await queryRunner.query(`DROP INDEX \`IDX_usage_workspaceInstanceId\` ON \`d_b_usage\``);
35+
await queryRunner.query(`DROP INDEX \`IDX_usage_status\` ON \`d_b_usage\``);
36+
await queryRunner.query(`DROP TABLE \`d_b_usage\``);
37+
}
38+
}
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package dbtest
6+
7+
import (
8+
"testing"
9+
10+
"github.com/gitpod-io/gitpod/usage/pkg/db"
11+
"github.com/google/uuid"
12+
"github.com/stretchr/testify/require"
13+
"gorm.io/gorm"
14+
)
15+
16+
func NewUsage(t *testing.T, record db.Usage) db.Usage {
17+
t.Helper()
18+
19+
result := db.Usage{
20+
ID: uuid.New(),
21+
AttributionID: db.NewUserAttributionID(uuid.New().String()),
22+
Description: "some description",
23+
Credits: 42,
24+
EffectiveTime: db.VarcharTime{},
25+
Kind: "workspaceinstance",
26+
WorkspaceInstanceID: uuid.New(),
27+
Status: "finalized",
28+
}
29+
30+
if record.ID.ID() != 0 {
31+
result.ID = record.ID
32+
}
33+
if record.EffectiveTime.IsSet() {
34+
result.EffectiveTime = record.EffectiveTime
35+
}
36+
if record.AttributionID != "" {
37+
result.AttributionID = record.AttributionID
38+
}
39+
if record.Description != "" {
40+
result.Description = record.Description
41+
}
42+
if record.Credits != 0 {
43+
result.Credits = record.Credits
44+
}
45+
if record.WorkspaceInstanceID.ID() != 0 {
46+
result.WorkspaceInstanceID = record.WorkspaceInstanceID
47+
}
48+
if record.Status != "" {
49+
result.Status = record.Status
50+
}
51+
if record.Metadata != nil {
52+
result.Metadata = record.Metadata
53+
}
54+
return result
55+
}
56+
57+
func CreateUsageRecords(t *testing.T, conn *gorm.DB, entries ...db.Usage) []db.Usage {
58+
t.Helper()
59+
60+
var records []db.Usage
61+
var ids []string
62+
for _, usageEntry := range entries {
63+
record := NewUsage(t, usageEntry)
64+
records = append(records, record)
65+
ids = append(ids, record.ID.String())
66+
}
67+
68+
require.NoError(t, conn.CreateInBatches(&records, 1000).Error)
69+
70+
t.Cleanup(func() {
71+
require.NoError(t, conn.Where(ids).Delete(&db.Usage{}).Error)
72+
})
73+
74+
t.Logf("stored %d", len(entries))
75+
76+
return records
77+
}

components/usage/pkg/db/usage.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package db
6+
7+
import (
8+
"context"
9+
"fmt"
10+
11+
"github.com/google/uuid"
12+
"gorm.io/datatypes"
13+
"gorm.io/gorm"
14+
)
15+
16+
type Usage struct {
17+
ID uuid.UUID `gorm:"primary_key;column:id;type:char;size:36;" json:"id"`
18+
AttributionID AttributionID `gorm:"column:attributionId;type:varchar;size:255;" json:"attributionId"`
19+
Description string `gorm:"column:description;type:varchar;size:255;" json:"description"`
20+
Credits float64 `gorm:"column:credits;type:double;" json:"credits"`
21+
EffectiveTime VarcharTime `gorm:"column:effectiveTime;type:varchar;size:255;" json:"effectiveTime"`
22+
Kind string `gorm:"column:kind;type:char;size:10;" json:"kind"`
23+
WorkspaceInstanceID uuid.UUID `gorm:"column:workspaceInstanceId;type:char;size:36;" json:"workspaceInstanceId"`
24+
Status string `gorm:"column:status;type:char;size:10;" json:"status"`
25+
Metadata datatypes.JSON `gorm:"column:metadata;type:text;size:65535" json:"metadata"`
26+
}
27+
28+
type FindUsageResult struct {
29+
UsageEntries []Usage
30+
}
31+
32+
// TableName sets the insert table name for this struct type
33+
func (u *Usage) TableName() string {
34+
return "d_b_usage"
35+
}
36+
37+
func FindUsage(ctx context.Context, conn *gorm.DB, attributionId AttributionID, from, to VarcharTime, offset int64, limit int64) ([]Usage, error) {
38+
db := conn.WithContext(ctx)
39+
40+
var usageRecords []Usage
41+
result := db.
42+
WithContext(ctx).
43+
Table((&Usage{}).TableName()).
44+
Where("attributionId = ?", attributionId).
45+
Where("? <= effectiveTime AND effectiveTime < ?", from.String(), to.String()).
46+
Order("effectiveTime DESC").
47+
Offset(int(offset)).
48+
Limit(int(limit)).
49+
Find(&usageRecords)
50+
if result.Error != nil {
51+
return nil, fmt.Errorf("failed to get usage records: %s", result.Error)
52+
}
53+
return usageRecords, nil
54+
}

components/usage/pkg/db/usage_test.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package db_test
6+
7+
import (
8+
"context"
9+
"testing"
10+
"time"
11+
12+
"github.com/gitpod-io/gitpod/usage/pkg/db"
13+
"github.com/gitpod-io/gitpod/usage/pkg/db/dbtest"
14+
"github.com/google/uuid"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func TestFindUsageInRange(t *testing.T) {
19+
conn := dbtest.ConnectForTests(t)
20+
21+
start := time.Date(2022, 7, 1, 0, 0, 0, 0, time.UTC)
22+
end := time.Date(2022, 8, 1, 0, 0, 0, 0, time.UTC)
23+
24+
attributionID := db.NewTeamAttributionID(uuid.New().String())
25+
26+
entryBefore := dbtest.NewUsage(t, db.Usage{
27+
AttributionID: attributionID,
28+
EffectiveTime: db.NewVarcharTime(start.Add(-1 * 23 * time.Hour)),
29+
})
30+
31+
entryInside := dbtest.NewUsage(t, db.Usage{
32+
AttributionID: attributionID,
33+
EffectiveTime: db.NewVarcharTime(start.Add(2 * time.Minute)),
34+
})
35+
36+
entryAfter := dbtest.NewUsage(t, db.Usage{
37+
AttributionID: attributionID,
38+
EffectiveTime: db.NewVarcharTime(end.Add(2 * time.Hour)),
39+
})
40+
41+
usageEntries := []db.Usage{entryBefore, entryInside, entryAfter}
42+
dbtest.CreateUsageRecords(t, conn, usageEntries...)
43+
listResult, err := db.FindUsage(context.Background(), conn, attributionID, db.NewVarcharTime(start), db.NewVarcharTime(end), 0, 10)
44+
require.NoError(t, err)
45+
46+
require.Equal(t, 1, len(listResult))
47+
require.Equal(t, []db.Usage{entryInside}, listResult)
48+
}

0 commit comments

Comments
 (0)