mirror of
https://github.com/coder/coder.git
synced 2026-09-21 12:44:32 +08:00
fix(coderd)!: only show task status for current build (#19966)
This changes the task get endpoint to omit app statuses for previous 'lifetimes' of a workspace. It also introduces a [breaking change](https://github.com/coder/coder/blob/release/2.26/codersdk/aitasks.go#L83) to bring `TaskStateComplete` in line with `WorkspaceAppStatusStateComplete`. I can alternatively revert this change and add a conversion function between the two SDK types.
This commit is contained in:
@@ -96,9 +96,9 @@ func Test_TaskStatus(t *testing.T) {
|
||||
STATE CHANGED STATUS STATE MESSAGE
|
||||
4s ago running
|
||||
3s ago running working Reticulating splines...
|
||||
2s ago running completed Splines reticulated successfully!
|
||||
2s ago stopping completed Splines reticulated successfully!
|
||||
2s ago stopped completed Splines reticulated successfully!`,
|
||||
2s ago running complete Splines reticulated successfully!
|
||||
2s ago stopping complete Splines reticulated successfully!
|
||||
2s ago stopped complete Splines reticulated successfully!`,
|
||||
hf: func(ctx context.Context, now time.Time) func(http.ResponseWriter, *http.Request) {
|
||||
var calls atomic.Int64
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -143,7 +143,7 @@ STATE CHANGED STATUS STATE MESSAGE
|
||||
CreatedAt: now.Add(-5 * time.Second),
|
||||
UpdatedAt: now.Add(-4 * time.Second),
|
||||
CurrentState: &codersdk.TaskStateEntry{
|
||||
State: codersdk.TaskStateCompleted,
|
||||
State: codersdk.TaskStateComplete,
|
||||
Timestamp: now.Add(-2 * time.Second),
|
||||
Message: "Splines reticulated successfully!",
|
||||
},
|
||||
@@ -155,7 +155,7 @@ STATE CHANGED STATUS STATE MESSAGE
|
||||
CreatedAt: now.Add(-5 * time.Second),
|
||||
UpdatedAt: now.Add(-1 * time.Second),
|
||||
CurrentState: &codersdk.TaskStateEntry{
|
||||
State: codersdk.TaskStateCompleted,
|
||||
State: codersdk.TaskStateComplete,
|
||||
Timestamp: now.Add(-2 * time.Second),
|
||||
Message: "Splines reticulated successfully!",
|
||||
},
|
||||
@@ -167,7 +167,7 @@ STATE CHANGED STATUS STATE MESSAGE
|
||||
CreatedAt: now.Add(-5 * time.Second),
|
||||
UpdatedAt: now,
|
||||
CurrentState: &codersdk.TaskStateEntry{
|
||||
State: codersdk.TaskStateCompleted,
|
||||
State: codersdk.TaskStateComplete,
|
||||
Timestamp: now.Add(-2 * time.Second),
|
||||
Message: "Splines reticulated successfully!",
|
||||
},
|
||||
|
||||
+10
-5
@@ -245,13 +245,18 @@ func taskFromWorkspace(ws codersdk.Workspace, initialPrompt string) codersdk.Tas
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore 'latest app status' if it is older than the latest build and the latest build is a 'start' transition.
|
||||
// This ensures that you don't show a stale app status from a previous build.
|
||||
// For stop transitions, there is still value in showing the latest app status.
|
||||
var currentState *codersdk.TaskStateEntry
|
||||
if ws.LatestAppStatus != nil {
|
||||
currentState = &codersdk.TaskStateEntry{
|
||||
Timestamp: ws.LatestAppStatus.CreatedAt,
|
||||
State: codersdk.TaskState(ws.LatestAppStatus.State),
|
||||
Message: ws.LatestAppStatus.Message,
|
||||
URI: ws.LatestAppStatus.URI,
|
||||
if ws.LatestBuild.Transition != codersdk.WorkspaceTransitionStart || ws.LatestAppStatus.CreatedAt.After(ws.LatestBuild.CreatedAt) {
|
||||
currentState = &codersdk.TaskStateEntry{
|
||||
Timestamp: ws.LatestAppStatus.CreatedAt,
|
||||
State: codersdk.TaskState(ws.LatestAppStatus.State),
|
||||
Message: ws.LatestAppStatus.Message,
|
||||
URI: ws.LatestAppStatus.URI,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+49
-10
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/util/slice"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
||||
@@ -269,19 +270,39 @@ func TestTasks(t *testing.T) {
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
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},
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
template := createAITemplate(t, client, user)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
ws := coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
// 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)
|
||||
agentID := ws.LatestBuild.Resources[0].Agents[0].ID
|
||||
taskAppID := ws.LatestBuild.Resources[0].Agents[0].Apps[0].ID
|
||||
|
||||
// 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},
|
||||
}
|
||||
// Insert an app status for the workspace
|
||||
_, err := db.InsertWorkspaceAppStatus(dbauthz.AsSystemRestricted(ctx), database.InsertWorkspaceAppStatusParams{
|
||||
ID: uuid.New(),
|
||||
WorkspaceID: workspace.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
AgentID: agentID,
|
||||
AppID: taskAppID,
|
||||
State: database.WorkspaceAppStatusStateComplete,
|
||||
Message: "all done",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Fetch the task by ID via experimental API and verify fields.
|
||||
exp := codersdk.NewExperimentalClient(client)
|
||||
@@ -293,6 +314,24 @@ func TestTasks(t *testing.T) {
|
||||
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.Status, "task status should not be empty")
|
||||
|
||||
// Stop the workspace
|
||||
coderdtest.MustTransitionWorkspace(t, client, workspace.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop)
|
||||
|
||||
// Verify that the previous status still remains
|
||||
updated, err := exp.TaskByID(ctx, workspace.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)
|
||||
|
||||
// Verify that the status from the previous build is no longer present
|
||||
updated, err = exp.TaskByID(ctx, workspace.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, updated.CurrentState, "current state should be nil")
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
|
||||
+4
-4
@@ -96,10 +96,10 @@ type TaskState string
|
||||
|
||||
// TaskState enums.
|
||||
const (
|
||||
TaskStateWorking TaskState = "working"
|
||||
TaskStateIdle TaskState = "idle"
|
||||
TaskStateCompleted TaskState = "completed"
|
||||
TaskStateFailed TaskState = "failed"
|
||||
TaskStateWorking TaskState = "working"
|
||||
TaskStateIdle TaskState = "idle"
|
||||
TaskStateComplete TaskState = "complete"
|
||||
TaskStateFailed TaskState = "failed"
|
||||
)
|
||||
|
||||
// Task represents a task.
|
||||
|
||||
Generated
+2
-2
@@ -2907,7 +2907,7 @@ export interface TaskSendRequest {
|
||||
}
|
||||
|
||||
// From codersdk/aitasks.go
|
||||
export type TaskState = "completed" | "failed" | "idle" | "working";
|
||||
export type TaskState = "complete" | "failed" | "idle" | "working";
|
||||
|
||||
// From codersdk/aitasks.go
|
||||
export interface TaskStateEntry {
|
||||
@@ -2918,7 +2918,7 @@ export interface TaskStateEntry {
|
||||
}
|
||||
|
||||
export const TaskStates: TaskState[] = [
|
||||
"completed",
|
||||
"complete",
|
||||
"failed",
|
||||
"idle",
|
||||
"working",
|
||||
|
||||
Reference in New Issue
Block a user