mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: persist AI task state in template imports & workspace builds (#18449)
This commit is contained in:
@@ -4518,6 +4518,28 @@ func (q *querier) UpdateTemplateScheduleByID(ctx context.Context, arg database.U
|
||||
return update(q.log, q.auth, fetch, q.db.UpdateTemplateScheduleByID)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateTemplateVersionAITaskByJobID(ctx context.Context, arg database.UpdateTemplateVersionAITaskByJobIDParams) error {
|
||||
// An actor is allowed to update the template version AI task flag if they are authorized to update the template.
|
||||
tv, err := q.db.GetTemplateVersionByJobID(ctx, arg.JobID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var obj rbac.Objecter
|
||||
if !tv.TemplateID.Valid {
|
||||
obj = rbac.ResourceTemplate.InOrg(tv.OrganizationID)
|
||||
} else {
|
||||
tpl, err := q.db.GetTemplateByID(ctx, tv.TemplateID.UUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj = tpl
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, obj); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.UpdateTemplateVersionAITaskByJobID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateTemplateVersionByID(ctx context.Context, arg database.UpdateTemplateVersionByIDParams) error {
|
||||
// An actor is allowed to update the template version if they are authorized to update the template.
|
||||
tv, err := q.db.GetTemplateVersionByID(ctx, arg.ID)
|
||||
@@ -4874,6 +4896,24 @@ func (q *querier) UpdateWorkspaceAutostart(ctx context.Context, arg database.Upd
|
||||
return update(q.log, q.auth, fetch, q.db.UpdateWorkspaceAutostart)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceBuildAITaskByID(ctx context.Context, arg database.UpdateWorkspaceBuildAITaskByIDParams) error {
|
||||
build, err := q.db.GetWorkspaceBuildByID(ctx, arg.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workspace, err := q.db.GetWorkspaceByID(ctx, build.WorkspaceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = q.authorizeContext(ctx, policy.ActionUpdate, workspace.RBACObject())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.UpdateWorkspaceBuildAITaskByID(ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateWorkspaceBuildCostByID is used by the provisioning system to update the cost of a workspace build.
|
||||
func (q *querier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
|
||||
@@ -1391,6 +1391,24 @@ func (s *MethodTestSuite) TestTemplate() {
|
||||
ID: t1.ID,
|
||||
}).Asserts(t1, policy.ActionUpdate)
|
||||
}))
|
||||
s.Run("UpdateTemplateVersionAITaskByJobID", s.Subtest(func(db database.Store, check *expects) {
|
||||
dbtestutil.DisableForeignKeysAndTriggers(s.T(), db)
|
||||
o := dbgen.Organization(s.T(), db, database.Organization{})
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
_ = dbgen.OrganizationMember(s.T(), db, database.OrganizationMember{OrganizationID: o.ID, UserID: u.ID})
|
||||
t := dbgen.Template(s.T(), db, database.Template{OrganizationID: o.ID, CreatedBy: u.ID})
|
||||
job := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{OrganizationID: o.ID})
|
||||
_ = dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
|
||||
OrganizationID: o.ID,
|
||||
CreatedBy: u.ID,
|
||||
JobID: job.ID,
|
||||
TemplateID: uuid.NullUUID{UUID: t.ID, Valid: true},
|
||||
})
|
||||
check.Args(database.UpdateTemplateVersionAITaskByJobIDParams{
|
||||
JobID: job.ID,
|
||||
HasAITask: sql.NullBool{Bool: true, Valid: true},
|
||||
}).Asserts(t, policy.ActionUpdate)
|
||||
}))
|
||||
s.Run("UpdateTemplateWorkspacesLastUsedAt", s.Subtest(func(db database.Store, check *expects) {
|
||||
dbtestutil.DisableForeignKeysAndTriggers(s.T(), db)
|
||||
t1 := dbgen.Template(s.T(), db, database.Template{})
|
||||
@@ -3050,6 +3068,40 @@ func (s *MethodTestSuite) TestWorkspace() {
|
||||
Deadline: b.Deadline,
|
||||
}).Asserts(w, policy.ActionUpdate)
|
||||
}))
|
||||
s.Run("UpdateWorkspaceBuildAITaskByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
o := dbgen.Organization(s.T(), db, database.Organization{})
|
||||
tpl := dbgen.Template(s.T(), db, database.Template{
|
||||
OrganizationID: o.ID,
|
||||
CreatedBy: u.ID,
|
||||
})
|
||||
tv := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true},
|
||||
OrganizationID: o.ID,
|
||||
CreatedBy: u.ID,
|
||||
})
|
||||
w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{
|
||||
TemplateID: tpl.ID,
|
||||
OrganizationID: o.ID,
|
||||
OwnerID: u.ID,
|
||||
})
|
||||
j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
})
|
||||
b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{
|
||||
JobID: j.ID,
|
||||
WorkspaceID: w.ID,
|
||||
TemplateVersionID: tv.ID,
|
||||
})
|
||||
res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: b.JobID})
|
||||
agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID})
|
||||
app := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: agt.ID})
|
||||
check.Args(database.UpdateWorkspaceBuildAITaskByIDParams{
|
||||
HasAITask: sql.NullBool{Bool: true, Valid: true},
|
||||
SidebarAppID: uuid.NullUUID{UUID: app.ID, Valid: true},
|
||||
ID: b.ID,
|
||||
}).Asserts(w, policy.ActionUpdate)
|
||||
}))
|
||||
s.Run("SoftDeleteWorkspaceByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
o := dbgen.Organization(s.T(), db, database.Organization{})
|
||||
|
||||
@@ -390,6 +390,9 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil
|
||||
t.Helper()
|
||||
|
||||
buildID := takeFirst(orig.ID, uuid.New())
|
||||
jobID := takeFirst(orig.JobID, uuid.New())
|
||||
hasAITask := takeFirst(orig.HasAITask, sql.NullBool{})
|
||||
sidebarAppID := takeFirst(orig.AITaskSidebarAppID, uuid.NullUUID{})
|
||||
var build database.WorkspaceBuild
|
||||
err := db.InTx(func(db database.Store) error {
|
||||
err := db.InsertWorkspaceBuild(genCtx, database.InsertWorkspaceBuildParams{
|
||||
@@ -401,7 +404,7 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil
|
||||
BuildNumber: takeFirst(orig.BuildNumber, 1),
|
||||
Transition: takeFirst(orig.Transition, database.WorkspaceTransitionStart),
|
||||
InitiatorID: takeFirst(orig.InitiatorID, uuid.New()),
|
||||
JobID: takeFirst(orig.JobID, uuid.New()),
|
||||
JobID: jobID,
|
||||
ProvisionerState: takeFirstSlice(orig.ProvisionerState, []byte{}),
|
||||
Deadline: takeFirst(orig.Deadline, dbtime.Now().Add(time.Hour)),
|
||||
MaxDeadline: takeFirst(orig.MaxDeadline, time.Time{}),
|
||||
@@ -410,7 +413,6 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil
|
||||
UUID: uuid.UUID{},
|
||||
Valid: false,
|
||||
}),
|
||||
HasAITask: orig.HasAITask,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -424,6 +426,15 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
if hasAITask.Valid {
|
||||
require.NoError(t, db.UpdateWorkspaceBuildAITaskByID(genCtx, database.UpdateWorkspaceBuildAITaskByIDParams{
|
||||
HasAITask: hasAITask,
|
||||
SidebarAppID: sidebarAppID,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
ID: buildID,
|
||||
}))
|
||||
}
|
||||
|
||||
build, err = db.GetWorkspaceBuildByID(genCtx, buildID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -971,6 +982,8 @@ func ExternalAuthLink(t testing.TB, db database.Store, orig database.ExternalAut
|
||||
|
||||
func TemplateVersion(t testing.TB, db database.Store, orig database.TemplateVersion) database.TemplateVersion {
|
||||
var version database.TemplateVersion
|
||||
hasAITask := takeFirst(orig.HasAITask, sql.NullBool{})
|
||||
jobID := takeFirst(orig.JobID, uuid.New())
|
||||
err := db.InTx(func(db database.Store) error {
|
||||
versionID := takeFirst(orig.ID, uuid.New())
|
||||
err := db.InsertTemplateVersion(genCtx, database.InsertTemplateVersionParams{
|
||||
@@ -982,15 +995,22 @@ func TemplateVersion(t testing.TB, db database.Store, orig database.TemplateVers
|
||||
Name: takeFirst(orig.Name, testutil.GetRandomName(t)),
|
||||
Message: orig.Message,
|
||||
Readme: takeFirst(orig.Readme, testutil.GetRandomName(t)),
|
||||
JobID: takeFirst(orig.JobID, uuid.New()),
|
||||
JobID: jobID,
|
||||
CreatedBy: takeFirst(orig.CreatedBy, uuid.New()),
|
||||
SourceExampleID: takeFirst(orig.SourceExampleID, sql.NullString{}),
|
||||
HasAITask: orig.HasAITask,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hasAITask.Valid {
|
||||
require.NoError(t, db.UpdateTemplateVersionAITaskByJobID(genCtx, database.UpdateTemplateVersionAITaskByJobIDParams{
|
||||
JobID: jobID,
|
||||
HasAITask: hasAITask,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
}))
|
||||
}
|
||||
|
||||
version, err = db.GetTemplateVersionByID(genCtx, versionID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -9465,7 +9465,6 @@ func (q *FakeQuerier) InsertTemplateVersion(_ context.Context, arg database.Inse
|
||||
JobID: arg.JobID,
|
||||
CreatedBy: arg.CreatedBy,
|
||||
SourceExampleID: arg.SourceExampleID,
|
||||
HasAITask: arg.HasAITask,
|
||||
}
|
||||
q.templateVersions = append(q.templateVersions, version)
|
||||
return nil
|
||||
@@ -10103,7 +10102,6 @@ func (q *FakeQuerier) InsertWorkspaceBuild(_ context.Context, arg database.Inser
|
||||
MaxDeadline: arg.MaxDeadline,
|
||||
Reason: arg.Reason,
|
||||
TemplateVersionPresetID: arg.TemplateVersionPresetID,
|
||||
HasAITask: arg.HasAITask,
|
||||
}
|
||||
q.workspaceBuilds = append(q.workspaceBuilds, workspaceBuild)
|
||||
return nil
|
||||
@@ -11308,6 +11306,26 @@ func (q *FakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateTemplateVersionAITaskByJobID(_ context.Context, arg database.UpdateTemplateVersionAITaskByJobIDParams) error {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for index, templateVersion := range q.templateVersions {
|
||||
if templateVersion.JobID != arg.JobID {
|
||||
continue
|
||||
}
|
||||
templateVersion.HasAITask = arg.HasAITask
|
||||
templateVersion.UpdatedAt = arg.UpdatedAt
|
||||
q.templateVersions[index] = templateVersion
|
||||
return nil
|
||||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateTemplateVersionByID(_ context.Context, arg database.UpdateTemplateVersionByIDParams) error {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return err
|
||||
@@ -12003,6 +12021,35 @@ func (q *FakeQuerier) UpdateWorkspaceAutostart(_ context.Context, arg database.U
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateWorkspaceBuildAITaskByID(_ context.Context, arg database.UpdateWorkspaceBuildAITaskByIDParams) error {
|
||||
if arg.HasAITask.Bool && !arg.SidebarAppID.Valid {
|
||||
return xerrors.Errorf("ai_task_sidebar_app_id is required when has_ai_task is true")
|
||||
}
|
||||
if !arg.HasAITask.Valid && arg.SidebarAppID.Valid {
|
||||
return xerrors.Errorf("ai_task_sidebar_app_id is can only be set when has_ai_task is true")
|
||||
}
|
||||
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for index, workspaceBuild := range q.workspaceBuilds {
|
||||
if workspaceBuild.ID != arg.ID {
|
||||
continue
|
||||
}
|
||||
workspaceBuild.HasAITask = arg.HasAITask
|
||||
workspaceBuild.AITaskSidebarAppID = arg.SidebarAppID
|
||||
workspaceBuild.UpdatedAt = dbtime.Now()
|
||||
q.workspaceBuilds[index] = workspaceBuild
|
||||
return nil
|
||||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateWorkspaceBuildCostByID(_ context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return err
|
||||
|
||||
@@ -2825,6 +2825,13 @@ func (m queryMetricsStore) UpdateTemplateScheduleByID(ctx context.Context, arg d
|
||||
return err
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateTemplateVersionAITaskByJobID(ctx context.Context, arg database.UpdateTemplateVersionAITaskByJobIDParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpdateTemplateVersionAITaskByJobID(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateTemplateVersionAITaskByJobID").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateTemplateVersionByID(ctx context.Context, arg database.UpdateTemplateVersionByIDParams) error {
|
||||
start := time.Now()
|
||||
err := m.s.UpdateTemplateVersionByID(ctx, arg)
|
||||
@@ -3028,6 +3035,13 @@ func (m queryMetricsStore) UpdateWorkspaceAutostart(ctx context.Context, arg dat
|
||||
return err
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateWorkspaceBuildAITaskByID(ctx context.Context, arg database.UpdateWorkspaceBuildAITaskByIDParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpdateWorkspaceBuildAITaskByID(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateWorkspaceBuildAITaskByID").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error {
|
||||
start := time.Now()
|
||||
err := m.s.UpdateWorkspaceBuildCostByID(ctx, arg)
|
||||
|
||||
@@ -5977,6 +5977,20 @@ func (mr *MockStoreMockRecorder) UpdateTemplateScheduleByID(ctx, arg any) *gomoc
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTemplateScheduleByID", reflect.TypeOf((*MockStore)(nil).UpdateTemplateScheduleByID), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateTemplateVersionAITaskByJobID mocks base method.
|
||||
func (m *MockStore) UpdateTemplateVersionAITaskByJobID(ctx context.Context, arg database.UpdateTemplateVersionAITaskByJobIDParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateTemplateVersionAITaskByJobID", ctx, arg)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateTemplateVersionAITaskByJobID indicates an expected call of UpdateTemplateVersionAITaskByJobID.
|
||||
func (mr *MockStoreMockRecorder) UpdateTemplateVersionAITaskByJobID(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTemplateVersionAITaskByJobID", reflect.TypeOf((*MockStore)(nil).UpdateTemplateVersionAITaskByJobID), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateTemplateVersionByID mocks base method.
|
||||
func (m *MockStore) UpdateTemplateVersionByID(ctx context.Context, arg database.UpdateTemplateVersionByIDParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -6395,6 +6409,20 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceAutostart(ctx, arg any) *gomock.
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceAutostart", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceAutostart), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateWorkspaceBuildAITaskByID mocks base method.
|
||||
func (m *MockStore) UpdateWorkspaceBuildAITaskByID(ctx context.Context, arg database.UpdateWorkspaceBuildAITaskByIDParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateWorkspaceBuildAITaskByID", ctx, arg)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateWorkspaceBuildAITaskByID indicates an expected call of UpdateWorkspaceBuildAITaskByID.
|
||||
func (mr *MockStoreMockRecorder) UpdateWorkspaceBuildAITaskByID(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceBuildAITaskByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceBuildAITaskByID), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateWorkspaceBuildCostByID mocks base method.
|
||||
func (m *MockStore) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Generated
+4
-3
@@ -2099,7 +2099,8 @@ CREATE TABLE workspace_builds (
|
||||
max_deadline timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL,
|
||||
template_version_preset_id uuid,
|
||||
has_ai_task boolean,
|
||||
ai_tasks_sidebar_app_id uuid
|
||||
ai_task_sidebar_app_id uuid,
|
||||
CONSTRAINT workspace_builds_ai_task_sidebar_app_id_required CHECK (((((has_ai_task IS NULL) OR (has_ai_task = false)) AND (ai_task_sidebar_app_id IS NULL)) OR ((has_ai_task = true) AND (ai_task_sidebar_app_id IS NOT NULL))))
|
||||
);
|
||||
|
||||
CREATE VIEW workspace_build_with_user AS
|
||||
@@ -2119,7 +2120,7 @@ CREATE VIEW workspace_build_with_user AS
|
||||
workspace_builds.max_deadline,
|
||||
workspace_builds.template_version_preset_id,
|
||||
workspace_builds.has_ai_task,
|
||||
workspace_builds.ai_tasks_sidebar_app_id,
|
||||
workspace_builds.ai_task_sidebar_app_id,
|
||||
COALESCE(visible_users.avatar_url, ''::text) AS initiator_by_avatar_url,
|
||||
COALESCE(visible_users.username, ''::text) AS initiator_by_username,
|
||||
COALESCE(visible_users.name, ''::text) AS initiator_by_name
|
||||
@@ -3095,7 +3096,7 @@ ALTER TABLE ONLY workspace_build_parameters
|
||||
ADD CONSTRAINT workspace_build_parameters_workspace_build_id_fkey FOREIGN KEY (workspace_build_id) REFERENCES workspace_builds(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY workspace_builds
|
||||
ADD CONSTRAINT workspace_builds_ai_tasks_sidebar_app_id_fkey FOREIGN KEY (ai_tasks_sidebar_app_id) REFERENCES workspace_apps(id);
|
||||
ADD CONSTRAINT workspace_builds_ai_task_sidebar_app_id_fkey FOREIGN KEY (ai_task_sidebar_app_id) REFERENCES workspace_apps(id);
|
||||
|
||||
ALTER TABLE ONLY workspace_builds
|
||||
ADD CONSTRAINT workspace_builds_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
|
||||
|
||||
@@ -83,7 +83,7 @@ const (
|
||||
ForeignKeyWorkspaceAppStatusesWorkspaceID ForeignKeyConstraint = "workspace_app_statuses_workspace_id_fkey" // ALTER TABLE ONLY workspace_app_statuses ADD CONSTRAINT workspace_app_statuses_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id);
|
||||
ForeignKeyWorkspaceAppsAgentID ForeignKeyConstraint = "workspace_apps_agent_id_fkey" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
ForeignKeyWorkspaceBuildParametersWorkspaceBuildID ForeignKeyConstraint = "workspace_build_parameters_workspace_build_id_fkey" // ALTER TABLE ONLY workspace_build_parameters ADD CONSTRAINT workspace_build_parameters_workspace_build_id_fkey FOREIGN KEY (workspace_build_id) REFERENCES workspace_builds(id) ON DELETE CASCADE;
|
||||
ForeignKeyWorkspaceBuildsAiTasksSidebarAppID ForeignKeyConstraint = "workspace_builds_ai_tasks_sidebar_app_id_fkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_ai_tasks_sidebar_app_id_fkey FOREIGN KEY (ai_tasks_sidebar_app_id) REFERENCES workspace_apps(id);
|
||||
ForeignKeyWorkspaceBuildsAiTaskSidebarAppID ForeignKeyConstraint = "workspace_builds_ai_task_sidebar_app_id_fkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_ai_task_sidebar_app_id_fkey FOREIGN KEY (ai_task_sidebar_app_id) REFERENCES workspace_apps(id);
|
||||
ForeignKeyWorkspaceBuildsJobID ForeignKeyConstraint = "workspace_builds_job_id_fkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
|
||||
ForeignKeyWorkspaceBuildsTemplateVersionID ForeignKeyConstraint = "workspace_builds_template_version_id_fkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_template_version_id_fkey FOREIGN KEY (template_version_id) REFERENCES template_versions(id) ON DELETE CASCADE;
|
||||
ForeignKeyWorkspaceBuildsTemplateVersionPresetID ForeignKeyConstraint = "workspace_builds_template_version_preset_id_fkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_template_version_preset_id_fkey FOREIGN KEY (template_version_preset_id) REFERENCES template_version_presets(id) ON DELETE SET NULL;
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
-- Drop the check constraint first
|
||||
ALTER TABLE workspace_builds DROP CONSTRAINT workspace_builds_ai_task_sidebar_app_id_required;
|
||||
|
||||
-- Revert ai_task_sidebar_app_id back to ai_tasks_sidebar_app_id in workspace_builds table
|
||||
ALTER TABLE workspace_builds DROP CONSTRAINT workspace_builds_ai_task_sidebar_app_id_fkey;
|
||||
|
||||
ALTER TABLE workspace_builds RENAME COLUMN ai_task_sidebar_app_id TO ai_tasks_sidebar_app_id;
|
||||
|
||||
ALTER TABLE workspace_builds ADD CONSTRAINT workspace_builds_ai_tasks_sidebar_app_id_fkey FOREIGN KEY (ai_tasks_sidebar_app_id) REFERENCES workspace_apps(id);
|
||||
|
||||
-- Revert the workspace_build_with_user view to use the original column name
|
||||
DROP VIEW workspace_build_with_user;
|
||||
|
||||
CREATE VIEW workspace_build_with_user AS
|
||||
SELECT
|
||||
workspace_builds.id,
|
||||
workspace_builds.created_at,
|
||||
workspace_builds.updated_at,
|
||||
workspace_builds.workspace_id,
|
||||
workspace_builds.template_version_id,
|
||||
workspace_builds.build_number,
|
||||
workspace_builds.transition,
|
||||
workspace_builds.initiator_id,
|
||||
workspace_builds.provisioner_state,
|
||||
workspace_builds.job_id,
|
||||
workspace_builds.deadline,
|
||||
workspace_builds.reason,
|
||||
workspace_builds.daily_cost,
|
||||
workspace_builds.max_deadline,
|
||||
workspace_builds.template_version_preset_id,
|
||||
workspace_builds.has_ai_task,
|
||||
workspace_builds.ai_tasks_sidebar_app_id,
|
||||
COALESCE(
|
||||
visible_users.avatar_url,
|
||||
'' :: text
|
||||
) AS initiator_by_avatar_url,
|
||||
COALESCE(
|
||||
visible_users.username,
|
||||
'' :: text
|
||||
) AS initiator_by_username,
|
||||
COALESCE(visible_users.name, '' :: text) AS initiator_by_name
|
||||
FROM
|
||||
(
|
||||
workspace_builds
|
||||
LEFT JOIN visible_users ON (
|
||||
(
|
||||
workspace_builds.initiator_id = visible_users.id
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
COMMENT ON VIEW workspace_build_with_user IS 'Joins in the username + avatar url of the initiated by user.';
|
||||
@@ -0,0 +1,66 @@
|
||||
-- Rename ai_tasks_sidebar_app_id to ai_task_sidebar_app_id in workspace_builds table
|
||||
ALTER TABLE workspace_builds DROP CONSTRAINT workspace_builds_ai_tasks_sidebar_app_id_fkey;
|
||||
|
||||
ALTER TABLE workspace_builds RENAME COLUMN ai_tasks_sidebar_app_id TO ai_task_sidebar_app_id;
|
||||
|
||||
ALTER TABLE workspace_builds ADD CONSTRAINT workspace_builds_ai_task_sidebar_app_id_fkey FOREIGN KEY (ai_task_sidebar_app_id) REFERENCES workspace_apps(id);
|
||||
|
||||
-- if has_ai_task is true, ai_task_sidebar_app_id MUST be set
|
||||
-- ai_task_sidebar_app_id can ONLY be set if has_ai_task is true
|
||||
--
|
||||
-- has_ai_task | ai_task_sidebar_app_id | Result
|
||||
-- ------------|------------------------|---------------
|
||||
-- NULL | NULL | TRUE (passes)
|
||||
-- NULL | NOT NULL | FALSE (fails)
|
||||
-- FALSE | NULL | TRUE (passes)
|
||||
-- FALSE | NOT NULL | FALSE (fails)
|
||||
-- TRUE | NULL | FALSE (fails)
|
||||
-- TRUE | NOT NULL | TRUE (passes)
|
||||
ALTER TABLE workspace_builds
|
||||
ADD CONSTRAINT workspace_builds_ai_task_sidebar_app_id_required CHECK (
|
||||
((has_ai_task IS NULL OR has_ai_task = false) AND ai_task_sidebar_app_id IS NULL)
|
||||
OR (has_ai_task = true AND ai_task_sidebar_app_id IS NOT NULL)
|
||||
);
|
||||
|
||||
-- Update the workspace_build_with_user view to use the new column name
|
||||
DROP VIEW workspace_build_with_user;
|
||||
|
||||
CREATE VIEW workspace_build_with_user AS
|
||||
SELECT
|
||||
workspace_builds.id,
|
||||
workspace_builds.created_at,
|
||||
workspace_builds.updated_at,
|
||||
workspace_builds.workspace_id,
|
||||
workspace_builds.template_version_id,
|
||||
workspace_builds.build_number,
|
||||
workspace_builds.transition,
|
||||
workspace_builds.initiator_id,
|
||||
workspace_builds.provisioner_state,
|
||||
workspace_builds.job_id,
|
||||
workspace_builds.deadline,
|
||||
workspace_builds.reason,
|
||||
workspace_builds.daily_cost,
|
||||
workspace_builds.max_deadline,
|
||||
workspace_builds.template_version_preset_id,
|
||||
workspace_builds.has_ai_task,
|
||||
workspace_builds.ai_task_sidebar_app_id,
|
||||
COALESCE(
|
||||
visible_users.avatar_url,
|
||||
'' :: text
|
||||
) AS initiator_by_avatar_url,
|
||||
COALESCE(
|
||||
visible_users.username,
|
||||
'' :: text
|
||||
) AS initiator_by_username,
|
||||
COALESCE(visible_users.name, '' :: text) AS initiator_by_name
|
||||
FROM
|
||||
(
|
||||
workspace_builds
|
||||
LEFT JOIN visible_users ON (
|
||||
(
|
||||
workspace_builds.initiator_id = visible_users.id
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
COMMENT ON VIEW workspace_build_with_user IS 'Joins in the username + avatar url of the initiated by user.';
|
||||
@@ -3865,7 +3865,7 @@ type WorkspaceBuild struct {
|
||||
MaxDeadline time.Time `db:"max_deadline" json:"max_deadline"`
|
||||
TemplateVersionPresetID uuid.NullUUID `db:"template_version_preset_id" json:"template_version_preset_id"`
|
||||
HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"`
|
||||
AITasksSidebarAppID uuid.NullUUID `db:"ai_tasks_sidebar_app_id" json:"ai_tasks_sidebar_app_id"`
|
||||
AITaskSidebarAppID uuid.NullUUID `db:"ai_task_sidebar_app_id" json:"ai_task_sidebar_app_id"`
|
||||
InitiatorByAvatarUrl string `db:"initiator_by_avatar_url" json:"initiator_by_avatar_url"`
|
||||
InitiatorByUsername string `db:"initiator_by_username" json:"initiator_by_username"`
|
||||
InitiatorByName string `db:"initiator_by_name" json:"initiator_by_name"`
|
||||
@@ -3896,7 +3896,7 @@ type WorkspaceBuildTable struct {
|
||||
MaxDeadline time.Time `db:"max_deadline" json:"max_deadline"`
|
||||
TemplateVersionPresetID uuid.NullUUID `db:"template_version_preset_id" json:"template_version_preset_id"`
|
||||
HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"`
|
||||
AITasksSidebarAppID uuid.NullUUID `db:"ai_tasks_sidebar_app_id" json:"ai_tasks_sidebar_app_id"`
|
||||
AITaskSidebarAppID uuid.NullUUID `db:"ai_task_sidebar_app_id" json:"ai_task_sidebar_app_id"`
|
||||
}
|
||||
|
||||
type WorkspaceLatestBuild struct {
|
||||
|
||||
@@ -596,6 +596,7 @@ type sqlcQuerier interface {
|
||||
UpdateTemplateDeletedByID(ctx context.Context, arg UpdateTemplateDeletedByIDParams) error
|
||||
UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error
|
||||
UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) error
|
||||
UpdateTemplateVersionAITaskByJobID(ctx context.Context, arg UpdateTemplateVersionAITaskByJobIDParams) error
|
||||
UpdateTemplateVersionByID(ctx context.Context, arg UpdateTemplateVersionByIDParams) error
|
||||
UpdateTemplateVersionDescriptionByJobID(ctx context.Context, arg UpdateTemplateVersionDescriptionByJobIDParams) error
|
||||
UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context.Context, arg UpdateTemplateVersionExternalAuthProvidersByJobIDParams) error
|
||||
@@ -625,6 +626,7 @@ type sqlcQuerier interface {
|
||||
UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error
|
||||
UpdateWorkspaceAutomaticUpdates(ctx context.Context, arg UpdateWorkspaceAutomaticUpdatesParams) error
|
||||
UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error
|
||||
UpdateWorkspaceBuildAITaskByID(ctx context.Context, arg UpdateWorkspaceBuildAITaskByIDParams) error
|
||||
UpdateWorkspaceBuildCostByID(ctx context.Context, arg UpdateWorkspaceBuildCostByIDParams) error
|
||||
UpdateWorkspaceBuildDeadlineByID(ctx context.Context, arg UpdateWorkspaceBuildDeadlineByIDParams) error
|
||||
UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg UpdateWorkspaceBuildProvisionerStateByIDParams) error
|
||||
|
||||
@@ -11926,11 +11926,10 @@ INSERT INTO
|
||||
readme,
|
||||
job_id,
|
||||
created_by,
|
||||
source_example_id,
|
||||
has_ai_task
|
||||
source_example_id
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
`
|
||||
|
||||
type InsertTemplateVersionParams struct {
|
||||
@@ -11945,7 +11944,6 @@ type InsertTemplateVersionParams struct {
|
||||
JobID uuid.UUID `db:"job_id" json:"job_id"`
|
||||
CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
|
||||
SourceExampleID sql.NullString `db:"source_example_id" json:"source_example_id"`
|
||||
HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertTemplateVersion(ctx context.Context, arg InsertTemplateVersionParams) error {
|
||||
@@ -11961,7 +11959,6 @@ func (q *sqlQuerier) InsertTemplateVersion(ctx context.Context, arg InsertTempla
|
||||
arg.JobID,
|
||||
arg.CreatedBy,
|
||||
arg.SourceExampleID,
|
||||
arg.HasAITask,
|
||||
)
|
||||
return err
|
||||
}
|
||||
@@ -11987,6 +11984,27 @@ func (q *sqlQuerier) UnarchiveTemplateVersion(ctx context.Context, arg Unarchive
|
||||
return err
|
||||
}
|
||||
|
||||
const updateTemplateVersionAITaskByJobID = `-- name: UpdateTemplateVersionAITaskByJobID :exec
|
||||
UPDATE
|
||||
template_versions
|
||||
SET
|
||||
has_ai_task = $2,
|
||||
updated_at = $3
|
||||
WHERE
|
||||
job_id = $1
|
||||
`
|
||||
|
||||
type UpdateTemplateVersionAITaskByJobIDParams struct {
|
||||
JobID uuid.UUID `db:"job_id" json:"job_id"`
|
||||
HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateTemplateVersionAITaskByJobID(ctx context.Context, arg UpdateTemplateVersionAITaskByJobIDParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateTemplateVersionAITaskByJobID, arg.JobID, arg.HasAITask, arg.UpdatedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateTemplateVersionByID = `-- name: UpdateTemplateVersionByID :exec
|
||||
UPDATE
|
||||
template_versions
|
||||
@@ -14312,7 +14330,7 @@ const getWorkspaceAgentAndLatestBuildByAuthToken = `-- name: GetWorkspaceAgentAn
|
||||
SELECT
|
||||
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at,
|
||||
workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.expanded_directory, workspace_agents.logs_length, workspace_agents.logs_overflowed, workspace_agents.started_at, workspace_agents.ready_at, workspace_agents.subsystems, workspace_agents.display_apps, workspace_agents.api_version, workspace_agents.display_order, workspace_agents.parent_id, workspace_agents.api_key_scope, workspace_agents.deleted,
|
||||
workspace_build_with_user.id, workspace_build_with_user.created_at, workspace_build_with_user.updated_at, workspace_build_with_user.workspace_id, workspace_build_with_user.template_version_id, workspace_build_with_user.build_number, workspace_build_with_user.transition, workspace_build_with_user.initiator_id, workspace_build_with_user.provisioner_state, workspace_build_with_user.job_id, workspace_build_with_user.deadline, workspace_build_with_user.reason, workspace_build_with_user.daily_cost, workspace_build_with_user.max_deadline, workspace_build_with_user.template_version_preset_id, workspace_build_with_user.has_ai_task, workspace_build_with_user.ai_tasks_sidebar_app_id, workspace_build_with_user.initiator_by_avatar_url, workspace_build_with_user.initiator_by_username, workspace_build_with_user.initiator_by_name
|
||||
workspace_build_with_user.id, workspace_build_with_user.created_at, workspace_build_with_user.updated_at, workspace_build_with_user.workspace_id, workspace_build_with_user.template_version_id, workspace_build_with_user.build_number, workspace_build_with_user.transition, workspace_build_with_user.initiator_id, workspace_build_with_user.provisioner_state, workspace_build_with_user.job_id, workspace_build_with_user.deadline, workspace_build_with_user.reason, workspace_build_with_user.daily_cost, workspace_build_with_user.max_deadline, workspace_build_with_user.template_version_preset_id, workspace_build_with_user.has_ai_task, workspace_build_with_user.ai_task_sidebar_app_id, workspace_build_with_user.initiator_by_avatar_url, workspace_build_with_user.initiator_by_username, workspace_build_with_user.initiator_by_name
|
||||
FROM
|
||||
workspace_agents
|
||||
JOIN
|
||||
@@ -14422,7 +14440,7 @@ func (q *sqlQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Cont
|
||||
&i.WorkspaceBuild.MaxDeadline,
|
||||
&i.WorkspaceBuild.TemplateVersionPresetID,
|
||||
&i.WorkspaceBuild.HasAITask,
|
||||
&i.WorkspaceBuild.AITasksSidebarAppID,
|
||||
&i.WorkspaceBuild.AITaskSidebarAppID,
|
||||
&i.WorkspaceBuild.InitiatorByAvatarUrl,
|
||||
&i.WorkspaceBuild.InitiatorByUsername,
|
||||
&i.WorkspaceBuild.InitiatorByName,
|
||||
@@ -17039,7 +17057,7 @@ func (q *sqlQuerier) InsertWorkspaceBuildParameters(ctx context.Context, arg Ins
|
||||
}
|
||||
|
||||
const getActiveWorkspaceBuildsByTemplateID = `-- name: GetActiveWorkspaceBuildsByTemplateID :many
|
||||
SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_tasks_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name
|
||||
SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name
|
||||
FROM (
|
||||
SELECT
|
||||
workspace_id, MAX(build_number) as max_build_number
|
||||
@@ -17095,7 +17113,7 @@ func (q *sqlQuerier) GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, t
|
||||
&i.MaxDeadline,
|
||||
&i.TemplateVersionPresetID,
|
||||
&i.HasAITask,
|
||||
&i.AITasksSidebarAppID,
|
||||
&i.AITaskSidebarAppID,
|
||||
&i.InitiatorByAvatarUrl,
|
||||
&i.InitiatorByUsername,
|
||||
&i.InitiatorByName,
|
||||
@@ -17195,7 +17213,7 @@ func (q *sqlQuerier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, a
|
||||
|
||||
const getLatestWorkspaceBuildByWorkspaceID = `-- name: GetLatestWorkspaceBuildByWorkspaceID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_tasks_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
|
||||
FROM
|
||||
workspace_build_with_user AS workspace_builds
|
||||
WHERE
|
||||
@@ -17226,7 +17244,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, w
|
||||
&i.MaxDeadline,
|
||||
&i.TemplateVersionPresetID,
|
||||
&i.HasAITask,
|
||||
&i.AITasksSidebarAppID,
|
||||
&i.AITaskSidebarAppID,
|
||||
&i.InitiatorByAvatarUrl,
|
||||
&i.InitiatorByUsername,
|
||||
&i.InitiatorByName,
|
||||
@@ -17235,7 +17253,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, w
|
||||
}
|
||||
|
||||
const getLatestWorkspaceBuilds = `-- name: GetLatestWorkspaceBuilds :many
|
||||
SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_tasks_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name
|
||||
SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name
|
||||
FROM (
|
||||
SELECT
|
||||
workspace_id, MAX(build_number) as max_build_number
|
||||
@@ -17275,7 +17293,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceB
|
||||
&i.MaxDeadline,
|
||||
&i.TemplateVersionPresetID,
|
||||
&i.HasAITask,
|
||||
&i.AITasksSidebarAppID,
|
||||
&i.AITaskSidebarAppID,
|
||||
&i.InitiatorByAvatarUrl,
|
||||
&i.InitiatorByUsername,
|
||||
&i.InitiatorByName,
|
||||
@@ -17294,7 +17312,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceB
|
||||
}
|
||||
|
||||
const getLatestWorkspaceBuildsByWorkspaceIDs = `-- name: GetLatestWorkspaceBuildsByWorkspaceIDs :many
|
||||
SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_tasks_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name
|
||||
SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name
|
||||
FROM (
|
||||
SELECT
|
||||
workspace_id, MAX(build_number) as max_build_number
|
||||
@@ -17336,7 +17354,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context,
|
||||
&i.MaxDeadline,
|
||||
&i.TemplateVersionPresetID,
|
||||
&i.HasAITask,
|
||||
&i.AITasksSidebarAppID,
|
||||
&i.AITaskSidebarAppID,
|
||||
&i.InitiatorByAvatarUrl,
|
||||
&i.InitiatorByUsername,
|
||||
&i.InitiatorByName,
|
||||
@@ -17356,7 +17374,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context,
|
||||
|
||||
const getWorkspaceBuildByID = `-- name: GetWorkspaceBuildByID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_tasks_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
|
||||
FROM
|
||||
workspace_build_with_user AS workspace_builds
|
||||
WHERE
|
||||
@@ -17385,7 +17403,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (W
|
||||
&i.MaxDeadline,
|
||||
&i.TemplateVersionPresetID,
|
||||
&i.HasAITask,
|
||||
&i.AITasksSidebarAppID,
|
||||
&i.AITaskSidebarAppID,
|
||||
&i.InitiatorByAvatarUrl,
|
||||
&i.InitiatorByUsername,
|
||||
&i.InitiatorByName,
|
||||
@@ -17395,7 +17413,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (W
|
||||
|
||||
const getWorkspaceBuildByJobID = `-- name: GetWorkspaceBuildByJobID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_tasks_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
|
||||
FROM
|
||||
workspace_build_with_user AS workspace_builds
|
||||
WHERE
|
||||
@@ -17424,7 +17442,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UU
|
||||
&i.MaxDeadline,
|
||||
&i.TemplateVersionPresetID,
|
||||
&i.HasAITask,
|
||||
&i.AITasksSidebarAppID,
|
||||
&i.AITaskSidebarAppID,
|
||||
&i.InitiatorByAvatarUrl,
|
||||
&i.InitiatorByUsername,
|
||||
&i.InitiatorByName,
|
||||
@@ -17434,7 +17452,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UU
|
||||
|
||||
const getWorkspaceBuildByWorkspaceIDAndBuildNumber = `-- name: GetWorkspaceBuildByWorkspaceIDAndBuildNumber :one
|
||||
SELECT
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_tasks_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
|
||||
FROM
|
||||
workspace_build_with_user AS workspace_builds
|
||||
WHERE
|
||||
@@ -17467,7 +17485,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Co
|
||||
&i.MaxDeadline,
|
||||
&i.TemplateVersionPresetID,
|
||||
&i.HasAITask,
|
||||
&i.AITasksSidebarAppID,
|
||||
&i.AITaskSidebarAppID,
|
||||
&i.InitiatorByAvatarUrl,
|
||||
&i.InitiatorByUsername,
|
||||
&i.InitiatorByName,
|
||||
@@ -17544,7 +17562,7 @@ func (q *sqlQuerier) GetWorkspaceBuildStatsByTemplates(ctx context.Context, sinc
|
||||
|
||||
const getWorkspaceBuildsByWorkspaceID = `-- name: GetWorkspaceBuildsByWorkspaceID :many
|
||||
SELECT
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_tasks_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
|
||||
FROM
|
||||
workspace_build_with_user AS workspace_builds
|
||||
WHERE
|
||||
@@ -17616,7 +17634,7 @@ func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg Ge
|
||||
&i.MaxDeadline,
|
||||
&i.TemplateVersionPresetID,
|
||||
&i.HasAITask,
|
||||
&i.AITasksSidebarAppID,
|
||||
&i.AITaskSidebarAppID,
|
||||
&i.InitiatorByAvatarUrl,
|
||||
&i.InitiatorByUsername,
|
||||
&i.InitiatorByName,
|
||||
@@ -17635,7 +17653,7 @@ func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg Ge
|
||||
}
|
||||
|
||||
const getWorkspaceBuildsCreatedAfter = `-- name: GetWorkspaceBuildsCreatedAfter :many
|
||||
SELECT id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_tasks_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user WHERE created_at > $1
|
||||
SELECT id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user WHERE created_at > $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceBuild, error) {
|
||||
@@ -17664,7 +17682,7 @@ func (q *sqlQuerier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, created
|
||||
&i.MaxDeadline,
|
||||
&i.TemplateVersionPresetID,
|
||||
&i.HasAITask,
|
||||
&i.AITasksSidebarAppID,
|
||||
&i.AITaskSidebarAppID,
|
||||
&i.InitiatorByAvatarUrl,
|
||||
&i.InitiatorByUsername,
|
||||
&i.InitiatorByName,
|
||||
@@ -17698,11 +17716,10 @@ INSERT INTO
|
||||
deadline,
|
||||
max_deadline,
|
||||
reason,
|
||||
template_version_preset_id,
|
||||
has_ai_task
|
||||
template_version_preset_id
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
||||
`
|
||||
|
||||
type InsertWorkspaceBuildParams struct {
|
||||
@@ -17720,7 +17737,6 @@ type InsertWorkspaceBuildParams struct {
|
||||
MaxDeadline time.Time `db:"max_deadline" json:"max_deadline"`
|
||||
Reason BuildReason `db:"reason" json:"reason"`
|
||||
TemplateVersionPresetID uuid.NullUUID `db:"template_version_preset_id" json:"template_version_preset_id"`
|
||||
HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) error {
|
||||
@@ -17739,7 +17755,33 @@ func (q *sqlQuerier) InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspa
|
||||
arg.MaxDeadline,
|
||||
arg.Reason,
|
||||
arg.TemplateVersionPresetID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateWorkspaceBuildAITaskByID = `-- name: UpdateWorkspaceBuildAITaskByID :exec
|
||||
UPDATE
|
||||
workspace_builds
|
||||
SET
|
||||
has_ai_task = $1,
|
||||
ai_task_sidebar_app_id = $2,
|
||||
updated_at = $3::timestamptz
|
||||
WHERE id = $4::uuid
|
||||
`
|
||||
|
||||
type UpdateWorkspaceBuildAITaskByIDParams struct {
|
||||
HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"`
|
||||
SidebarAppID uuid.NullUUID `db:"sidebar_app_id" json:"sidebar_app_id"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateWorkspaceBuildAITaskByID(ctx context.Context, arg UpdateWorkspaceBuildAITaskByIDParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateWorkspaceBuildAITaskByID,
|
||||
arg.HasAITask,
|
||||
arg.SidebarAppID,
|
||||
arg.UpdatedAt,
|
||||
arg.ID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -88,11 +88,10 @@ INSERT INTO
|
||||
readme,
|
||||
job_id,
|
||||
created_by,
|
||||
source_example_id,
|
||||
has_ai_task
|
||||
source_example_id
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);
|
||||
|
||||
-- name: UpdateTemplateVersionByID :exec
|
||||
UPDATE
|
||||
@@ -123,6 +122,15 @@ SET
|
||||
WHERE
|
||||
job_id = $1;
|
||||
|
||||
-- name: UpdateTemplateVersionAITaskByJobID :exec
|
||||
UPDATE
|
||||
template_versions
|
||||
SET
|
||||
has_ai_task = $2,
|
||||
updated_at = $3
|
||||
WHERE
|
||||
job_id = $1;
|
||||
|
||||
-- name: GetPreviousTemplateVersion :one
|
||||
SELECT
|
||||
*
|
||||
|
||||
@@ -121,11 +121,10 @@ INSERT INTO
|
||||
deadline,
|
||||
max_deadline,
|
||||
reason,
|
||||
template_version_preset_id,
|
||||
has_ai_task
|
||||
template_version_preset_id
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15);
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);
|
||||
|
||||
-- name: UpdateWorkspaceBuildCostByID :exec
|
||||
UPDATE
|
||||
@@ -152,6 +151,15 @@ SET
|
||||
updated_at = @updated_at::timestamptz
|
||||
WHERE id = @id::uuid;
|
||||
|
||||
-- name: UpdateWorkspaceBuildAITaskByID :exec
|
||||
UPDATE
|
||||
workspace_builds
|
||||
SET
|
||||
has_ai_task = @has_ai_task,
|
||||
ai_task_sidebar_app_id = @sidebar_app_id,
|
||||
updated_at = @updated_at::timestamptz
|
||||
WHERE id = @id::uuid;
|
||||
|
||||
-- name: GetActiveWorkspaceBuildsByTemplateID :many
|
||||
SELECT wb.*
|
||||
FROM (
|
||||
|
||||
@@ -148,7 +148,7 @@ sql:
|
||||
crypto_key_feature_oidc_convert: CryptoKeyFeatureOIDCConvert
|
||||
stale_interval_ms: StaleIntervalMS
|
||||
has_ai_task: HasAITask
|
||||
ai_tasks_sidebar_app_id: AITasksSidebarAppID
|
||||
ai_task_sidebar_app_id: AITaskSidebarAppID
|
||||
latest_build_has_ai_task: LatestBuildHasAITask
|
||||
rules:
|
||||
- name: do-not-use-public-schema-in-queries
|
||||
|
||||
@@ -1655,6 +1655,17 @@ func (s *server) completeTemplateImportJob(ctx context.Context, job database.Pro
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update template version external auth providers: %w", err)
|
||||
}
|
||||
err = db.UpdateTemplateVersionAITaskByJobID(ctx, database.UpdateTemplateVersionAITaskByJobIDParams{
|
||||
JobID: jobID,
|
||||
HasAITask: sql.NullBool{
|
||||
Bool: jobType.TemplateImport.HasAiTasks,
|
||||
Valid: true,
|
||||
},
|
||||
UpdatedAt: now,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update template version external auth providers: %w", err)
|
||||
}
|
||||
|
||||
// Process terraform values
|
||||
plan := jobType.TemplateImport.Plan
|
||||
@@ -1867,6 +1878,37 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
|
||||
}
|
||||
}
|
||||
|
||||
var sidebarAppID uuid.NullUUID
|
||||
hasAITask := len(jobType.WorkspaceBuild.AiTasks) == 1
|
||||
if hasAITask {
|
||||
task := jobType.WorkspaceBuild.AiTasks[0]
|
||||
if task.SidebarApp == nil {
|
||||
return xerrors.Errorf("update ai task: sidebar app is nil")
|
||||
}
|
||||
|
||||
id, err := uuid.Parse(task.SidebarApp.Id)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse sidebar app id: %w", err)
|
||||
}
|
||||
|
||||
sidebarAppID = uuid.NullUUID{UUID: id, Valid: true}
|
||||
}
|
||||
|
||||
// Regardless of whether there is an AI task or not, update the field to indicate one way or the other since it
|
||||
// always defaults to nil. ONLY if has_ai_task=true MUST ai_task_sidebar_app_id be set.
|
||||
err = db.UpdateWorkspaceBuildAITaskByID(ctx, database.UpdateWorkspaceBuildAITaskByIDParams{
|
||||
ID: workspaceBuild.ID,
|
||||
HasAITask: sql.NullBool{
|
||||
Bool: hasAITask,
|
||||
Valid: true,
|
||||
},
|
||||
SidebarAppID: sidebarAppID,
|
||||
UpdatedAt: now,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update workspace build ai tasks flag: %w", err)
|
||||
}
|
||||
|
||||
// Insert timings inside the transaction now
|
||||
// nolint:exhaustruct // The other fields are set further down.
|
||||
params := database.InsertProvisionerJobTimingsParams{
|
||||
|
||||
@@ -2435,6 +2435,233 @@ func TestCompleteJob(t *testing.T) {
|
||||
testutil.RequireReceive(ctx, t, done)
|
||||
require.Equal(t, replacements, orchestrator.replacements)
|
||||
})
|
||||
|
||||
t.Run("AITasks", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// has_ai_task has a default value of nil, but once the template import completes it will have a value;
|
||||
// it is set to "true" if the template has any coder_ai_task resources defined.
|
||||
t.Run("TemplateImport", func(t *testing.T) {
|
||||
type testcase struct {
|
||||
name string
|
||||
input *proto.CompletedJob_TemplateImport
|
||||
expected bool
|
||||
}
|
||||
|
||||
for _, tc := range []testcase{
|
||||
{
|
||||
name: "has_ai_task is false by default",
|
||||
input: &proto.CompletedJob_TemplateImport{
|
||||
// HasAiTasks is not set.
|
||||
Plan: []byte("{}"),
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "has_ai_task gets set to true",
|
||||
input: &proto.CompletedJob_TemplateImport{
|
||||
HasAiTasks: true,
|
||||
Plan: []byte("{}"),
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv, db, _, pd := setup(t, false, &overrides{})
|
||||
|
||||
importJobID := uuid.New()
|
||||
tvID := uuid.New()
|
||||
template := dbgen.Template(t, db, database.Template{
|
||||
Name: "template",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
version := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
ID: tvID,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
TemplateID: uuid.NullUUID{
|
||||
UUID: template.ID,
|
||||
Valid: true,
|
||||
},
|
||||
JobID: importJobID,
|
||||
})
|
||||
_ = version
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
job, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
||||
ID: importJobID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
OrganizationID: pd.OrganizationID,
|
||||
InitiatorID: uuid.New(),
|
||||
Input: must(json.Marshal(provisionerdserver.TemplateVersionImportJob{
|
||||
TemplateVersionID: tvID,
|
||||
})),
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
||||
Type: database.ProvisionerJobTypeTemplateVersionImport,
|
||||
Tags: pd.Tags,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
||||
OrganizationID: pd.OrganizationID,
|
||||
WorkerID: uuid.NullUUID{
|
||||
UUID: pd.ID,
|
||||
Valid: true,
|
||||
},
|
||||
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
||||
ProvisionerTags: must(json.Marshal(job.Tags)),
|
||||
StartedAt: sql.NullTime{Time: job.CreatedAt, Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
version, err = db.GetTemplateVersionByID(ctx, tvID)
|
||||
require.NoError(t, err)
|
||||
require.False(t, version.HasAITask.Valid) // Value should be nil (i.e. valid = false).
|
||||
|
||||
completedJob := proto.CompletedJob{
|
||||
JobId: job.ID.String(),
|
||||
Type: &proto.CompletedJob_TemplateImport_{
|
||||
TemplateImport: tc.input,
|
||||
},
|
||||
}
|
||||
_, err = srv.CompleteJob(ctx, &completedJob)
|
||||
require.NoError(t, err)
|
||||
|
||||
version, err = db.GetTemplateVersionByID(ctx, tvID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, version.HasAITask.Valid) // We ALWAYS expect a value to be set, therefore not nil, i.e. valid = true.
|
||||
require.Equal(t, tc.expected, version.HasAITask.Bool)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// has_ai_task has a default value of nil, but once the workspace build completes it will have a value;
|
||||
// it is set to "true" if the related template has any coder_ai_task resources defined, and its sidebar app ID
|
||||
// will be set as well in that case.
|
||||
t.Run("WorkspaceBuild", func(t *testing.T) {
|
||||
type testcase struct {
|
||||
name string
|
||||
input *proto.CompletedJob_WorkspaceBuild
|
||||
expected bool
|
||||
}
|
||||
|
||||
sidebarAppID := uuid.NewString()
|
||||
for _, tc := range []testcase{
|
||||
{
|
||||
name: "has_ai_task is false by default",
|
||||
input: &proto.CompletedJob_WorkspaceBuild{
|
||||
// No AiTasks defined.
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "has_ai_task is set to true",
|
||||
input: &proto.CompletedJob_WorkspaceBuild{
|
||||
AiTasks: []*sdkproto.AITask{
|
||||
{
|
||||
Id: uuid.NewString(),
|
||||
SidebarApp: &sdkproto.AITaskSidebarApp{
|
||||
Id: sidebarAppID,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv, db, _, pd := setup(t, false, &overrides{})
|
||||
|
||||
importJobID := uuid.New()
|
||||
tvID := uuid.New()
|
||||
template := dbgen.Template(t, db, database.Template{
|
||||
Name: "template",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
version := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
ID: tvID,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
TemplateID: uuid.NullUUID{
|
||||
UUID: template.ID,
|
||||
Valid: true,
|
||||
},
|
||||
JobID: importJobID,
|
||||
})
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
workspaceTable := dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
TemplateID: template.ID,
|
||||
OwnerID: user.ID,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
build := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
WorkspaceID: workspaceTable.ID,
|
||||
TemplateVersionID: version.ID,
|
||||
InitiatorID: user.ID,
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
job, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
||||
ID: importJobID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
OrganizationID: pd.OrganizationID,
|
||||
InitiatorID: uuid.New(),
|
||||
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
||||
WorkspaceBuildID: build.ID,
|
||||
LogLevel: "DEBUG",
|
||||
})),
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
Tags: pd.Tags,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
||||
OrganizationID: pd.OrganizationID,
|
||||
WorkerID: uuid.NullUUID{
|
||||
UUID: pd.ID,
|
||||
Valid: true,
|
||||
},
|
||||
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
||||
ProvisionerTags: must(json.Marshal(job.Tags)),
|
||||
StartedAt: sql.NullTime{Time: job.CreatedAt, Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
build, err = db.GetWorkspaceBuildByID(ctx, build.ID)
|
||||
require.NoError(t, err)
|
||||
require.False(t, build.HasAITask.Valid) // Value should be nil (i.e. valid = false).
|
||||
|
||||
completedJob := proto.CompletedJob{
|
||||
JobId: job.ID.String(),
|
||||
Type: &proto.CompletedJob_WorkspaceBuild_{
|
||||
WorkspaceBuild: tc.input,
|
||||
},
|
||||
}
|
||||
_, err = srv.CompleteJob(ctx, &completedJob)
|
||||
require.NoError(t, err)
|
||||
|
||||
build, err = db.GetWorkspaceBuildByID(ctx, build.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, build.HasAITask.Valid) // We ALWAYS expect a value to be set, therefore not nil, i.e. valid = true.
|
||||
require.Equal(t, tc.expected, build.HasAITask.Bool)
|
||||
|
||||
if tc.expected {
|
||||
require.Equal(t, sidebarAppID, build.AITaskSidebarAppID.UUID.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type mockPrebuildsOrchestrator struct {
|
||||
|
||||
@@ -1730,12 +1730,6 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht
|
||||
String: req.ExampleID,
|
||||
Valid: req.ExampleID != "",
|
||||
},
|
||||
// appease the exhaustruct linter
|
||||
// TODO: set this to whether the template version defines a `coder_ai_task` tf resource
|
||||
HasAITask: sql.NullBool{
|
||||
Bool: false,
|
||||
Valid: false,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if database.IsUniqueViolation(err, database.UniqueTemplateVersionsTemplateIDNameKey) {
|
||||
|
||||
@@ -1108,8 +1108,8 @@ func (api *API) convertWorkspaceBuild(
|
||||
hasAITask = &build.HasAITask.Bool
|
||||
}
|
||||
var aiTasksSidebarAppID *uuid.UUID
|
||||
if build.AITasksSidebarAppID.Valid {
|
||||
aiTasksSidebarAppID = &build.AITasksSidebarAppID.UUID
|
||||
if build.AITaskSidebarAppID.Valid {
|
||||
aiTasksSidebarAppID = &build.AITaskSidebarAppID.UUID
|
||||
}
|
||||
|
||||
apiJob := convertProvisionerJob(job)
|
||||
|
||||
@@ -4509,7 +4509,10 @@ func TestWorkspaceFilterHasAITask(t *testing.T) {
|
||||
|
||||
// Helper function to create workspace with AI task configuration
|
||||
createWorkspaceWithAIConfig := func(hasAITask sql.NullBool, jobCompleted bool, aiTaskPrompt *string) database.WorkspaceTable {
|
||||
// When a provisioner job uses these tags, no provisioner will match it
|
||||
// When a provisioner job uses these tags, no provisioner will match it.
|
||||
// We do this so jobs will always be stuck in "pending", allowing us to exercise the intermediary state when
|
||||
// has_ai_task is nil and we compensate by looking at pending provisioning jobs.
|
||||
// See GetWorkspaces clauses.
|
||||
unpickableTags := database.StringMap{"custom": "true"}
|
||||
|
||||
ws := dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
@@ -4528,20 +4531,30 @@ func TestWorkspaceFilterHasAITask(t *testing.T) {
|
||||
}
|
||||
job := dbgen.ProvisionerJob(t, db, pubsub, jobConfig)
|
||||
|
||||
res := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{JobID: job.ID})
|
||||
agnt := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ResourceID: res.ID})
|
||||
|
||||
var sidebarAppID uuid.UUID
|
||||
if hasAITask.Bool {
|
||||
sidebarApp := dbgen.WorkspaceApp(t, db, database.WorkspaceApp{AgentID: agnt.ID})
|
||||
sidebarAppID = sidebarApp.ID
|
||||
}
|
||||
|
||||
build := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
WorkspaceID: ws.ID,
|
||||
TemplateVersionID: version.ID,
|
||||
InitiatorID: user.UserID,
|
||||
JobID: job.ID,
|
||||
BuildNumber: 1,
|
||||
HasAITask: hasAITask,
|
||||
WorkspaceID: ws.ID,
|
||||
TemplateVersionID: version.ID,
|
||||
InitiatorID: user.UserID,
|
||||
JobID: job.ID,
|
||||
BuildNumber: 1,
|
||||
HasAITask: hasAITask,
|
||||
AITaskSidebarAppID: uuid.NullUUID{UUID: sidebarAppID, Valid: sidebarAppID != uuid.Nil},
|
||||
})
|
||||
|
||||
if aiTaskPrompt != nil {
|
||||
//nolint:gocritic // unit test
|
||||
err := db.InsertWorkspaceBuildParameters(dbauthz.AsSystemRestricted(ctx), database.InsertWorkspaceBuildParametersParams{
|
||||
WorkspaceBuildID: build.ID,
|
||||
Name: []string{"AI Prompt"},
|
||||
Name: []string{provider.TaskPromptParameterName},
|
||||
Value: []string{*aiTaskPrompt},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -4551,7 +4564,7 @@ func TestWorkspaceFilterHasAITask(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create test workspaces with different AI task configurations
|
||||
wsWithAITask := createWorkspaceWithAIConfig(sql.NullBool{Bool: true, Valid: true}, false, nil)
|
||||
wsWithAITask := createWorkspaceWithAIConfig(sql.NullBool{Bool: true, Valid: true}, true, nil)
|
||||
wsWithoutAITask := createWorkspaceWithAIConfig(sql.NullBool{Bool: false, Valid: true}, false, nil)
|
||||
|
||||
aiTaskPrompt := "Build me a web app"
|
||||
@@ -4699,3 +4712,51 @@ func TestWorkspaceAppUpsertRestart(t *testing.T) {
|
||||
require.Equal(t, codersdk.ProvisionerJobSucceeded, workspace.LatestBuild.Job.Status)
|
||||
require.Empty(t, workspace.LatestBuild.Job.Error)
|
||||
}
|
||||
|
||||
func TestMultipleAITasksDisallowed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, pubsub := dbtestutil.NewDB(t)
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
Database: db,
|
||||
Pubsub: pubsub,
|
||||
IncludeProvisionerDaemon: true,
|
||||
})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
HasAiTasks: true,
|
||||
AiTasks: []*proto.AITask{
|
||||
{
|
||||
Id: uuid.NewString(),
|
||||
SidebarApp: &proto.AITaskSidebarApp{
|
||||
Id: uuid.NewString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: uuid.NewString(),
|
||||
SidebarApp: &proto.AITaskSidebarApp{
|
||||
Id: uuid.NewString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
|
||||
ws := coderdtest.CreateWorkspace(t, client, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
|
||||
|
||||
//nolint: gocritic // testing
|
||||
ctx := dbauthz.AsSystemRestricted(t.Context())
|
||||
pj, err := db.GetProvisionerJobByID(ctx, ws.LatestBuild.Job.ID)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, pj.Error.String, "only one 'coder_ai_task' resource can be provisioned per template")
|
||||
}
|
||||
|
||||
@@ -426,12 +426,6 @@ func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Object
|
||||
UUID: b.templateVersionPresetID,
|
||||
Valid: b.templateVersionPresetID != uuid.Nil,
|
||||
},
|
||||
// appease the exhaustruct linter
|
||||
// TODO: set this to whether the build included a `coder_ai_task` tf resource
|
||||
HasAITask: sql.NullBool{
|
||||
Bool: false,
|
||||
Valid: false,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
code := http.StatusInternalServerError
|
||||
|
||||
Reference in New Issue
Block a user