Show task failure list in the web frontend
Show the task failure list in the web frontend's `TaskDetails` component.
This commit is contained in:
parent
7f14dac62f
commit
81f81d0e0a
@ -31,6 +31,7 @@ type PersistenceService interface {
|
|||||||
FetchJob(ctx context.Context, jobID string) (*persistence.Job, error)
|
FetchJob(ctx context.Context, jobID string) (*persistence.Job, error)
|
||||||
// FetchTask fetches the given task and the accompanying job.
|
// FetchTask fetches the given task and the accompanying job.
|
||||||
FetchTask(ctx context.Context, taskID string) (*persistence.Task, error)
|
FetchTask(ctx context.Context, taskID string) (*persistence.Task, error)
|
||||||
|
FetchTaskFailureList(context.Context, *persistence.Task) ([]*persistence.Worker, error)
|
||||||
SaveTask(ctx context.Context, task *persistence.Task) error
|
SaveTask(ctx context.Context, task *persistence.Task) error
|
||||||
SaveTaskActivity(ctx context.Context, t *persistence.Task) error
|
SaveTaskActivity(ctx context.Context, t *persistence.Task) error
|
||||||
// TaskTouchedByWorker marks the task as 'touched' by a worker. This is used for timeout detection.
|
// TaskTouchedByWorker marks the task as 'touched' by a worker. This is used for timeout detection.
|
||||||
|
@ -275,20 +275,13 @@ func taskDBtoAPI(dbTask *persistence.Task) api.Task {
|
|||||||
Status: dbTask.Status,
|
Status: dbTask.Status,
|
||||||
Activity: dbTask.Activity,
|
Activity: dbTask.Activity,
|
||||||
Commands: make([]api.Command, len(dbTask.Commands)),
|
Commands: make([]api.Command, len(dbTask.Commands)),
|
||||||
|
Worker: workerToTaskWorker(dbTask.Worker),
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbTask.Job != nil {
|
if dbTask.Job != nil {
|
||||||
apiTask.JobId = dbTask.Job.UUID
|
apiTask.JobId = dbTask.Job.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbTask.Worker != nil {
|
|
||||||
apiTask.Worker = &api.TaskWorker{
|
|
||||||
Id: dbTask.Worker.UUID,
|
|
||||||
Name: dbTask.Worker.Name,
|
|
||||||
Address: dbTask.Worker.Address,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !dbTask.LastTouchedAt.IsZero() {
|
if !dbTask.LastTouchedAt.IsZero() {
|
||||||
apiTask.LastTouched = &dbTask.LastTouchedAt
|
apiTask.LastTouched = &dbTask.LastTouchedAt
|
||||||
}
|
}
|
||||||
@ -306,3 +299,15 @@ func commandDBtoAPI(dbCommand persistence.Command) api.Command {
|
|||||||
Parameters: dbCommand.Parameters,
|
Parameters: dbCommand.Parameters,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// workerToTaskWorker is nil-safe.
|
||||||
|
func workerToTaskWorker(worker *persistence.Worker) *api.TaskWorker {
|
||||||
|
if worker == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &api.TaskWorker{
|
||||||
|
Id: worker.UUID,
|
||||||
|
Name: worker.Name,
|
||||||
|
Address: worker.Address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
package api_impl
|
package api_impl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -99,8 +100,9 @@ func (f *Flamenco) FetchTask(e echo.Context, taskID string) error {
|
|||||||
return sendAPIError(e, http.StatusBadRequest, "job ID not valid")
|
return sendAPIError(e, http.StatusBadRequest, "job ID not valid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch & convert the task.
|
||||||
task, err := f.persist.FetchTask(ctx, taskID)
|
task, err := f.persist.FetchTask(ctx, taskID)
|
||||||
if err == persistence.ErrTaskNotFound {
|
if errors.Is(err, persistence.ErrTaskNotFound) {
|
||||||
logger.Debug().Msg("non-existent task requested")
|
logger.Debug().Msg("non-existent task requested")
|
||||||
return sendAPIError(e, http.StatusNotFound, "no such task")
|
return sendAPIError(e, http.StatusNotFound, "no such task")
|
||||||
}
|
}
|
||||||
@ -108,8 +110,20 @@ func (f *Flamenco) FetchTask(e echo.Context, taskID string) error {
|
|||||||
logger.Warn().Err(err).Msg("error fetching task")
|
logger.Warn().Err(err).Msg("error fetching task")
|
||||||
return sendAPIError(e, http.StatusInternalServerError, "error fetching task")
|
return sendAPIError(e, http.StatusInternalServerError, "error fetching task")
|
||||||
}
|
}
|
||||||
|
|
||||||
apiTask := taskDBtoAPI(task)
|
apiTask := taskDBtoAPI(task)
|
||||||
|
|
||||||
|
// Fetch & convert the failure list.
|
||||||
|
failedWorkers, err := f.persist.FetchTaskFailureList(ctx, task)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn().Err(err).Msg("error fetching task failure list")
|
||||||
|
return sendAPIError(e, http.StatusInternalServerError, "error fetching task failure list")
|
||||||
|
}
|
||||||
|
failedTaskWorkers := make([]api.TaskWorker, len(failedWorkers))
|
||||||
|
for idx, worker := range failedWorkers {
|
||||||
|
failedTaskWorkers[idx] = *workerToTaskWorker(worker)
|
||||||
|
}
|
||||||
|
apiTask.FailedByWorkers = &failedTaskWorkers
|
||||||
|
|
||||||
return e.JSON(http.StatusOK, apiTask)
|
return e.JSON(http.StatusOK, apiTask)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ func TestFetchTask(t *testing.T) {
|
|||||||
workerUUID := "b5725bb3-d540-4070-a2b6-7b4b26925f94"
|
workerUUID := "b5725bb3-d540-4070-a2b6-7b4b26925f94"
|
||||||
jobUUID := "8b179118-0189-478a-b463-73798409898c"
|
jobUUID := "8b179118-0189-478a-b463-73798409898c"
|
||||||
|
|
||||||
|
taskWorker := persistence.Worker{UUID: workerUUID, Name: "Radnik", Address: "Slapić"}
|
||||||
|
|
||||||
dbTask := persistence.Task{
|
dbTask := persistence.Task{
|
||||||
Model: persistence.Model{
|
Model: persistence.Model{
|
||||||
ID: 327,
|
ID: 327,
|
||||||
@ -36,7 +38,7 @@ func TestFetchTask(t *testing.T) {
|
|||||||
Priority: 47,
|
Priority: 47,
|
||||||
Status: api.TaskStatusQueued,
|
Status: api.TaskStatusQueued,
|
||||||
WorkerID: new(uint),
|
WorkerID: new(uint),
|
||||||
Worker: &persistence.Worker{UUID: workerUUID, Name: "Radnik", Address: "Slapić"},
|
Worker: &taskWorker,
|
||||||
Dependencies: []*persistence.Task{},
|
Dependencies: []*persistence.Task{},
|
||||||
Activity: "used in unit test",
|
Activity: "used in unit test",
|
||||||
|
|
||||||
@ -68,11 +70,18 @@ func TestFetchTask(t *testing.T) {
|
|||||||
"src": "/render/_flamenco/tests/renders/2022-04-29 Weekly/2022-04-29_140531__intermediate-2022-04-29_140531",
|
"src": "/render/_flamenco/tests/renders/2022-04-29 Weekly/2022-04-29_140531__intermediate-2022-04-29_140531",
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
FailedByWorkers: ptr([]api.TaskWorker{
|
||||||
|
{Id: workerUUID, Name: "Radnik", Address: "Slapić"},
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
mf.persistence.EXPECT().FetchTask(gomock.Any(), taskUUID).Return(&dbTask, nil)
|
|
||||||
|
|
||||||
echoCtx := mf.prepareMockedRequest(nil)
|
echoCtx := mf.prepareMockedRequest(nil)
|
||||||
|
ctx := echoCtx.Request().Context()
|
||||||
|
mf.persistence.EXPECT().FetchTask(ctx, taskUUID).Return(&dbTask, nil)
|
||||||
|
mf.persistence.EXPECT().FetchTaskFailureList(ctx, &dbTask).
|
||||||
|
Return([]*persistence.Worker{&taskWorker}, nil)
|
||||||
|
|
||||||
err := mf.flamenco.FetchTask(echoCtx, taskUUID)
|
err := mf.flamenco.FetchTask(echoCtx, taskUUID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -127,6 +127,21 @@ func (mr *MockPersistenceServiceMockRecorder) FetchTask(arg0, arg1 interface{})
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchTask", reflect.TypeOf((*MockPersistenceService)(nil).FetchTask), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchTask", reflect.TypeOf((*MockPersistenceService)(nil).FetchTask), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FetchTaskFailureList mocks base method.
|
||||||
|
func (m *MockPersistenceService) FetchTaskFailureList(arg0 context.Context, arg1 *persistence.Task) ([]*persistence.Worker, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FetchTaskFailureList", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].([]*persistence.Worker)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchTaskFailureList indicates an expected call of FetchTaskFailureList.
|
||||||
|
func (mr *MockPersistenceServiceMockRecorder) FetchTaskFailureList(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchTaskFailureList", reflect.TypeOf((*MockPersistenceService)(nil).FetchTaskFailureList), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// FetchWorker mocks base method.
|
// FetchWorker mocks base method.
|
||||||
func (m *MockPersistenceService) FetchWorker(arg0 context.Context, arg1 string) (*persistence.Worker, error) {
|
func (m *MockPersistenceService) FetchWorker(arg0 context.Context, arg1 string) (*persistence.Worker, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -482,3 +482,15 @@ func (db *DB) ClearFailureListOfJob(ctx context.Context, j *Job) error {
|
|||||||
Delete(&TaskFailure{})
|
Delete(&TaskFailure{})
|
||||||
return tx.Error
|
return tx.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) FetchTaskFailureList(ctx context.Context, t *Task) ([]*Worker, error) {
|
||||||
|
var workers []*Worker
|
||||||
|
|
||||||
|
tx := db.gormDB.WithContext(ctx).
|
||||||
|
Model(&Worker{}).
|
||||||
|
Joins("inner join task_failures TF on TF.worker_id = workers.id").
|
||||||
|
Where("TF.task_id = ?", t.ID).
|
||||||
|
Scan(&workers)
|
||||||
|
|
||||||
|
return workers, tx.Error
|
||||||
|
}
|
||||||
|
@ -379,6 +379,47 @@ func TestClearFailureListOfJob(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFetchTaskFailureList(t *testing.T) {
|
||||||
|
ctx, close, db, _, authoredJob1 := jobTasksTestFixtures(t)
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
// Test with non-existing task.
|
||||||
|
fakeTask := Task{Model: Model{ID: 327}}
|
||||||
|
failures, err := db.FetchTaskFailureList(ctx, &fakeTask)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, failures)
|
||||||
|
|
||||||
|
task1_1, _ := db.FetchTask(ctx, authoredJob1.Tasks[1].UUID)
|
||||||
|
task1_2, _ := db.FetchTask(ctx, authoredJob1.Tasks[2].UUID)
|
||||||
|
|
||||||
|
// Test without failures.
|
||||||
|
failures, err = db.FetchTaskFailureList(ctx, task1_1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, failures)
|
||||||
|
|
||||||
|
worker1 := createWorker(ctx, t, db)
|
||||||
|
worker2 := createWorkerFrom(ctx, t, db, *worker1)
|
||||||
|
|
||||||
|
// Store some failures for different tasks and jobs
|
||||||
|
_, _ = db.AddWorkerToTaskFailedList(ctx, task1_1, worker1)
|
||||||
|
_, _ = db.AddWorkerToTaskFailedList(ctx, task1_1, worker2)
|
||||||
|
_, _ = db.AddWorkerToTaskFailedList(ctx, task1_2, worker1)
|
||||||
|
|
||||||
|
// Fetch one task's failure list.
|
||||||
|
failures, err = db.FetchTaskFailureList(ctx, task1_1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
if assert.Len(t, failures, 2) {
|
||||||
|
assert.Equal(t, worker1.UUID, failures[0].UUID)
|
||||||
|
assert.Equal(t, worker1.Name, failures[0].Name)
|
||||||
|
assert.Equal(t, worker1.Address, failures[0].Address)
|
||||||
|
|
||||||
|
assert.Equal(t, worker2.UUID, failures[1].UUID)
|
||||||
|
assert.Equal(t, worker2.Name, failures[1].Name)
|
||||||
|
assert.Equal(t, worker2.Address, failures[1].Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createTestAuthoredJobWithTasks() job_compilers.AuthoredJob {
|
func createTestAuthoredJobWithTasks() job_compilers.AuthoredJob {
|
||||||
task1 := job_compilers.AuthoredTask{
|
task1 := job_compilers.AuthoredTask{
|
||||||
Name: "render-1-3",
|
Name: "render-1-3",
|
||||||
|
@ -29,6 +29,13 @@
|
|||||||
|
|
||||||
<dt class="field-last-touched">Last Touched by Worker</dt>
|
<dt class="field-last-touched">Last Touched by Worker</dt>
|
||||||
<dd>{{ datetime.relativeTime(taskData.last_touched) }}</dd>
|
<dd>{{ datetime.relativeTime(taskData.last_touched) }}</dd>
|
||||||
|
|
||||||
|
<template v-if="taskData.failed_by_workers.length > 0">
|
||||||
|
<dt class="field-failed-by-workers">Failed by Workers</dt>
|
||||||
|
<dd v-for="worker in taskData.failed_by_workers">
|
||||||
|
<router-link :to="{ name: 'workers', params: { workerID: worker.id } }">{{ worker.name }} ({{ worker.address }})</router-link>
|
||||||
|
</dd>
|
||||||
|
</template>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<h3 class="sub-title">Commands</h3>
|
<h3 class="sub-title">Commands</h3>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user