Skip to content

Commit e0ba4ac

Browse files
committed
endpoint
1 parent 62455e7 commit e0ba4ac

File tree

8 files changed

+387
-8
lines changed

8 files changed

+387
-8
lines changed

coderd/database/dbmem/dbmem.go

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7894,10 +7894,7 @@ func (q *FakeQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(_ context.Con
78947894
return database.WorkspaceBuild{}, sql.ErrNoRows
78957895
}
78967896

7897-
func (q *FakeQuerier) GetWorkspaceBuildParameters(_ context.Context, workspaceBuildID uuid.UUID) ([]database.WorkspaceBuildParameter, error) {
7898-
q.mutex.RLock()
7899-
defer q.mutex.RUnlock()
7900-
7897+
func (q *FakeQuerier) getWorkspaceBuildParametersNoLock(workspaceBuildID uuid.UUID) ([]database.WorkspaceBuildParameter, error) {
79017898
params := make([]database.WorkspaceBuildParameter, 0)
79027899
for _, param := range q.workspaceBuildParameters {
79037900
if param.WorkspaceBuildID != workspaceBuildID {
@@ -7908,6 +7905,13 @@ func (q *FakeQuerier) GetWorkspaceBuildParameters(_ context.Context, workspaceBu
79087905
return params, nil
79097906
}
79107907

7908+
func (q *FakeQuerier) GetWorkspaceBuildParameters(_ context.Context, workspaceBuildID uuid.UUID) ([]database.WorkspaceBuildParameter, error) {
7909+
q.mutex.RLock()
7910+
defer q.mutex.RUnlock()
7911+
7912+
return q.getWorkspaceBuildParametersNoLock(workspaceBuildID)
7913+
}
7914+
79117915
func (q *FakeQuerier) GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow, error) {
79127916
q.mutex.RLock()
79137917
defer q.mutex.RUnlock()
@@ -13233,6 +13237,18 @@ func (q *FakeQuerier) GetAuthorizedTemplates(ctx context.Context, arg database.G
1323313237
continue
1323413238
}
1323513239
}
13240+
13241+
if arg.HasAITask.Valid {
13242+
tv, err := q.getTemplateVersionByIDNoLock(ctx, template.ActiveVersionID)
13243+
if err != nil {
13244+
return nil, xerrors.Errorf("get template version: %w", err)
13245+
}
13246+
tvHasAITask := tv.HasAITask.Valid && tv.HasAITask.Bool
13247+
if tvHasAITask != arg.HasAITask.Bool {
13248+
continue
13249+
}
13250+
}
13251+
1323613252
templates = append(templates, template)
1323713253
}
1323813254
if len(templates) > 0 {
@@ -13562,6 +13578,43 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
1356213578
}
1356313579
}
1356413580

13581+
if arg.HasAITask.Valid {
13582+
hasAITask, err := func() (bool, error) {
13583+
build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID)
13584+
if err != nil {
13585+
return false, xerrors.Errorf("get latest build: %w", err)
13586+
}
13587+
if build.HasAITask.Valid {
13588+
return build.HasAITask.Bool, nil
13589+
}
13590+
// If the build has a nil AI task, check if the job is in progress
13591+
// and if it has a non-empty ai_task_prompt parameter
13592+
job, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID)
13593+
if err != nil {
13594+
return false, xerrors.Errorf("get provisioner job: %w", err)
13595+
}
13596+
if job.CompletedAt.Valid {
13597+
return false, nil
13598+
}
13599+
parameters, err := q.getWorkspaceBuildParametersNoLock(build.ID)
13600+
if err != nil {
13601+
return false, xerrors.Errorf("get workspace build parameters: %w", err)
13602+
}
13603+
for _, param := range parameters {
13604+
if param.Name == "ai_task_prompt" && param.Value != "" {
13605+
return true, nil
13606+
}
13607+
}
13608+
return false, nil
13609+
}()
13610+
if err != nil {
13611+
return nil, xerrors.Errorf("get hasAITask: %w", err)
13612+
}
13613+
if hasAITask != arg.HasAITask.Bool {
13614+
continue
13615+
}
13616+
}
13617+
1356513618
// If the filter exists, ensure the object is authorized.
1356613619
if prepared != nil && prepared.Authorize(ctx, workspace.RBACObject()) != nil {
1356713620
continue

coderd/rbac/regosql/compile_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,8 @@ internal.member_2(input.object.org_owner, {"3bf82434-e40b-44ae-b3d8-d0115bba9bad
236236
neq(input.object.owner, "");
237237
"806dd721-775f-4c85-9ce3-63fbbd975954" = input.object.owner`,
238238
},
239-
ExpectedSQL: p(p("organization_id :: text != ''") + " AND " +
240-
p("organization_id :: text = ANY(ARRAY ['3bf82434-e40b-44ae-b3d8-d0115bba9bad','5630fda3-26ab-462c-9014-a88a62d7a415','c304877a-bc0d-4e9b-9623-a38eae412929'])") + " AND " +
239+
ExpectedSQL: p(p("t.organization_id :: text != ''") + " AND " +
240+
p("t.organization_id :: text = ANY(ARRAY ['3bf82434-e40b-44ae-b3d8-d0115bba9bad','5630fda3-26ab-462c-9014-a88a62d7a415','c304877a-bc0d-4e9b-9623-a38eae412929'])") + " AND " +
241241
p("false") + " AND " +
242242
p("false")),
243243
VariableConverter: regosql.TemplateConverter(),

coderd/rbac/regosql/configs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func userACLMatcher(m sqltypes.VariableMatcher) sqltypes.VariableMatcher {
2525
func TemplateConverter() *sqltypes.VariableConverter {
2626
matcher := sqltypes.NewVariableConverter().RegisterMatcher(
2727
resourceIDMatcher(),
28-
organizationOwnerMatcher(),
28+
sqltypes.StringVarMatcher("t.organization_id :: text", []string{"input", "object", "org_owner"}),
2929
// Templates have no user owner, only owner by an organization.
3030
sqltypes.AlwaysFalse(userOwnerMatcher()),
3131
)

coderd/searchquery/search.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ func Workspaces(ctx context.Context, db database.Store, query string, page coder
146146
// which will return all workspaces.
147147
Valid: values.Has("outdated"),
148148
}
149+
filter.HasAITask = parser.NullableBoolean(values, sql.NullBool{}, "has-ai-task")
149150
filter.OrganizationID = parseOrganization(ctx, db, parser, values, "organization")
150151

151152
type paramMatch struct {
@@ -206,6 +207,7 @@ func Templates(ctx context.Context, db database.Store, query string) (database.G
206207
IDs: parser.UUIDs(values, []uuid.UUID{}, "ids"),
207208
Deprecated: parser.NullableBoolean(values, sql.NullBool{}, "deprecated"),
208209
OrganizationID: parseOrganization(ctx, db, parser, values, "organization"),
210+
HasAITask: parser.NullableBoolean(values, sql.NullBool{}, "has-ai-task"),
209211
}
210212

211213
parser.ErrorExcessParams(values)

coderd/searchquery/search_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,36 @@ func TestSearchWorkspace(t *testing.T) {
222222
OrganizationID: uuid.MustParse("08eb6715-02f8-45c5-b86d-03786fcfbb4e"),
223223
},
224224
},
225+
{
226+
Name: "HasAITaskTrue",
227+
Query: "has-ai-task:true",
228+
Expected: database.GetWorkspacesParams{
229+
HasAITask: sql.NullBool{
230+
Bool: true,
231+
Valid: true,
232+
},
233+
},
234+
},
235+
{
236+
Name: "HasAITaskFalse",
237+
Query: "has-ai-task:false",
238+
Expected: database.GetWorkspacesParams{
239+
HasAITask: sql.NullBool{
240+
Bool: false,
241+
Valid: true,
242+
},
243+
},
244+
},
245+
{
246+
Name: "HasAITaskMissing",
247+
Query: "",
248+
Expected: database.GetWorkspacesParams{
249+
HasAITask: sql.NullBool{
250+
Bool: false,
251+
Valid: false,
252+
},
253+
},
254+
},
225255

226256
// Failures
227257
{
@@ -559,6 +589,36 @@ func TestSearchTemplates(t *testing.T) {
559589
FuzzyName: "foobar",
560590
},
561591
},
592+
{
593+
Name: "HasAITaskTrue",
594+
Query: "has-ai-task:true",
595+
Expected: database.GetTemplatesWithFilterParams{
596+
HasAITask: sql.NullBool{
597+
Bool: true,
598+
Valid: true,
599+
},
600+
},
601+
},
602+
{
603+
Name: "HasAITaskFalse",
604+
Query: "has-ai-task:false",
605+
Expected: database.GetTemplatesWithFilterParams{
606+
HasAITask: sql.NullBool{
607+
Bool: false,
608+
Valid: true,
609+
},
610+
},
611+
},
612+
{
613+
Name: "HasAITaskMissing",
614+
Query: "",
615+
Expected: database.GetTemplatesWithFilterParams{
616+
HasAITask: sql.NullBool{
617+
Bool: false,
618+
Valid: false,
619+
},
620+
},
621+
},
562622
}
563623

564624
for _, c := range testCases {

coderd/templates_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package coderd_test
22

33
import (
44
"context"
5+
"database/sql"
56
"net/http"
67
"sync/atomic"
78
"testing"
@@ -16,6 +17,7 @@ import (
1617
"github.com/coder/coder/v2/coderd/coderdtest"
1718
"github.com/coder/coder/v2/coderd/database"
1819
"github.com/coder/coder/v2/coderd/database/dbauthz"
20+
"github.com/coder/coder/v2/coderd/database/dbgen"
1921
"github.com/coder/coder/v2/coderd/database/dbtestutil"
2022
"github.com/coder/coder/v2/coderd/database/dbtime"
2123
"github.com/coder/coder/v2/coderd/notifications"
@@ -1809,3 +1811,66 @@ func TestTemplateNotifications(t *testing.T) {
18091811
})
18101812
})
18111813
}
1814+
1815+
func TestTemplateFilterHasAITask(t *testing.T) {
1816+
t.Parallel()
1817+
1818+
db, pubsub := dbtestutil.NewDB(t)
1819+
client := coderdtest.New(t, &coderdtest.Options{
1820+
Database: db,
1821+
Pubsub: pubsub,
1822+
IncludeProvisionerDaemon: true,
1823+
})
1824+
user := coderdtest.CreateFirstUser(t, client)
1825+
1826+
jobWithAITask := dbgen.ProvisionerJob(t, db, pubsub, database.ProvisionerJob{
1827+
OrganizationID: user.OrganizationID,
1828+
InitiatorID: user.UserID,
1829+
Tags: database.StringMap{},
1830+
Type: database.ProvisionerJobTypeTemplateVersionImport,
1831+
})
1832+
jobWithoutAITask := dbgen.ProvisionerJob(t, db, pubsub, database.ProvisionerJob{
1833+
OrganizationID: user.OrganizationID,
1834+
InitiatorID: user.UserID,
1835+
Tags: database.StringMap{},
1836+
Type: database.ProvisionerJobTypeTemplateVersionImport,
1837+
})
1838+
versionWithAITask := dbgen.TemplateVersion(t, db, database.TemplateVersion{
1839+
OrganizationID: user.OrganizationID,
1840+
CreatedBy: user.UserID,
1841+
HasAITask: sql.NullBool{Bool: true, Valid: true},
1842+
JobID: jobWithAITask.ID,
1843+
})
1844+
versionWithoutAITask := dbgen.TemplateVersion(t, db, database.TemplateVersion{
1845+
OrganizationID: user.OrganizationID,
1846+
CreatedBy: user.UserID,
1847+
HasAITask: sql.NullBool{Bool: false, Valid: true},
1848+
JobID: jobWithoutAITask.ID,
1849+
})
1850+
templateWithAITask := coderdtest.CreateTemplate(t, client, user.OrganizationID, versionWithAITask.ID)
1851+
templateWithoutAITask := coderdtest.CreateTemplate(t, client, user.OrganizationID, versionWithoutAITask.ID)
1852+
1853+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1854+
defer cancel()
1855+
1856+
// Test filtering
1857+
templates, err := client.Templates(ctx, codersdk.TemplateFilter{
1858+
SearchQuery: "has-ai-task:true",
1859+
})
1860+
require.NoError(t, err)
1861+
require.Len(t, templates, 1)
1862+
require.Equal(t, templateWithAITask.ID, templates[0].ID)
1863+
1864+
templates, err = client.Templates(ctx, codersdk.TemplateFilter{
1865+
SearchQuery: "has-ai-task:false",
1866+
})
1867+
require.NoError(t, err)
1868+
require.Len(t, templates, 1)
1869+
require.Equal(t, templateWithoutAITask.ID, templates[0].ID)
1870+
1871+
templates, err = client.Templates(ctx, codersdk.TemplateFilter{})
1872+
require.NoError(t, err)
1873+
require.Len(t, templates, 2)
1874+
require.Contains(t, templates, templateWithAITask)
1875+
require.Contains(t, templates, templateWithoutAITask)
1876+
}

coderd/workspaces.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
136136
// @Security CoderSessionToken
137137
// @Produce json
138138
// @Tags Workspaces
139-
// @Param q query string false "Search query in the format `key:value`. Available keys are: owner, template, name, status, has-agent, dormant, last_used_after, last_used_before."
139+
// @Param q query string false "Search query in the format `key:value`. Available keys are: owner, template, name, status, has-agent, dormant, last_used_after, last_used_before, has-ai-task."
140140
// @Param limit query int false "Page limit"
141141
// @Param offset query int false "Page offset"
142142
// @Success 200 {object} codersdk.WorkspacesResponse

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy