2
0
mirror of https://github.com/hibiken/asynq.git synced 2025-10-03 05:12:01 +08:00

(cli): task modal

This commit is contained in:
Ken Hibino
2022-05-23 16:19:43 -07:00
parent b6dac5c0a1
commit 89e785e870
5 changed files with 105 additions and 6 deletions

View File

@@ -5,6 +5,7 @@
package dash
import (
"errors"
"fmt"
"os"
"time"
@@ -90,6 +91,7 @@ func Run(opts Options) {
// channels to send/receive data fetched asynchronously
errorCh = make(chan error)
queueCh = make(chan *asynq.QueueInfo)
taskCh = make(chan *asynq.TaskInfo)
queuesCh = make(chan []*asynq.QueueInfo)
groupsCh = make(chan []*asynq.GroupInfo)
tasksCh = make(chan []*asynq.TaskInfo)
@@ -102,6 +104,7 @@ func Run(opts Options) {
opts,
errorCh,
queueCh,
taskCh,
queuesCh,
groupsCh,
tasksCh,
@@ -156,6 +159,11 @@ func Run(opts Options) {
go fetchTasks(inspector, state.selectedQueue.Queue, state.taskState,
taskPageSize(s), state.pageNum, tasksCh, errorCh)
}
// if the task modal is open, fetch the selected task's info
if state.taskID != "" {
go fetchTaskInfo(inspector, state.selectedQueue.Queue, state.taskID, taskCh, errorCh)
}
case viewTypeRedis:
go fetchRedisInfo(redisInfoCh, errorCh)
}
@@ -189,13 +197,22 @@ func Run(opts Options) {
}
d.draw(&state)
case t := <-taskCh:
state.selectedTask = t
state.err = nil
d.draw(&state)
case redisInfo := <-redisInfoCh:
state.redisInfo = *redisInfo
state.err = nil
d.draw(&state)
case err := <-errorCh:
state.err = err
if errors.Is(err, asynq.ErrTaskNotFound) {
state.selectedTask = nil
} else {
state.err = err
}
d.draw(&state)
}
}

View File

@@ -10,6 +10,8 @@ import (
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"
"github.com/gdamore/tcell/v2"
"github.com/hibiken/asynq"
@@ -445,16 +447,74 @@ func drawTaskModal(d *ScreenDrawer, state *State) {
if state.taskID == "" {
return
}
task := state.selectedTask
if task == nil {
// task no longer found
contents := []*rowContent{
{" === Task Summary ===", baseStyle.Bold(true)},
{"", baseStyle},
{fmt.Sprintf(" Task %q no longer exists", state.taskID), baseStyle},
}
withModal(d, contents)
return
}
contents := []*rowContent{
{" === Task Summary ===", baseStyle.Bold(true)},
{"", baseStyle},
{" ID: xxxxx", baseStyle},
{" Type: xxxxx", baseStyle},
{" State: xxxx", baseStyle},
{fmt.Sprintf(" ID: %s", task.ID), baseStyle},
{fmt.Sprintf(" Type: %s", task.Type), baseStyle},
{fmt.Sprintf(" State: %s", task.State.String()), baseStyle},
{fmt.Sprintf(" Queue: %s", task.Queue), baseStyle},
{fmt.Sprintf(" Retried: %d/%d", task.Retried, task.MaxRetry), baseStyle},
}
if task.LastErr != "" {
contents = append(contents, &rowContent{
fmt.Sprintf(" Last Failure: %s", task.LastErr),
baseStyle,
})
contents = append(contents, &rowContent{
fmt.Sprintf(" Last Failure Time: %v", task.LastFailedAt),
baseStyle,
})
}
if !task.NextProcessAt.IsZero() {
contents = append(contents, &rowContent{
fmt.Sprintf(" Next Process Time: %v", task.NextProcessAt),
baseStyle,
})
}
if !task.CompletedAt.IsZero() {
contents = append(contents, &rowContent{
fmt.Sprintf(" Completion Time: %v", task.CompletedAt),
baseStyle,
})
}
if isPrintable(task.Payload) {
contents = append(contents, &rowContent{
fmt.Sprintf("Payload: %s", string(task.Payload)),
baseStyle,
})
}
withModal(d, contents)
}
// Reports whether the given byte slice is printable (i.e. human readable)
func isPrintable(data []byte) bool {
if !utf8.Valid(data) {
return false
}
isAllSpace := true
for _, r := range string(data) {
if !unicode.IsGraphic(r) {
return false
}
if !unicode.IsSpace(r) {
isAllSpace = false
}
}
return !isAllSpace
}
type rowContent struct {
s string // should not include newline
style tcell.Style

View File

@@ -14,6 +14,7 @@ type fetcher interface {
fetchQueues()
fetchQueueInfo(qname string)
fetchRedisInfo()
fetchTaskInfo(qname, taskID string)
fetchTasks(qname string, taskState asynq.TaskState, pageSize, pageNum int)
fetchAggregatingTasks(qname, group string, pageSize, pageNum int)
fetchGroups(qname string)
@@ -25,6 +26,7 @@ type dataFetcher struct {
errorCh chan<- error
queueCh chan<- *asynq.QueueInfo
taskCh chan<- *asynq.TaskInfo
queuesCh chan<- []*asynq.QueueInfo
groupsCh chan<- []*asynq.GroupInfo
tasksCh chan<- []*asynq.TaskInfo
@@ -169,3 +171,21 @@ func fetchTasks(i *asynq.Inspector, qname string, taskState asynq.TaskState, pag
}
tasksCh <- tasks
}
func (f *dataFetcher) fetchTaskInfo(qname, taskID string) {
var (
i = f.inspector
taskCh = f.taskCh
errorCh = f.errorCh
)
go fetchTaskInfo(i, qname, taskID, taskCh, errorCh)
}
func fetchTaskInfo(i *asynq.Inspector, qname, taskID string, taskCh chan<- *asynq.TaskInfo, errorCh chan<- error) {
info, err := i.GetTaskInfo(qname, taskID)
if err != nil {
errorCh <- err
return
}
taskCh <- info
}

View File

@@ -210,9 +210,10 @@ func (h *keyEventHandler) enterKeyQueueDetails() {
d.draw(state)
} else if !shouldShowGroupTable(state) && state.taskTableRowIdx != 0 {
task := state.tasks[state.taskTableRowIdx-1]
state.taskID = task.ID
state.selectedTask = task
// TODO: go fetch task info
state.taskID = task.ID
f.fetchTaskInfo(state.selectedQueue.Queue, task.ID)
h.resetTicker()
d.draw(state)
}

View File

@@ -183,6 +183,7 @@ type fakeFetcher struct{}
func (f *fakeFetcher) fetchQueues() {}
func (f *fakeFetcher) fetchQueueInfo(qname string) {}
func (f *fakeFetcher) fetchRedisInfo() {}
func (f *fakeFetcher) fetchTaskInfo(qname, taskID string) {}
func (f *fakeFetcher) fetchTasks(qname string, taskState asynq.TaskState, pageSize, pageNum int) {}
func (f *fakeFetcher) fetchAggregatingTasks(qname, group string, pageSize, pageNum int) {}
func (f *fakeFetcher) fetchGroups(qname string) {}