feat(coderd): add workspace timings endpoint (#14648)

This commit is contained in:
Bruno Quaresma
2024-09-16 16:31:05 -03:00
committed by GitHub
parent c330af0e4d
commit 705b9ccda8
19 changed files with 640 additions and 2 deletions

View File

@ -3556,3 +3556,169 @@ func TestWorkspaceNotifications(t *testing.T) {
})
})
}
func TestWorkspaceTimings(t *testing.T) {
t.Parallel()
// Setup a base template for the workspaces
db, pubsub := dbtestutil.NewDB(t)
client := coderdtest.New(t, &coderdtest.Options{
Database: db,
Pubsub: pubsub,
})
owner := coderdtest.CreateFirstUser(t, client)
file := dbgen.File(t, db, database.File{
CreatedBy: owner.UserID,
})
versionJob := dbgen.ProvisionerJob(t, db, pubsub, database.ProvisionerJob{
OrganizationID: owner.OrganizationID,
InitiatorID: owner.UserID,
WorkerID: uuid.NullUUID{},
FileID: file.ID,
Tags: database.StringMap{
"custom": "true",
},
})
version := dbgen.TemplateVersion(t, db, database.TemplateVersion{
OrganizationID: owner.OrganizationID,
JobID: versionJob.ID,
CreatedBy: owner.UserID,
})
template := dbgen.Template(t, db, database.Template{
OrganizationID: owner.OrganizationID,
ActiveVersionID: version.ID,
CreatedBy: owner.UserID,
})
// Since the tests run in parallel, we need to create a new workspace for
// each test to avoid fetching the wrong latest build.
type workspaceWithBuild struct {
database.Workspace
build database.WorkspaceBuild
}
makeWorkspace := func() workspaceWithBuild {
ws := dbgen.Workspace(t, db, database.Workspace{
OwnerID: owner.UserID,
OrganizationID: owner.OrganizationID,
TemplateID: template.ID,
// Generate unique name for the workspace
Name: "test-workspace-" + uuid.New().String(),
})
jobID := uuid.New()
job := dbgen.ProvisionerJob(t, db, pubsub, database.ProvisionerJob{
ID: jobID,
OrganizationID: owner.OrganizationID,
Type: database.ProvisionerJobTypeWorkspaceBuild,
Tags: database.StringMap{jobID.String(): "true"},
})
build := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
WorkspaceID: ws.ID,
TemplateVersionID: version.ID,
BuildNumber: 1,
Transition: database.WorkspaceTransitionStart,
InitiatorID: owner.UserID,
JobID: job.ID,
})
return workspaceWithBuild{
Workspace: ws,
build: build,
}
}
makeProvisionerTimings := func(jobID uuid.UUID, count int) []database.ProvisionerJobTiming {
// Use the database.ProvisionerJobTiming struct to mock timings data instead
// of directly creating database.InsertProvisionerJobTimingsParams. This
// approach makes the mock data easier to understand, as
// database.InsertProvisionerJobTimingsParams requires slices of each field
// for batch inserts.
timings := make([]database.ProvisionerJobTiming, count)
now := time.Now()
for i := range count {
startedAt := now.Add(-time.Hour + time.Duration(i)*time.Minute)
endedAt := startedAt.Add(time.Minute)
timings[i] = database.ProvisionerJobTiming{
StartedAt: startedAt,
EndedAt: endedAt,
Stage: database.ProvisionerJobTimingStageInit,
Action: string(database.AuditActionCreate),
Source: "source",
Resource: fmt.Sprintf("resource[%d]", i),
}
}
insertParams := database.InsertProvisionerJobTimingsParams{
JobID: jobID,
}
for _, timing := range timings {
insertParams.StartedAt = append(insertParams.StartedAt, timing.StartedAt)
insertParams.EndedAt = append(insertParams.EndedAt, timing.EndedAt)
insertParams.Stage = append(insertParams.Stage, timing.Stage)
insertParams.Action = append(insertParams.Action, timing.Action)
insertParams.Source = append(insertParams.Source, timing.Source)
insertParams.Resource = append(insertParams.Resource, timing.Resource)
}
return dbgen.ProvisionerJobTimings(t, db, insertParams)
}
// Given
testCases := []struct {
name string
provisionerTimings int
workspace workspaceWithBuild
error bool
}{
{
name: "workspace with 5 provisioner timings",
provisionerTimings: 5,
workspace: makeWorkspace(),
},
{
name: "workspace with 2 provisioner timings",
provisionerTimings: 2,
workspace: makeWorkspace(),
},
{
name: "workspace with 0 provisioner timings",
provisionerTimings: 0,
workspace: makeWorkspace(),
},
{
name: "workspace not found",
provisionerTimings: 0,
workspace: workspaceWithBuild{},
error: true,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Generate timings based on test config
generatedTimings := makeProvisionerTimings(tc.workspace.build.JobID, tc.provisionerTimings)
res, err := client.WorkspaceTimings(context.Background(), tc.workspace.ID)
// When error is expected, than an error is returned
if tc.error {
require.Error(t, err)
return
}
// When success is expected, than no error is returned and the length and
// fields are correctly returned
require.NoError(t, err)
require.Len(t, res.ProvisionerTimings, tc.provisionerTimings)
for i := range res.ProvisionerTimings {
timingRes := res.ProvisionerTimings[i]
genTiming := generatedTimings[i]
require.Equal(t, genTiming.Resource, timingRes.Resource)
require.Equal(t, genTiming.Action, timingRes.Action)
require.Equal(t, string(genTiming.Stage), timingRes.Stage)
require.Equal(t, genTiming.JobID.String(), timingRes.JobID.String())
require.Equal(t, genTiming.Source, timingRes.Source)
require.Equal(t, genTiming.StartedAt.UnixMilli(), timingRes.StartedAt.UnixMilli())
require.Equal(t, genTiming.EndedAt.UnixMilli(), timingRes.EndedAt.UnixMilli())
}
})
}
}