diff --git a/app/lib/components/status_table.dart b/app/lib/components/status_table.dart index 06cfdd46d4..022bc47c0a 100644 --- a/app/lib/components/status_table.dart +++ b/app/lib/components/status_table.dart @@ -67,6 +67,7 @@ import 'package:http/http.dart' as http;
+ {{getAttempts(status.checklist.checklist.commit.sha, metaTask.name)}}
@@ -141,6 +142,8 @@ class StatusTable implements OnInit { String cssClass; if (taskStatus == 'Succeeded' && attempts > 1) { cssClass = 'task-succeeded-but-flaky'; + } else if (taskStatus == 'New' && attempts > 1) { + cssClass = 'task-underperformed'; } else { cssClass = statusMap[taskStatus] ?? 'task-unknown'; } @@ -166,6 +169,17 @@ class StatusTable implements OnInit { return taskStatusToCssStyle(taskEntity.task.status, taskEntity.task.attempts); } + String getAttempts(String sha, String taskName) { + TaskEntity taskEntity = _findTask(sha, taskName); + + if (taskEntity == null) + return ''; + + int attempts = taskEntity.task.attempts; + bool succeededImmediately = attempts == 1 && taskEntity.task.status == 'Succeeded'; + return attempts == 0 || succeededImmediately ? '' : '${attempts}'; + } + List getAgentStyle(AgentStatus status) { return [ 'agent-chip', diff --git a/app/web/buildStyles.css b/app/web/buildStyles.css index 5dfda47353..855c411d4f 100644 --- a/app/web/buildStyles.css +++ b/app/web/buildStyles.css @@ -284,14 +284,16 @@ td.stats-value { border-radius: 10px; height: 20px; width: 20px; + text-align: center; + color: white; } .task-new { background-color: #CCCCFF; } .task-in-progress { - background: linear-gradient(0deg, blue 0%, blue 40%, white 41%, white 59%, blue 60%, blue 100%);; - animation: inProgressAnimation 2s infinite linear; + background-color: blue; + animation: inProgressAnimation 3s infinite linear; cursor: pointer; } .task-succeeded { @@ -323,7 +325,7 @@ td.stats-value { transform: rotateZ(0deg); } 100% { - transform: rotateZ(180deg); + transform: rotateZ(360deg); } } diff --git a/commands/get_status.go b/commands/get_status.go index 4d53355ea2..ad34b4c8d6 100644 --- a/commands/get_status.go +++ b/commands/get_status.go @@ -25,7 +25,9 @@ type BuildStatus struct { // GetStatus returns current build status. func GetStatus(c *db.Cocoon, inputJSON []byte) (interface{}, error) { var err error - checklists, err := c.QueryLatestChecklists() + + const maxStatusesToReturn = 50 + checklists, err := c.QueryLatestChecklists(maxStatusesToReturn) if err != nil { return nil, err diff --git a/commands/reserve_task.go b/commands/reserve_task.go index e34121eb7f..15b0420639 100644 --- a/commands/reserve_task.go +++ b/commands/reserve_task.go @@ -8,6 +8,7 @@ import ( "cocoon/db" "encoding/json" "fmt" + "sort" "golang.org/x/net/context" @@ -86,7 +87,8 @@ func ReserveTask(cocoon *db.Cocoon, inputJSON []byte) (interface{}, error) { } func findNextTaskToRun(cocoon *db.Cocoon, agent *db.Agent) (*db.TaskEntity, *db.ChecklistEntity, error) { - checklists, err := cocoon.QueryLatestChecklists() + const maxChecklistsToScan = 20 + checklists, err := cocoon.QueryLatestChecklists(maxChecklistsToScan) if err != nil { return nil, nil, err @@ -110,7 +112,7 @@ func findNextTaskToRun(cocoon *db.Cocoon, agent *db.Agent) (*db.TaskEntity, *db. // these tasks for agents. continue } - for _, taskEntity := range stage.Tasks { + for _, taskEntity := range sortByAttemptCount(stage.Tasks) { task := taskEntity.Task if len(task.RequiredCapabilities) == 0 { @@ -175,3 +177,19 @@ func allPrimaryStagesSuccessful(stages []*db.Stage) bool { } return db.Every(len(stages), func(i int) interface{} { return stages[i] }, isSuccessfulPrimaryOrAnySecondary) } + +// Run tasks with fewest prior attempts first. +func sortByAttemptCount(tasks []*db.TaskEntity) []*db.TaskEntity { + sorted := make([]*db.TaskEntity, len(tasks)) + copy(sorted, tasks) + sort.Sort(byAttemptCount(sorted)) + return sorted +} + +type byAttemptCount []*db.TaskEntity + +func (tasks byAttemptCount) Len() int { return len(tasks) } +func (tasks byAttemptCount) Swap(i, j int) { tasks[i], tasks[j] = tasks[j], tasks[i] } +func (tasks byAttemptCount) Less(i, j int) bool { + return tasks[i].Task.Attempts < tasks[j].Task.Attempts +} diff --git a/db/db.go b/db/db.go index b69cec726b..61f981fd00 100644 --- a/db/db.go +++ b/db/db.go @@ -87,8 +87,8 @@ func (c *Cocoon) GetChecklist(key *datastore.Key) (*ChecklistEntity, error) { // QueryLatestChecklists queries the datastore for the latest checklists sorted // by CreateTimestamp in descending order. Returns up to 20 entities. -func (c *Cocoon) QueryLatestChecklists() ([]*ChecklistEntity, error) { - query := datastore.NewQuery("Checklist").Order("-CreateTimestamp").Limit(20) +func (c *Cocoon) QueryLatestChecklists(limit int) ([]*ChecklistEntity, error) { + query := datastore.NewQuery("Checklist").Order("-CreateTimestamp").Limit(limit) var buffer []*ChecklistEntity for iter := query.Run(c.Ctx); ; { var checklist Checklist @@ -193,7 +193,8 @@ func (c *Cocoon) QueryAllPendingTasks() ([]*FullTask, error) { // // See also IsFinal. func (c *Cocoon) QueryPendingTasks(taskName string) ([]*FullTask, error) { - checklists, err := c.QueryLatestChecklists() + const maxChecklistsToScan = 20 + checklists, err := c.QueryLatestChecklists(maxChecklistsToScan) if err != nil { return nil, err