feat(coderd): use task data model for list (#20394)

Updates coder/internal#976
This commit is contained in:
Mathias Fredriksson
2025-10-23 20:22:51 +03:00
committed by GitHub
parent 2c6cbf15e2
commit a106d67c07
28 changed files with 985 additions and 622 deletions
+62 -7
View File
@@ -120,19 +120,23 @@ func (b WorkspaceBuildBuilder) WithAgent(mutations ...func([]*sdkproto.Agent) []
}
func (b WorkspaceBuildBuilder) WithTask(seed *sdkproto.App) WorkspaceBuildBuilder {
//nolint: revive // returns modified struct
b.taskAppID = uuid.New()
if seed == nil {
seed = &sdkproto.App{}
}
var err error
//nolint: revive // returns modified struct
b.taskAppID, err = uuid.Parse(takeFirst(seed.Id, uuid.NewString()))
require.NoError(b.t, err)
return b.Params(database.WorkspaceBuildParameter{
Name: codersdk.AITaskPromptParameterName,
Value: "list me",
}).WithAgent(func(a []*sdkproto.Agent) []*sdkproto.Agent {
a[0].Apps = []*sdkproto.App{
{
Id: takeFirst(seed.Id, b.taskAppID.String()),
Slug: takeFirst(seed.Slug, "vcode"),
Id: b.taskAppID.String(),
Slug: takeFirst(seed.Slug, "task-app"),
Url: takeFirst(seed.Url, ""),
},
}
@@ -195,11 +199,11 @@ func (b WorkspaceBuildBuilder) Do() WorkspaceResponse {
if b.ws.ID == uuid.Nil {
// nolint: revive
b.ws = dbgen.Workspace(b.t, b.db, b.ws)
resp.Workspace = b.ws
b.logger.Debug(context.Background(), "created workspace",
slog.F("name", resp.Workspace.Name),
slog.F("workspace_id", resp.Workspace.ID))
slog.F("name", b.ws.Name),
slog.F("workspace_id", b.ws.ID))
}
resp.Workspace = b.ws
b.seed.WorkspaceID = b.ws.ID
b.seed.InitiatorID = takeFirst(b.seed.InitiatorID, b.ws.OwnerID)
@@ -273,6 +277,30 @@ func (b WorkspaceBuildBuilder) Do() WorkspaceResponse {
slog.F("workspace_id", resp.Workspace.ID),
slog.F("build_number", resp.Build.BuildNumber))
// If this is a task workspace, link it to the workspace build.
task, err := b.db.GetTaskByWorkspaceID(ownerCtx, resp.Workspace.ID)
if err != nil {
if b.taskAppID != uuid.Nil {
require.Fail(b.t, "task app configured but failed to get task by workspace id", err)
}
} else {
if b.taskAppID == uuid.Nil {
require.Fail(b.t, "task app not configured but workspace is a task workspace")
}
app := mustWorkspaceAppByWorkspaceAndBuildAndAppID(ownerCtx, b.t, b.db, resp.Workspace.ID, resp.Build.BuildNumber, b.taskAppID)
_, err = b.db.UpsertTaskWorkspaceApp(ownerCtx, database.UpsertTaskWorkspaceAppParams{
TaskID: task.ID,
WorkspaceBuildNumber: resp.Build.BuildNumber,
WorkspaceAgentID: uuid.NullUUID{UUID: app.AgentID, Valid: true},
WorkspaceAppID: uuid.NullUUID{UUID: app.ID, Valid: true},
})
require.NoError(b.t, err, "upsert task workspace app")
b.logger.Debug(context.Background(), "linked task to workspace build",
slog.F("task_id", task.ID),
slog.F("build_number", resp.Build.BuildNumber))
}
for i := range b.params {
b.params[i].WorkspaceBuildID = resp.Build.ID
}
@@ -623,3 +651,30 @@ func takeFirst[Value comparable](values ...Value) Value {
return v != empty
})
}
// mustWorkspaceAppByWorkspaceAndBuildAndAppID finds a workspace app by
// workspace ID, build number, and app ID. It returns the workspace app
// if found, otherwise fails the test.
func mustWorkspaceAppByWorkspaceAndBuildAndAppID(ctx context.Context, t testing.TB, db database.Store, workspaceID uuid.UUID, buildNumber int32, appID uuid.UUID) database.WorkspaceApp {
t.Helper()
agents, err := db.GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx, database.GetWorkspaceAgentsByWorkspaceAndBuildNumberParams{
WorkspaceID: workspaceID,
BuildNumber: buildNumber,
})
require.NoError(t, err, "get workspace agents")
require.NotEmpty(t, agents, "no agents found for workspace")
for _, agent := range agents {
apps, err := db.GetWorkspaceAppsByAgentID(ctx, agent.ID)
require.NoError(t, err, "get workspace apps")
for _, app := range apps {
if app.ID == appID {
return app
}
}
}
require.FailNow(t, "could not find workspace app", "workspaceID=%s buildNumber=%d appID=%s", workspaceID, buildNumber, appID)
return database.WorkspaceApp{} // Unreachable.
}
+3 -1
View File
@@ -12717,16 +12717,18 @@ SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, t
WHERE tws.deleted_at IS NULL
AND CASE WHEN $1::UUID != '00000000-0000-0000-0000-000000000000' THEN tws.owner_id = $1::UUID ELSE TRUE END
AND CASE WHEN $2::UUID != '00000000-0000-0000-0000-000000000000' THEN tws.organization_id = $2::UUID ELSE TRUE END
AND CASE WHEN $3::text != '' THEN tws.status = $3::task_status ELSE TRUE END
ORDER BY tws.created_at DESC
`
type ListTasksParams struct {
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
Status string `db:"status" json:"status"`
}
func (q *sqlQuerier) ListTasks(ctx context.Context, arg ListTasksParams) ([]Task, error) {
rows, err := q.db.QueryContext(ctx, listTasks, arg.OwnerID, arg.OrganizationID)
rows, err := q.db.QueryContext(ctx, listTasks, arg.OwnerID, arg.OrganizationID, arg.Status)
if err != nil {
return nil, err
}
+1
View File
@@ -46,6 +46,7 @@ SELECT * FROM tasks_with_status tws
WHERE tws.deleted_at IS NULL
AND CASE WHEN @owner_id::UUID != '00000000-0000-0000-0000-000000000000' THEN tws.owner_id = @owner_id::UUID ELSE TRUE END
AND CASE WHEN @organization_id::UUID != '00000000-0000-0000-0000-000000000000' THEN tws.organization_id = @organization_id::UUID ELSE TRUE END
AND CASE WHEN @status::text != '' THEN tws.status = @status::task_status ELSE TRUE END
ORDER BY tws.created_at DESC;
-- name: DeleteTask :one