mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add the /aitasks/prompts endpoint (#18464)
Add an endpoint to fetch AI task prompts for multiple workspace builds at the same time. A prompt is the value of the "AI Prompt" workspace build parameter. On main, the only way our API allows fetching workspace build parameters is by using the `/workspacebuilds/$build_id/parameters` endpoint, requiring a separate API call for every build. The Tasks dashboard fetches Task workspaces in order to show them in a list, and then needs to fetch the value of the `AI Prompt` parameter for every task workspace (using its latest build id), requiring an additional API call for each list item. This endpoint will allow the dashboard to make just 2 calls to render the list: one to fetch task workspaces, the other to fetch prompts. <img width="1512" alt="Screenshot 2025-06-20 at 11 33 11" src="https://github.com/user-attachments/assets/92899999-e922-44c5-8325-b4b23a0d2bff" /> Related to https://github.com/coder/internal/issues/660.
This commit is contained in:
@@ -3281,6 +3281,15 @@ func (q *querier) GetWorkspaceBuildParameters(ctx context.Context, workspaceBuil
|
||||
return q.db.GetWorkspaceBuildParameters(ctx, workspaceBuildID)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIDs []uuid.UUID) ([]database.WorkspaceBuildParameter, error) {
|
||||
prep, err := prepareSQLFilter(ctx, q.auth, policy.ActionRead, rbac.ResourceWorkspace.Type)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("(dev error) prepare sql filter: %w", err)
|
||||
}
|
||||
|
||||
return q.db.GetAuthorizedWorkspaceBuildParametersByBuildIDs(ctx, workspaceBuildIDs, prep)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
@@ -5266,6 +5275,10 @@ func (q *querier) GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx context.Context,
|
||||
return q.GetWorkspacesAndAgentsByOwnerID(ctx, ownerID)
|
||||
}
|
||||
|
||||
func (q *querier) GetAuthorizedWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIDs []uuid.UUID, _ rbac.PreparedAuthorized) ([]database.WorkspaceBuildParameter, error) {
|
||||
return q.GetWorkspaceBuildParametersByBuildIDs(ctx, workspaceBuildIDs)
|
||||
}
|
||||
|
||||
// GetAuthorizedUsers is not required for dbauthz since GetUsers is already
|
||||
// authenticated.
|
||||
func (q *querier) GetAuthorizedUsers(ctx context.Context, arg database.GetUsersParams, _ rbac.PreparedAuthorized) ([]database.GetUsersRow, error) {
|
||||
|
||||
@@ -2012,6 +2012,14 @@ func (s *MethodTestSuite) TestWorkspace() {
|
||||
// No asserts here because SQLFilter.
|
||||
check.Args(ws.OwnerID, emptyPreparedAuthorized{}).Asserts()
|
||||
}))
|
||||
s.Run("GetWorkspaceBuildParametersByBuildIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
// no asserts here because SQLFilter
|
||||
check.Args([]uuid.UUID{}).Asserts()
|
||||
}))
|
||||
s.Run("GetAuthorizedWorkspaceBuildParametersByBuildIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
// no asserts here because SQLFilter
|
||||
check.Args([]uuid.UUID{}, emptyPreparedAuthorized{}).Asserts()
|
||||
}))
|
||||
s.Run("GetLatestWorkspaceBuildByWorkspaceID", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
o := dbgen.Organization(s.T(), db, database.Organization{})
|
||||
|
||||
@@ -7960,6 +7960,11 @@ func (q *FakeQuerier) GetWorkspaceBuildParameters(_ context.Context, workspaceBu
|
||||
return q.getWorkspaceBuildParametersNoLock(workspaceBuildID)
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIDs []uuid.UUID) ([]database.WorkspaceBuildParameter, error) {
|
||||
// No auth filter.
|
||||
return q.GetAuthorizedWorkspaceBuildParametersByBuildIDs(ctx, workspaceBuildIDs, nil)
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
@@ -13901,6 +13906,30 @@ func (q *FakeQuerier) GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx context.Cont
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetAuthorizedWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIDs []uuid.UUID, prepared rbac.PreparedAuthorized) ([]database.WorkspaceBuildParameter, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
if prepared != nil {
|
||||
// Call this to match the same function calls as the SQL implementation.
|
||||
_, err := prepared.CompileToSQL(ctx, rbac.ConfigWithoutACL())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
filteredParameters := make([]database.WorkspaceBuildParameter, 0)
|
||||
for _, buildID := range workspaceBuildIDs {
|
||||
parameters, err := q.GetWorkspaceBuildParameters(ctx, buildID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filteredParameters = append(filteredParameters, parameters...)
|
||||
}
|
||||
|
||||
return filteredParameters, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetAuthorizedUsers(ctx context.Context, arg database.GetUsersParams, prepared rbac.PreparedAuthorized) ([]database.GetUsersRow, error) {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1873,6 +1873,13 @@ func (m queryMetricsStore) GetWorkspaceBuildParameters(ctx context.Context, work
|
||||
return params, err
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIds []uuid.UUID) ([]database.WorkspaceBuildParameter, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetWorkspaceBuildParametersByBuildIDs(ctx, workspaceBuildIds)
|
||||
m.queryLatencies.WithLabelValues("GetWorkspaceBuildParametersByBuildIDs").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetWorkspaceBuildStatsByTemplates(ctx, since)
|
||||
@@ -3343,6 +3350,13 @@ func (m queryMetricsStore) GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx context
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetAuthorizedWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIDs []uuid.UUID, prepared rbac.PreparedAuthorized) ([]database.WorkspaceBuildParameter, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetAuthorizedWorkspaceBuildParametersByBuildIDs(ctx, workspaceBuildIDs, prepared)
|
||||
m.queryLatencies.WithLabelValues("GetAuthorizedWorkspaceBuildParametersByBuildIDs").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetAuthorizedUsers(ctx context.Context, arg database.GetUsersParams, prepared rbac.PreparedAuthorized) ([]database.GetUsersRow, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetAuthorizedUsers(ctx, arg, prepared)
|
||||
|
||||
@@ -1247,6 +1247,21 @@ func (mr *MockStoreMockRecorder) GetAuthorizedUsers(ctx, arg, prepared any) *gom
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorizedUsers", reflect.TypeOf((*MockStore)(nil).GetAuthorizedUsers), ctx, arg, prepared)
|
||||
}
|
||||
|
||||
// GetAuthorizedWorkspaceBuildParametersByBuildIDs mocks base method.
|
||||
func (m *MockStore) GetAuthorizedWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIDs []uuid.UUID, prepared rbac.PreparedAuthorized) ([]database.WorkspaceBuildParameter, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAuthorizedWorkspaceBuildParametersByBuildIDs", ctx, workspaceBuildIDs, prepared)
|
||||
ret0, _ := ret[0].([]database.WorkspaceBuildParameter)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAuthorizedWorkspaceBuildParametersByBuildIDs indicates an expected call of GetAuthorizedWorkspaceBuildParametersByBuildIDs.
|
||||
func (mr *MockStoreMockRecorder) GetAuthorizedWorkspaceBuildParametersByBuildIDs(ctx, workspaceBuildIDs, prepared any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorizedWorkspaceBuildParametersByBuildIDs", reflect.TypeOf((*MockStore)(nil).GetAuthorizedWorkspaceBuildParametersByBuildIDs), ctx, workspaceBuildIDs, prepared)
|
||||
}
|
||||
|
||||
// GetAuthorizedWorkspaces mocks base method.
|
||||
func (m *MockStore) GetAuthorizedWorkspaces(ctx context.Context, arg database.GetWorkspacesParams, prepared rbac.PreparedAuthorized) ([]database.GetWorkspacesRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -3932,6 +3947,21 @@ func (mr *MockStoreMockRecorder) GetWorkspaceBuildParameters(ctx, workspaceBuild
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceBuildParameters", reflect.TypeOf((*MockStore)(nil).GetWorkspaceBuildParameters), ctx, workspaceBuildID)
|
||||
}
|
||||
|
||||
// GetWorkspaceBuildParametersByBuildIDs mocks base method.
|
||||
func (m *MockStore) GetWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIds []uuid.UUID) ([]database.WorkspaceBuildParameter, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetWorkspaceBuildParametersByBuildIDs", ctx, workspaceBuildIds)
|
||||
ret0, _ := ret[0].([]database.WorkspaceBuildParameter)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetWorkspaceBuildParametersByBuildIDs indicates an expected call of GetWorkspaceBuildParametersByBuildIDs.
|
||||
func (mr *MockStoreMockRecorder) GetWorkspaceBuildParametersByBuildIDs(ctx, workspaceBuildIds any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceBuildParametersByBuildIDs", reflect.TypeOf((*MockStore)(nil).GetWorkspaceBuildParametersByBuildIDs), ctx, workspaceBuildIds)
|
||||
}
|
||||
|
||||
// GetWorkspaceBuildStatsByTemplates mocks base method.
|
||||
func (m *MockStore) GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -226,6 +226,7 @@ func (q *sqlQuerier) GetTemplateGroupRoles(ctx context.Context, id uuid.UUID) ([
|
||||
type workspaceQuerier interface {
|
||||
GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspacesParams, prepared rbac.PreparedAuthorized) ([]GetWorkspacesRow, error)
|
||||
GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx context.Context, ownerID uuid.UUID, prepared rbac.PreparedAuthorized) ([]GetWorkspacesAndAgentsByOwnerIDRow, error)
|
||||
GetAuthorizedWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIDs []uuid.UUID, prepared rbac.PreparedAuthorized) ([]WorkspaceBuildParameter, error)
|
||||
}
|
||||
|
||||
// GetAuthorizedWorkspaces returns all workspaces that the user is authorized to access.
|
||||
@@ -372,6 +373,35 @@ func (q *sqlQuerier) GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx context.Conte
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetAuthorizedWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIDs []uuid.UUID, prepared rbac.PreparedAuthorized) ([]WorkspaceBuildParameter, error) {
|
||||
authorizedFilter, err := prepared.CompileToSQL(ctx, rbac.ConfigWorkspaces())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("compile authorized filter: %w", err)
|
||||
}
|
||||
|
||||
filtered, err := insertAuthorizedFilter(getWorkspaceBuildParametersByBuildIDs, fmt.Sprintf(" AND %s", authorizedFilter))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("insert authorized filter: %w", err)
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("-- name: GetAuthorizedWorkspaceBuildParametersByBuildIDs :many\n%s", filtered)
|
||||
rows, err := q.db.QueryContext(ctx, query, pq.Array(workspaceBuildIDs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []WorkspaceBuildParameter
|
||||
for rows.Next() {
|
||||
var i WorkspaceBuildParameter
|
||||
if err := rows.Scan(&i.WorkspaceBuildID, &i.Name, &i.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
type userQuerier interface {
|
||||
GetAuthorizedUsers(ctx context.Context, arg GetUsersParams, prepared rbac.PreparedAuthorized) ([]GetUsersRow, error)
|
||||
}
|
||||
|
||||
@@ -428,6 +428,7 @@ type sqlcQuerier interface {
|
||||
GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UUID) (WorkspaceBuild, error)
|
||||
GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Context, arg GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (WorkspaceBuild, error)
|
||||
GetWorkspaceBuildParameters(ctx context.Context, workspaceBuildID uuid.UUID) ([]WorkspaceBuildParameter, error)
|
||||
GetWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIds []uuid.UUID) ([]WorkspaceBuildParameter, error)
|
||||
GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]GetWorkspaceBuildStatsByTemplatesRow, error)
|
||||
GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg GetWorkspaceBuildsByWorkspaceIDParams) ([]WorkspaceBuild, error)
|
||||
GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceBuild, error)
|
||||
|
||||
@@ -17035,6 +17035,44 @@ func (q *sqlQuerier) GetWorkspaceBuildParameters(ctx context.Context, workspaceB
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getWorkspaceBuildParametersByBuildIDs = `-- name: GetWorkspaceBuildParametersByBuildIDs :many
|
||||
SELECT
|
||||
workspace_build_parameters.workspace_build_id, workspace_build_parameters.name, workspace_build_parameters.value
|
||||
FROM
|
||||
workspace_build_parameters
|
||||
JOIN
|
||||
workspace_builds ON workspace_builds.id = workspace_build_parameters.workspace_build_id
|
||||
JOIN
|
||||
workspaces ON workspaces.id = workspace_builds.workspace_id
|
||||
WHERE
|
||||
workspace_build_parameters.workspace_build_id = ANY($1 :: uuid[])
|
||||
-- Authorize Filter clause will be injected below in GetAuthorizedWorkspaceBuildParametersByBuildIDs
|
||||
-- @authorize_filter
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIds []uuid.UUID) ([]WorkspaceBuildParameter, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWorkspaceBuildParametersByBuildIDs, pq.Array(workspaceBuildIds))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []WorkspaceBuildParameter
|
||||
for rows.Next() {
|
||||
var i WorkspaceBuildParameter
|
||||
if err := rows.Scan(&i.WorkspaceBuildID, &i.Name, &i.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const insertWorkspaceBuildParameters = `-- name: InsertWorkspaceBuildParameters :exec
|
||||
INSERT INTO
|
||||
workspace_build_parameters (workspace_build_id, name, value)
|
||||
|
||||
@@ -41,3 +41,18 @@ FROM (
|
||||
) q1
|
||||
ORDER BY created_at DESC, name
|
||||
LIMIT 100;
|
||||
|
||||
-- name: GetWorkspaceBuildParametersByBuildIDs :many
|
||||
SELECT
|
||||
workspace_build_parameters.*
|
||||
FROM
|
||||
workspace_build_parameters
|
||||
JOIN
|
||||
workspace_builds ON workspace_builds.id = workspace_build_parameters.workspace_build_id
|
||||
JOIN
|
||||
workspaces ON workspaces.id = workspace_builds.workspace_id
|
||||
WHERE
|
||||
workspace_build_parameters.workspace_build_id = ANY(@workspace_build_ids :: uuid[])
|
||||
-- Authorize Filter clause will be injected below in GetAuthorizedWorkspaceBuildParametersByBuildIDs
|
||||
-- @authorize_filter
|
||||
;
|
||||
|
||||
Reference in New Issue
Block a user