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

View File

@@ -10,6 +10,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
"unicode/utf8"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
@@ -445,16 +447,74 @@ func drawTaskModal(d *ScreenDrawer, state *State) {
if state.taskID == "" { if state.taskID == "" {
return 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{ contents := []*rowContent{
{" === Task Summary ===", baseStyle.Bold(true)}, {" === Task Summary ===", baseStyle.Bold(true)},
{"", baseStyle}, {"", baseStyle},
{" ID: xxxxx", baseStyle}, {fmt.Sprintf(" ID: %s", task.ID), baseStyle},
{" Type: xxxxx", baseStyle}, {fmt.Sprintf(" Type: %s", task.Type), baseStyle},
{" State: xxxx", 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) 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 { type rowContent struct {
s string // should not include newline s string // should not include newline
style tcell.Style style tcell.Style

View File

@@ -14,6 +14,7 @@ type fetcher interface {
fetchQueues() fetchQueues()
fetchQueueInfo(qname string) fetchQueueInfo(qname string)
fetchRedisInfo() fetchRedisInfo()
fetchTaskInfo(qname, taskID string)
fetchTasks(qname string, taskState asynq.TaskState, pageSize, pageNum int) fetchTasks(qname string, taskState asynq.TaskState, pageSize, pageNum int)
fetchAggregatingTasks(qname, group string, pageSize, pageNum int) fetchAggregatingTasks(qname, group string, pageSize, pageNum int)
fetchGroups(qname string) fetchGroups(qname string)
@@ -25,6 +26,7 @@ type dataFetcher struct {
errorCh chan<- error errorCh chan<- error
queueCh chan<- *asynq.QueueInfo queueCh chan<- *asynq.QueueInfo
taskCh chan<- *asynq.TaskInfo
queuesCh chan<- []*asynq.QueueInfo queuesCh chan<- []*asynq.QueueInfo
groupsCh chan<- []*asynq.GroupInfo groupsCh chan<- []*asynq.GroupInfo
tasksCh chan<- []*asynq.TaskInfo tasksCh chan<- []*asynq.TaskInfo
@@ -169,3 +171,21 @@ func fetchTasks(i *asynq.Inspector, qname string, taskState asynq.TaskState, pag
} }
tasksCh <- tasks 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) d.draw(state)
} else if !shouldShowGroupTable(state) && state.taskTableRowIdx != 0 { } else if !shouldShowGroupTable(state) && state.taskTableRowIdx != 0 {
task := state.tasks[state.taskTableRowIdx-1] task := state.tasks[state.taskTableRowIdx-1]
state.taskID = task.ID
state.selectedTask = task state.selectedTask = task
// TODO: go fetch task info state.taskID = task.ID
f.fetchTaskInfo(state.selectedQueue.Queue, task.ID)
h.resetTicker()
d.draw(state) d.draw(state)
} }

View File

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