mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat(coderd): use task data model when fetching a task (#20380)
Updates coder/internal#976
This commit is contained in:
committed by
GitHub
parent
9855460524
commit
c6f63990cf
@@ -24,8 +24,6 @@ import (
|
||||
func Test_TaskStatus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Skip("TODO(mafredri): Remove, fixed down-stack!")
|
||||
|
||||
for _, tc := range []struct {
|
||||
args []string
|
||||
expectOutput string
|
||||
@@ -90,7 +88,9 @@ func Test_TaskStatus(t *testing.T) {
|
||||
Healthy: true,
|
||||
},
|
||||
WorkspaceAgentLifecycle: ptr.Ref(codersdk.WorkspaceAgentLifecycleReady),
|
||||
Status: codersdk.TaskStatusActive,
|
||||
})
|
||||
return
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
@@ -125,7 +125,9 @@ STATE CHANGED STATUS HEALTHY STATE MESSAGE
|
||||
Healthy: true,
|
||||
},
|
||||
WorkspaceAgentLifecycle: ptr.Ref(codersdk.WorkspaceAgentLifecycleReady),
|
||||
Status: codersdk.TaskStatusPending,
|
||||
})
|
||||
return
|
||||
case 1:
|
||||
httpapi.Write(ctx, w, http.StatusOK, codersdk.Task{
|
||||
ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
|
||||
@@ -136,7 +138,9 @@ STATE CHANGED STATUS HEALTHY STATE MESSAGE
|
||||
},
|
||||
WorkspaceAgentLifecycle: ptr.Ref(codersdk.WorkspaceAgentLifecycleReady),
|
||||
UpdatedAt: now.Add(-4 * time.Second),
|
||||
Status: codersdk.TaskStatusActive,
|
||||
})
|
||||
return
|
||||
case 2:
|
||||
httpapi.Write(ctx, w, http.StatusOK, codersdk.Task{
|
||||
ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
|
||||
@@ -152,7 +156,9 @@ STATE CHANGED STATUS HEALTHY STATE MESSAGE
|
||||
Timestamp: now.Add(-3 * time.Second),
|
||||
Message: "Reticulating splines...",
|
||||
},
|
||||
Status: codersdk.TaskStatusActive,
|
||||
})
|
||||
return
|
||||
case 3:
|
||||
httpapi.Write(ctx, w, http.StatusOK, codersdk.Task{
|
||||
ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
|
||||
@@ -168,13 +174,16 @@ STATE CHANGED STATUS HEALTHY STATE MESSAGE
|
||||
Timestamp: now.Add(-2 * time.Second),
|
||||
Message: "Splines reticulated successfully!",
|
||||
},
|
||||
Status: codersdk.TaskStatusActive,
|
||||
})
|
||||
return
|
||||
default:
|
||||
httpapi.InternalServerError(w, xerrors.New("too many calls!"))
|
||||
return
|
||||
}
|
||||
default:
|
||||
httpapi.InternalServerError(w, xerrors.Errorf("unexpected path: %q", r.URL.Path))
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -188,16 +197,18 @@ STATE CHANGED STATUS HEALTHY STATE MESSAGE
|
||||
"owner_name": "",
|
||||
"name": "",
|
||||
"template_id": "00000000-0000-0000-0000-000000000000",
|
||||
"template_version_id": "00000000-0000-0000-0000-000000000000",
|
||||
"template_name": "",
|
||||
"template_display_name": "",
|
||||
"template_icon": "",
|
||||
"workspace_id": null,
|
||||
"workspace_status": "running",
|
||||
"workspace_agent_id": null,
|
||||
"workspace_agent_lifecycle": null,
|
||||
"workspace_agent_health": null,
|
||||
"workspace_app_id": null,
|
||||
"initial_prompt": "",
|
||||
"status": "running",
|
||||
"status": "active",
|
||||
"current_state": {
|
||||
"timestamp": "2025-08-26T12:34:57Z",
|
||||
"state": "working",
|
||||
@@ -226,7 +237,9 @@ STATE CHANGED STATUS HEALTHY STATE MESSAGE
|
||||
Timestamp: ts.Add(time.Second),
|
||||
Message: "Thinking furiously...",
|
||||
},
|
||||
Status: codersdk.TaskStatusActive,
|
||||
})
|
||||
return
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
|
||||
+12
-52
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
"github.com/coder/coder/v2/coderd/searchquery"
|
||||
"github.com/coder/coder/v2/coderd/taskname"
|
||||
"github.com/coder/coder/v2/coderd/util/slice"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
|
||||
aiagentapi "github.com/coder/agentapi-sdk-go"
|
||||
@@ -604,25 +603,22 @@ func (api *API) tasksList(rw http.ResponseWriter, r *http.Request) {
|
||||
func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
apiKey := httpmw.APIKey(r)
|
||||
task := httpmw.TaskParam(r)
|
||||
|
||||
idStr := chi.URLParam(r, "task")
|
||||
taskID, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: fmt.Sprintf("Invalid UUID %q for task ID.", idStr),
|
||||
if !task.WorkspaceID.Valid {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching task.",
|
||||
Detail: "Task workspace ID is invalid.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// For now, taskID = workspaceID, once we have a task data model in
|
||||
// the DB, we can change this lookup.
|
||||
workspaceID := taskID
|
||||
workspace, err := api.Database.GetWorkspaceByID(ctx, workspaceID)
|
||||
if httpapi.Is404Error(err) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
workspace, err := api.Database.GetWorkspaceByID(ctx, task.WorkspaceID.UUID)
|
||||
if err != nil {
|
||||
if httpapi.Is404Error(err) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching workspace.",
|
||||
Detail: err.Error(),
|
||||
@@ -642,34 +638,6 @@ func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
if data.builds[0].HasAITask == nil || !*data.builds[0].HasAITask {
|
||||
// TODO(DanielleMaywood):
|
||||
// This is a temporary workaround. When a task has just been created, but
|
||||
// not yet provisioned, the workspace build will not have `HasAITask` set.
|
||||
//
|
||||
// When we reach this code flow, it is _either_ because the workspace is
|
||||
// not a task, or it is a task that has not yet been provisioned. This
|
||||
// endpoint should rarely be called with a non-task workspace so we
|
||||
// should be fine with this extra database call to check if it has the
|
||||
// special "AI Task" parameter.
|
||||
parameters, err := api.Database.GetWorkspaceBuildParameters(ctx, data.builds[0].ID)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching workspace build parameters.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
_, hasAITask := slice.Find(parameters, func(t database.WorkspaceBuildParameter) bool {
|
||||
return t.Name == codersdk.AITaskPromptParameterName
|
||||
})
|
||||
|
||||
if !hasAITask {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
appStatus := codersdk.WorkspaceAppStatus{}
|
||||
if len(data.appStatuses) > 0 {
|
||||
@@ -692,16 +660,8 @@ func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
tasks, err := api.tasksFromWorkspaces(ctx, []codersdk.Workspace{ws})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching task prompt and state.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, tasks[0])
|
||||
taskResp := taskFromDBTaskAndWorkspace(task, ws)
|
||||
httpapi.Write(ctx, rw, http.StatusOK, taskResp)
|
||||
}
|
||||
|
||||
// @Summary Delete AI task by ID
|
||||
|
||||
+28
-23
@@ -274,24 +274,27 @@ func TestTasks(t *testing.T) {
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Skip("TODO(mafredri): Remove, fixed down-stack!")
|
||||
|
||||
var (
|
||||
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
ctx = testutil.Context(t, testutil.WaitLong)
|
||||
user = coderdtest.CreateFirstUser(t, client)
|
||||
template = createAITemplate(t, client, user)
|
||||
// Create a workspace (task) with a specific prompt.
|
||||
wantPrompt = "review my code"
|
||||
workspace = coderdtest.CreateWorkspace(t, client, template.ID, func(req *codersdk.CreateWorkspaceRequest) {
|
||||
req.RichParameterValues = []codersdk.WorkspaceBuildParameter{
|
||||
{Name: codersdk.AITaskPromptParameterName, Value: wantPrompt},
|
||||
}
|
||||
})
|
||||
exp = codersdk.NewExperimentalClient(client)
|
||||
)
|
||||
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
ws := coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
|
||||
TemplateVersionID: template.ActiveVersionID,
|
||||
Input: wantPrompt,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.True(t, task.WorkspaceID.Valid)
|
||||
|
||||
// Get the workspace and wait for it to be ready.
|
||||
ws, err := client.Workspace(ctx, task.WorkspaceID.UUID)
|
||||
require.NoError(t, err)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
|
||||
ws = coderdtest.MustWorkspace(t, client, task.WorkspaceID.UUID)
|
||||
// Assert invariant: the workspace has exactly one resource with one agent with one app.
|
||||
require.Len(t, ws.LatestBuild.Resources, 1)
|
||||
require.Len(t, ws.LatestBuild.Resources[0].Agents, 1)
|
||||
@@ -299,9 +302,9 @@ func TestTasks(t *testing.T) {
|
||||
taskAppID := ws.LatestBuild.Resources[0].Agents[0].Apps[0].ID
|
||||
|
||||
// Insert an app status for the workspace
|
||||
_, err := db.InsertWorkspaceAppStatus(dbauthz.AsSystemRestricted(ctx), database.InsertWorkspaceAppStatusParams{
|
||||
_, err = db.InsertWorkspaceAppStatus(dbauthz.AsSystemRestricted(ctx), database.InsertWorkspaceAppStatusParams{
|
||||
ID: uuid.New(),
|
||||
WorkspaceID: workspace.ID,
|
||||
WorkspaceID: task.WorkspaceID.UUID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
AgentID: agentID,
|
||||
AppID: taskAppID,
|
||||
@@ -311,31 +314,33 @@ func TestTasks(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Fetch the task by ID via experimental API and verify fields.
|
||||
exp := codersdk.NewExperimentalClient(client)
|
||||
task, err := exp.TaskByID(ctx, workspace.ID)
|
||||
updated, err := exp.TaskByID(ctx, task.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, workspace.ID, task.ID, "task ID should match workspace ID")
|
||||
assert.Equal(t, workspace.Name, task.Name, "task name should map from workspace name")
|
||||
assert.Equal(t, wantPrompt, task.InitialPrompt, "task prompt should match the AI Prompt parameter")
|
||||
assert.Equal(t, workspace.ID, task.WorkspaceID.UUID, "workspace id should match")
|
||||
assert.NotEmpty(t, task.WorkspaceStatus, "task status should not be empty")
|
||||
assert.Equal(t, task.ID, updated.ID, "task ID should match")
|
||||
assert.Equal(t, task.Name, updated.Name, "task name should match")
|
||||
assert.Equal(t, wantPrompt, updated.InitialPrompt, "task prompt should match the AI Prompt parameter")
|
||||
assert.Equal(t, task.WorkspaceID.UUID, updated.WorkspaceID.UUID, "workspace id should match")
|
||||
assert.Equal(t, ws.LatestBuild.BuildNumber, updated.WorkspaceBuildNumber, "workspace build number should match")
|
||||
assert.Equal(t, agentID, updated.WorkspaceAgentID.UUID, "workspace agent id should match")
|
||||
assert.Equal(t, taskAppID, updated.WorkspaceAppID.UUID, "workspace app id should match")
|
||||
assert.NotEmpty(t, updated.WorkspaceStatus, "task status should not be empty")
|
||||
|
||||
// Stop the workspace
|
||||
coderdtest.MustTransitionWorkspace(t, client, workspace.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop)
|
||||
coderdtest.MustTransitionWorkspace(t, client, task.WorkspaceID.UUID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop)
|
||||
|
||||
// Verify that the previous status still remains
|
||||
updated, err := exp.TaskByID(ctx, workspace.ID)
|
||||
updated, err = exp.TaskByID(ctx, task.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, updated.CurrentState, "current state should not be nil")
|
||||
assert.Equal(t, "all done", updated.CurrentState.Message)
|
||||
assert.Equal(t, codersdk.TaskStateComplete, updated.CurrentState.State)
|
||||
|
||||
// Start the workspace again
|
||||
coderdtest.MustTransitionWorkspace(t, client, workspace.ID, codersdk.WorkspaceTransitionStop, codersdk.WorkspaceTransitionStart)
|
||||
coderdtest.MustTransitionWorkspace(t, client, task.WorkspaceID.UUID, codersdk.WorkspaceTransitionStop, codersdk.WorkspaceTransitionStart)
|
||||
|
||||
// Verify that the status from the previous build is no longer present
|
||||
updated, err = exp.TaskByID(ctx, workspace.ID)
|
||||
updated, err = exp.TaskByID(ctx, task.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, updated.CurrentState, "current state should be nil")
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user