feat: add has-ai-task filters to the /workspaces and /templates endpoints (#18387)

This PR allows filtering templates and workspaces with the `has-ai-task`
filter as described in the [Coder Tasks
RFC](https://www.notion.so/coderhq/Coder-Tasks-207d579be5928053ab68c8d9a4b59eaa?source=copy_link#20ad579be59280e6a000eb0646d3c2df).
This commit is contained in:
Hugo Dutka
2025-06-18 18:22:45 +02:00
committed by GitHub
parent 56ff0fb65a
commit 591f5db5f6
16 changed files with 431 additions and 51 deletions

View File

@ -1389,6 +1389,17 @@ func isDeprecated(template database.Template) bool {
return template.Deprecated != ""
}
func (q *FakeQuerier) getWorkspaceBuildParametersNoLock(workspaceBuildID uuid.UUID) ([]database.WorkspaceBuildParameter, error) {
params := make([]database.WorkspaceBuildParameter, 0)
for _, param := range q.workspaceBuildParameters {
if param.WorkspaceBuildID != workspaceBuildID {
continue
}
params = append(params, param)
}
return params, nil
}
func (*FakeQuerier) AcquireLock(_ context.Context, _ int64) error {
return xerrors.New("AcquireLock must only be called within a transaction")
}
@ -7898,14 +7909,7 @@ func (q *FakeQuerier) GetWorkspaceBuildParameters(_ context.Context, workspaceBu
q.mutex.RLock()
defer q.mutex.RUnlock()
params := make([]database.WorkspaceBuildParameter, 0)
for _, param := range q.workspaceBuildParameters {
if param.WorkspaceBuildID != workspaceBuildID {
continue
}
params = append(params, param)
}
return params, nil
return q.getWorkspaceBuildParametersNoLock(workspaceBuildID)
}
func (q *FakeQuerier) GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow, error) {
@ -13233,6 +13237,18 @@ func (q *FakeQuerier) GetAuthorizedTemplates(ctx context.Context, arg database.G
continue
}
}
if arg.HasAITask.Valid {
tv, err := q.getTemplateVersionByIDNoLock(ctx, template.ActiveVersionID)
if err != nil {
return nil, xerrors.Errorf("get template version: %w", err)
}
tvHasAITask := tv.HasAITask.Valid && tv.HasAITask.Bool
if tvHasAITask != arg.HasAITask.Bool {
continue
}
}
templates = append(templates, template)
}
if len(templates) > 0 {
@ -13562,6 +13578,43 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
}
}
if arg.HasAITask.Valid {
hasAITask, err := func() (bool, error) {
build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID)
if err != nil {
return false, xerrors.Errorf("get latest build: %w", err)
}
if build.HasAITask.Valid {
return build.HasAITask.Bool, nil
}
// If the build has a nil AI task, check if the job is in progress
// and if it has a non-empty AI Prompt parameter
job, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID)
if err != nil {
return false, xerrors.Errorf("get provisioner job: %w", err)
}
if job.CompletedAt.Valid {
return false, nil
}
parameters, err := q.getWorkspaceBuildParametersNoLock(build.ID)
if err != nil {
return false, xerrors.Errorf("get workspace build parameters: %w", err)
}
for _, param := range parameters {
if param.Name == "AI Prompt" && param.Value != "" {
return true, nil
}
}
return false, nil
}()
if err != nil {
return nil, xerrors.Errorf("get hasAITask: %w", err)
}
if hasAITask != arg.HasAITask.Bool {
continue
}
}
// If the filter exists, ensure the object is authorized.
if prepared != nil && prepared.Authorize(ctx, workspace.RBACObject()) != nil {
continue