diff --git a/tuiexporter/internal/tui/component/commands.go b/tuiexporter/internal/tui/component/commands.go new file mode 100644 index 0000000..c40a9ff --- /dev/null +++ b/tuiexporter/internal/tui/component/commands.go @@ -0,0 +1,66 @@ +package component + +import ( + "fmt" + "regexp" + "strings" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +var keyMapRegex = regexp.MustCompile(`Rune|\[|\]`) + +type KeyMap struct { + key *tcell.EventKey + description string +} + +type KeyMaps []*KeyMap + +func (m KeyMaps) keyTexts() string { + keytexts := []string{} + for _, v := range m { + keytexts = append(keytexts, fmt.Sprintf(" [yellow]%s[white]: %s", + keyMapRegex.ReplaceAllString(v.key.Name(), ""), + v.description, + )) + } + return strings.Join(keytexts, " | ") +} + +type Focusable interface { + SetFocusFunc(func()) *tview.Box +} + +func newCommandList() *tview.TextView { + return tview.NewTextView(). + SetDynamicColors(true) +} + +func attatchCommandList(commands *tview.TextView, p tview.Primitive) *tview.Flex { + base := tview.NewFlex().SetDirection(tview.FlexRow) + + if commands == nil { + return base + } + + base.AddItem(p, 0, 1, true). + AddItem(commands, 1, 1, false) + + return base +} + +func registerCommandList(commands *tview.TextView, c Focusable, origFocusFn func(), keys KeyMaps) { + if commands == nil { + return + } + + c.SetFocusFunc(func() { + commands.SetText(keys.keyTexts()) + + if origFocusFn != nil { + origFocusFn() + } + }) +} diff --git a/tuiexporter/internal/tui/component/log.go b/tuiexporter/internal/tui/component/log.go index c1dcb79..382da0e 100644 --- a/tuiexporter/internal/tui/component/log.go +++ b/tuiexporter/internal/tui/component/log.go @@ -3,6 +3,7 @@ package component import ( "fmt" + "github.com/gdamore/tcell/v2" "github.com/rivo/tview" "github.com/ymtdzzz/otel-tui/tuiexporter/internal/telemetry" ) @@ -79,7 +80,7 @@ func getCellFromLog(log *telemetry.LogData, column int) *tview.TableCell { return tview.NewTableCell(text) } -func getLogInfoTree(l *telemetry.LogData, tcache *telemetry.TraceCache, drawTimelineFn func(traceID string)) *tview.TreeView { +func getLogInfoTree(commands *tview.TextView, l *telemetry.LogData, tcache *telemetry.TraceCache, drawTimelineFn func(traceID string)) *tview.TreeView { if l == nil { return tview.NewTreeView() } @@ -167,5 +168,20 @@ func getLogInfoTree(l *telemetry.LogData, tcache *telemetry.TraceCache, drawTime node.SetExpanded(!node.IsExpanded()) }) + registerCommandList(commands, tree, nil, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), + description: "Reduce the width", + }, + { + key: tcell.NewEventKey(tcell.KeyRune, 'H', tcell.ModCtrl), + description: "Expand the width", + }, + { + key: tcell.NewEventKey(tcell.KeyEnter, ' ', tcell.ModNone), + description: "Toggle folding the child nodes", + }, + }) + return tree } diff --git a/tuiexporter/internal/tui/component/log_test.go b/tuiexporter/internal/tui/component/log_test.go index 11721d4..369dbba 100644 --- a/tuiexporter/internal/tui/component/log_test.go +++ b/tuiexporter/internal/tui/component/log_test.go @@ -182,7 +182,7 @@ func TestGetLogInfoTree(t *testing.T) { screen.Init() screen.SetSize(sw, sh) - gottree := getLogInfoTree(logs[0], nil, nil) + gottree := getLogInfoTree(nil, logs[0], nil, nil) gottree.SetRect(0, 0, sw, sh) gottree.Draw(screen) screen.Sync() diff --git a/tuiexporter/internal/tui/component/metric.go b/tuiexporter/internal/tui/component/metric.go index 6d4d853..4e090fd 100644 --- a/tuiexporter/internal/tui/component/metric.go +++ b/tuiexporter/internal/tui/component/metric.go @@ -88,7 +88,7 @@ func getCellFromMetrics(metric *telemetry.MetricData, column int) *tview.TableCe return tview.NewTableCell(text) } -func getMetricInfoTree(m *telemetry.MetricData) *tview.TreeView { +func getMetricInfoTree(commands *tview.TextView, m *telemetry.MetricData) *tview.TreeView { if m == nil { return nil } @@ -369,6 +369,21 @@ func getMetricInfoTree(m *telemetry.MetricData) *tview.TreeView { node.SetExpanded(!node.IsExpanded()) }) + registerCommandList(commands, tree, nil, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), + description: "Reduce the width", + }, + { + key: tcell.NewEventKey(tcell.KeyRune, 'H', tcell.ModCtrl), + description: "Expand the width", + }, + { + key: tcell.NewEventKey(tcell.KeyEnter, ' ', tcell.ModNone), + description: "Toggle folding the child nodes", + }, + }) + return tree } @@ -380,7 +395,7 @@ func (a ByTimestamp) Less(i, j int) bool { return a[i].Timestamp().AsTime().Before(a[j].Timestamp().AsTime()) } -func drawMetricChartByRow(store *telemetry.Store, row int) tview.Primitive { +func drawMetricChartByRow(commands *tview.TextView, store *telemetry.Store, row int) tview.Primitive { m := store.GetFilteredMetricByIdx(row) mcache := store.GetMetricCache() sname := "N/A" @@ -542,6 +557,8 @@ func drawMetricChartByRow(store *telemetry.Store, row int) tview.Primitive { chart.AddItem(ch, 0, 7, true).AddItem(legend, 0, 3, false) + registerCommandList(commands, ch, nil, KeyMaps{}) + return chart } diff --git a/tuiexporter/internal/tui/component/page.go b/tuiexporter/internal/tui/component/page.go index 14ce115..a15ee59 100644 --- a/tuiexporter/internal/tui/component/page.go +++ b/tuiexporter/internal/tui/component/page.go @@ -1,9 +1,7 @@ package component import ( - "fmt" "log" - "regexp" "strings" "github.com/gdamore/tcell/v2" @@ -27,15 +25,6 @@ const ( DEFAULT_PROPORTION_LOG_TABLE = 30 ) -var keyMapRegex = regexp.MustCompile(`Rune|\[|\]`) - -type KeyMap struct { - key *tcell.EventKey - description string -} - -type KeyMaps []*KeyMap - type TUIPages struct { pages *tview.Pages traces *tview.Flex @@ -45,6 +34,8 @@ type TUIPages struct { debuglog *tview.Flex current string setFocusFn func(p tview.Primitive) + // This is used when other components trigger to draw the timeline + commandsTimeline *tview.TextView } func NewTUIPages(store *telemetry.Store, setFocusFn func(p tview.Primitive)) *TUIPages { @@ -119,6 +110,7 @@ func (p *TUIPages) registerPages(store *telemetry.Store) { } func (p *TUIPages) createTracePage(store *telemetry.Store) *tview.Flex { + commands := newCommandList() basePage := tview.NewFlex().SetDirection(tview.FlexColumn) tableContainer := tview.NewFlex().SetDirection(tview.FlexRow) @@ -172,6 +164,16 @@ func (p *TUIPages) createTracePage(store *telemetry.Store) *tview.Flex { } return event }) + registerCommandList(commands, table, nil, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyRune, '/', tcell.ModNone), + description: "Search traces", + }, + { + key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), + description: "Clear all data", + }, + }) input := "" inputConfirmed := "" @@ -196,13 +198,23 @@ func (p *TUIPages) createTracePage(store *telemetry.Store) *tview.Flex { } p.setFocusFn(table) }) + registerCommandList(commands, search, nil, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyEsc, ' ', tcell.ModNone), + description: "Cancel", + }, + { + key: tcell.NewEventKey(tcell.KeyEnter, ' ', tcell.ModNone), + description: "Confirm", + }, + }) table.SetSelectionChangedFunc(func(row, _ int) { if row == 0 { return } details.Clear() - details.AddItem(getTraceInfoTree(store.GetFilteredServiceSpansByIdx(row-1)), 0, 1, true) + details.AddItem(getTraceInfoTree(commands, store.GetFilteredServiceSpansByIdx(row-1)), 0, 1, true) log.Printf("selected row(original): %d", row) }) tableContainer. @@ -235,39 +247,8 @@ func (p *TUIPages) createTracePage(store *telemetry.Store) *tview.Flex { return event }) - page := attatchCommandList(basePage, KeyMaps{ - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), - description: "(traces) Clear all data", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, '/', tcell.ModNone), - description: "Search traces", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyEsc, ' ', tcell.ModNone), - description: "(search) Cancel", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyEnter, ' ', tcell.ModNone), - description: "(search) Confirm", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), - description: "(detail) Resize side col", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, 'H', tcell.ModCtrl), - description: "(detail) Resize side col", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyF12, ' ', tcell.ModNone), - description: "(debug) Toggle Log", - }, - }) - page = attatchTab(page, PAGE_TRACES) - return page + return attatchTab(attatchCommandList(commands, basePage), PAGE_TRACES) } func (p *TUIPages) createTimelinePage() *tview.Flex { @@ -281,6 +262,9 @@ func (p *TUIPages) createTimelinePage() *tview.Flex { return event }) + // set TextView to draw the keymaps + p.commandsTimeline = newCommandList() + return page } @@ -300,12 +284,8 @@ func (p *TUIPages) showTimelineByRow(store *telemetry.Store, row int) { func (p *TUIPages) showTimeline(traceID string, tcache *telemetry.TraceCache, lcache *telemetry.LogCache, setFocusFn func(pr tview.Primitive)) { p.timeline.Clear() timeline := tview.NewFlex().SetDirection(tview.FlexRow) - var ( - keymaps KeyMaps - tl tview.Primitive - ) - - tl, keymaps = DrawTimeline( + tl := DrawTimeline( + p.commandsTimeline, traceID, tcache, lcache, @@ -313,21 +293,14 @@ func (p *TUIPages) showTimeline(traceID string, tcache *telemetry.TraceCache, lc ) timeline.AddItem(tl, 0, 1, true) - keymaps = append(keymaps, &KeyMap{ - key: tcell.NewEventKey(tcell.KeyF12, ' ', tcell.ModNone), - description: "Toggle Log", - }) - keymaps = append(keymaps, &KeyMap{ - key: tcell.NewEventKey(tcell.KeyEsc, ' ', tcell.ModNone), - description: "Back to Traces", - }) - timeline = attatchCommandList(timeline, keymaps) + timeline = attatchCommandList(p.commandsTimeline, timeline) p.timeline.AddItem(timeline, 0, 1, true) p.switchToPage(PAGE_TIMELINE) } func (p *TUIPages) createMetricsPage(store *telemetry.Store) *tview.Flex { + commands := newCommandList() basePage := tview.NewFlex().SetDirection(tview.FlexColumn) tableContainer := tview.NewFlex().SetDirection(tview.FlexRow) @@ -385,6 +358,16 @@ func (p *TUIPages) createMetricsPage(store *telemetry.Store) *tview.Flex { } return event }) + registerCommandList(commands, table, nil, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyRune, '/', tcell.ModNone), + description: "Search metrics", + }, + { + key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), + description: "Clear all data", + }, + }) input := "" inputConfirmed := "" @@ -408,6 +391,16 @@ func (p *TUIPages) createMetricsPage(store *telemetry.Store) *tview.Flex { } p.setFocusFn(table) }) + registerCommandList(commands, search, nil, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyEsc, ' ', tcell.ModNone), + description: "Cancel", + }, + { + key: tcell.NewEventKey(tcell.KeyEnter, ' ', tcell.ModNone), + description: "Confirm", + }, + }) table.SetSelectionChangedFunc(func(row, _ int) { if row == 0 { @@ -415,10 +408,10 @@ func (p *TUIPages) createMetricsPage(store *telemetry.Store) *tview.Flex { } selected := store.GetFilteredMetricByIdx(row - 1) details.Clear() - details.AddItem(getMetricInfoTree(selected), 0, 1, true) + details.AddItem(getMetricInfoTree(commands, selected), 0, 1, true) // TODO: async rendering with spinner chart.Clear() - chart.AddItem(drawMetricChartByRow(store, row-1), 0, 1, true) + chart.AddItem(drawMetricChartByRow(commands, store, row-1), 0, 1, true) }) tableContainer. @@ -454,42 +447,12 @@ func (p *TUIPages) createMetricsPage(store *telemetry.Store) *tview.Flex { return event }) - page := attatchCommandList(basePage, KeyMaps{ - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), - description: "(metrics) Clear all data", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, '/', tcell.ModNone), - description: "Search Metrics", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyEsc, ' ', tcell.ModNone), - description: "(search) Cancel", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyEnter, ' ', tcell.ModNone), - description: "(search) Confirm", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), - description: "(detail) Resize side col", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, 'H', tcell.ModCtrl), - description: "(detail) Resize side col", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyF12, ' ', tcell.ModNone), - description: "(debug) Toggle Log", - }, - }) - page = attatchTab(page, PAGE_METRICS) - return page + return attatchTab(attatchCommandList(commands, basePage), PAGE_METRICS) } func (p *TUIPages) createLogPage(store *telemetry.Store) *tview.Flex { + commands := newCommandList() pageContainer := tview.NewFlex().SetDirection(tview.FlexRow) page := tview.NewFlex().SetDirection(tview.FlexColumn) @@ -530,6 +493,7 @@ func (p *TUIPages) createLogPage(store *telemetry.Store) *tview.Flex { body := tview.NewTextView() body.SetBorder(true).SetTitle("Body (b)") + registerCommandList(commands, body, nil, KeyMaps{}) tableContainer.SetTitle("Logs (o)").SetBorder(true) table := tview.NewTable(). @@ -545,6 +509,20 @@ func (p *TUIPages) createLogPage(store *telemetry.Store) *tview.Flex { return event }) + registerCommandList(commands, table, nil, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyRune, '/', tcell.ModNone), + description: "Search logs", + }, + { + key: tcell.NewEventKey(tcell.KeyRune, 'y', tcell.ModNone), + description: "Copy Log to clipboard", + }, + { + key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), + description: "Clear all data", + }, + }) input := "" inputConfirmed := "" @@ -568,6 +546,16 @@ func (p *TUIPages) createLogPage(store *telemetry.Store) *tview.Flex { } p.setFocusFn(table) }) + registerCommandList(commands, search, nil, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyEsc, ' ', tcell.ModNone), + description: "Cancel", + }, + { + key: tcell.NewEventKey(tcell.KeyEnter, ' ', tcell.ModNone), + description: "Confirm", + }, + }) resolved := "" table.SetSelectionChangedFunc(func(row, _ int) { @@ -576,7 +564,7 @@ func (p *TUIPages) createLogPage(store *telemetry.Store) *tview.Flex { } selected := store.GetFilteredLogByIdx(row - 1) details.Clear() - details.AddItem(getLogInfoTree(selected, store.GetTraceCache(), func(traceID string) { + details.AddItem(getLogInfoTree(commands, selected, store.GetTraceCache(), func(traceID string) { p.showTimeline(traceID, store.GetTraceCache(), store.GetLogCache(), func(pr tview.Primitive) { p.setFocusFn(pr) }) @@ -628,43 +616,8 @@ func (p *TUIPages) createLogPage(store *telemetry.Store) *tview.Flex { return event }) pageContainer.AddItem(page, 0, 1, true).AddItem(body, 5, 1, false) - pageContainer = attatchCommandList(pageContainer, KeyMaps{ - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), - description: "(logs) Clear all data", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, 'y', tcell.ModNone), - description: "Copy Log to clipboard", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, '/', tcell.ModNone), - description: "Search Logs", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyEsc, ' ', tcell.ModNone), - description: "(search) Cancel", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyEnter, ' ', tcell.ModNone), - description: "(search) Confirm", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), - description: "(detail) Resize side col", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, 'H', tcell.ModCtrl), - description: "(detail) Resize side col", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyF12, ' ', tcell.ModNone), - description: "(debug) Toggle Log", - }, - }) - pageContainer = attatchTab(pageContainer, PAGE_LOGS) - return pageContainer + return attatchTab(attatchCommandList(commands, pageContainer), PAGE_LOGS) } func (p *TUIPages) createDebugLogPage() *tview.Flex { @@ -703,23 +656,3 @@ func attatchTab(p tview.Primitive, name string) *tview.Flex { return base } - -func attatchCommandList(p tview.Primitive, keys KeyMaps) *tview.Flex { - keytexts := []string{} - for _, v := range keys { - keytexts = append(keytexts, fmt.Sprintf("[yellow]%s[white]: %s", - keyMapRegex.ReplaceAllString(v.key.Name(), ""), - v.description, - )) - } - - command := tview.NewTextView(). - SetDynamicColors(true). - SetText(strings.Join(keytexts, " | ")) - - base := tview.NewFlex().SetDirection(tview.FlexRow) - base.AddItem(p, 0, 1, true). - AddItem(command, 1, 1, false) - - return base -} diff --git a/tuiexporter/internal/tui/component/timeline.go b/tuiexporter/internal/tui/component/timeline.go index 68a0f01..ce1efb8 100644 --- a/tuiexporter/internal/tui/component/timeline.go +++ b/tuiexporter/internal/tui/component/timeline.go @@ -41,13 +41,13 @@ type spanTreeNode struct { children []*spanTreeNode } -func DrawTimeline(traceID string, tcache *telemetry.TraceCache, lcache *telemetry.LogCache, setFocusFn func(p tview.Primitive)) (tview.Primitive, KeyMaps) { +func DrawTimeline(commands *tview.TextView, traceID string, tcache *telemetry.TraceCache, lcache *telemetry.LogCache, setFocusFn func(p tview.Primitive)) tview.Primitive { if traceID == "" || tcache == nil { - return newTextView("No spans found"), KeyMaps{} + return newTextView(commands, "No spans found") } _, ok := tcache.GetSpansByTraceID(traceID) if !ok { - return newTextView("No spans found"), KeyMaps{} + return newTextView(commands, "No spans found") } base := tview.NewFlex().SetDirection(tview.FlexRow) @@ -88,6 +88,28 @@ func DrawTimeline(traceID string, tcache *telemetry.TraceCache, lcache *telemetr AddItem(title, 0, 0, 1, 1, 0, 0, false). AddItem(timeline, 0, 1, 1, 1, 0, 0, false) grid.SetTitle("Trace Timeline (t)").SetBorder(true) + registerCommandList(commands, grid, nil, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyUp, ' ', tcell.ModNone), + description: "Move up", + }, + { + key: tcell.NewEventKey(tcell.KeyDown, ' ', tcell.ModNone), + description: "Move down", + }, + { + key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), + description: "Expand the width", + }, + { + key: tcell.NewEventKey(tcell.KeyRune, 'H', tcell.ModCtrl), + description: "Reduce the width", + }, + { + key: tcell.NewEventKey(tcell.KeyEsc, ' ', tcell.ModNone), + description: "Back to Traces", + }, + }) var ( tvs []*tview.TextView @@ -95,11 +117,11 @@ func DrawTimeline(traceID string, tcache *telemetry.TraceCache, lcache *telemetr ) totalRow := 0 for _, n := range tree { - totalRow = placeSpan(grid, n, totalRow, 0, &tvs, &nodes) + totalRow = placeSpan(commands, grid, n, totalRow, 0, &tvs, &nodes) } // details - details := getSpanInfoTree(nodes[0].span, TIMELINE_TREE_TITLE) + details := getSpanInfoTree(commands, nodes[0].span, TIMELINE_TREE_TITLE) detailspro := DEFAULT_PROPORTION_TIMELINE_DETAILS gridpro := DEFAULT_PROPORTION_TIMELINE_GRID @@ -131,7 +153,7 @@ func DrawTimeline(traceID string, tcache *telemetry.TraceCache, lcache *telemetr // update details oldDetails := traceContainer.GetItem(TIMELINE_DETAILS_IDX) traceContainer.RemoveItem(oldDetails) - details := getSpanInfoTree(nodes[currentRow].span, TIMELINE_TREE_TITLE) + details := getSpanInfoTree(commands, nodes[currentRow].span, TIMELINE_TREE_TITLE) details.SetInputCapture(detailsInputFunc(traceContainer, grid, details, &gridpro, &detailspro)) traceContainer.AddItem(details, 0, detailspro, false) } @@ -140,12 +162,12 @@ func DrawTimeline(traceID string, tcache *telemetry.TraceCache, lcache *telemetr if currentRow > 0 { currentRow-- setFocusFn(tvs[currentRow]) - details = getSpanInfoTree(nodes[currentRow].span, TIMELINE_TREE_TITLE) + details = getSpanInfoTree(commands, nodes[currentRow].span, TIMELINE_TREE_TITLE) // update details oldDetails := traceContainer.GetItem(TIMELINE_DETAILS_IDX) traceContainer.RemoveItem(oldDetails) - details := getSpanInfoTree(nodes[currentRow].span, TIMELINE_TREE_TITLE) + details := getSpanInfoTree(commands, nodes[currentRow].span, TIMELINE_TREE_TITLE) details.SetInputCapture(detailsInputFunc(traceContainer, grid, details, &gridpro, &detailspro)) traceContainer.AddItem(details, 0, detailspro, false) } @@ -172,6 +194,12 @@ func DrawTimeline(traceID string, tcache *telemetry.TraceCache, lcache *telemetr if lds, ok := lcache.GetLogsByTraceID(traceID); ok { logs.SetContent(NewLogDataForTable(&lds)) } + registerCommandList(commands, logs, nil, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyEsc, ' ', tcell.ModNone), + description: "Back to Traces", + }, + }) traceContainer.AddItem(grid, 0, DEFAULT_PROPORTION_TIMELINE_GRID, true). AddItem(details, 0, DEFAULT_PROPORTION_TIMELINE_DETAILS, false) @@ -196,24 +224,7 @@ func DrawTimeline(traceID string, tcache *telemetry.TraceCache, lcache *telemetr return event }) - return base, KeyMaps{ - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyUp, ' ', tcell.ModNone), - description: "Move up", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyDown, ' ', tcell.ModNone), - description: "Move down", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), - description: "(timeline or detail) Resize side col", - }, - &KeyMap{ - key: tcell.NewEventKey(tcell.KeyRune, 'H', tcell.ModCtrl), - description: "(timeline or detail) Resize side col", - }, - } + return base } func narrowInLimit(step, curr, limit int) int { @@ -282,7 +293,7 @@ func roundDownDuration(d time.Duration) time.Duration { return d } -func placeSpan(grid *tview.Grid, node *spanTreeNode, row, depth int, tvs *[]*tview.TextView, nodes *[]*spanTreeNode) int { +func placeSpan(commands *tview.TextView, grid *tview.Grid, node *spanTreeNode, row, depth int, tvs *[]*tview.TextView, nodes *[]*spanTreeNode) int { row++ label := node.label prefix := "" @@ -293,7 +304,7 @@ func placeSpan(grid *tview.Grid, node *spanTreeNode, row, depth int, tvs *[]*tvi } prefix = prefix + " " } - tv := newTextView(prefix + label) + tv := newTextView(commands, prefix+label) *tvs = append(*tvs, tv) *nodes = append(*nodes, node) grid.AddItem(tv, row, 0, 1, 1, 0, 0, false) @@ -304,7 +315,7 @@ func placeSpan(grid *tview.Grid, node *spanTreeNode, row, depth int, tvs *[]*tvi ) }) for _, child := range node.children { - row = placeSpan(grid, child, row, depth+1, tvs, nodes) + row = placeSpan(commands, grid, child, row, depth+1, tvs, nodes) } return row } @@ -369,14 +380,37 @@ func newSpanTree(traceID string, cache *telemetry.TraceCache) (rootNodes []*span return rootNodes, duration } -func newTextView(text string) *tview.TextView { +func newTextView(commands *tview.TextView, text string) *tview.TextView { tv := tview.NewTextView(). SetTextAlign(tview.AlignLeft). SetText(text). SetWordWrap(false) - tv.SetFocusFunc(func() { + // FIXME: The parent component Grid does not trigger FocusFunc so set it on child tvs + // But this is redundant. Is there any better ways? + registerCommandList(commands, tv, func() { tv.SetBackgroundColor(tcell.ColorWhite) tv.SetTextColor(tcell.ColorBlack) + }, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyUp, ' ', tcell.ModNone), + description: "Move up", + }, + { + key: tcell.NewEventKey(tcell.KeyDown, ' ', tcell.ModNone), + description: "Move down", + }, + { + key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), + description: "Expand the width", + }, + { + key: tcell.NewEventKey(tcell.KeyRune, 'H', tcell.ModCtrl), + description: "Reduce the width", + }, + { + key: tcell.NewEventKey(tcell.KeyEsc, ' ', tcell.ModNone), + description: "Back to Traces", + }, }) tv.SetBlurFunc(func() { tv.SetBackgroundColor(tcell.ColorNone) @@ -412,7 +446,7 @@ func createSpan(color tcell.Color, total, start, end time.Duration) (span *tview }) } -func getSpanInfoTree(span *telemetry.SpanData, title string) *tview.TreeView { +func getSpanInfoTree(commands *tview.TextView, span *telemetry.SpanData, title string) *tview.TreeView { traceID := span.Span.TraceID().String() sname, _ := span.ResourceSpan.Resource().Attributes().Get("service.name") root := tview.NewTreeNode(fmt.Sprintf("%s (%s)", sname.AsString(), traceID)) @@ -533,5 +567,24 @@ func getSpanInfoTree(span *telemetry.SpanData, title string) *tview.TreeView { node.SetExpanded(!node.IsExpanded()) }) + registerCommandList(commands, tree, nil, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), + description: "Reduce the width", + }, + { + key: tcell.NewEventKey(tcell.KeyRune, 'H', tcell.ModCtrl), + description: "Expand the width", + }, + { + key: tcell.NewEventKey(tcell.KeyEnter, ' ', tcell.ModNone), + description: "Toggle folding the child nodes", + }, + { + key: tcell.NewEventKey(tcell.KeyEsc, ' ', tcell.ModNone), + description: "Back to Traces", + }, + }) + return tree } diff --git a/tuiexporter/internal/tui/component/trace.go b/tuiexporter/internal/tui/component/trace.go index f4019e9..863618d 100644 --- a/tuiexporter/internal/tui/component/trace.go +++ b/tuiexporter/internal/tui/component/trace.go @@ -82,7 +82,7 @@ func getCellFromSpan(span *telemetry.SpanData, column int) *tview.TableCell { return tview.NewTableCell(text) } -func getTraceInfoTree(spans []*telemetry.SpanData) *tview.TreeView { +func getTraceInfoTree(commands *tview.TextView, spans []*telemetry.SpanData) *tview.TreeView { if len(spans) == 0 { return tview.NewTreeView() } @@ -136,6 +136,21 @@ func getTraceInfoTree(spans []*telemetry.SpanData) *tview.TreeView { node.SetExpanded(!node.IsExpanded()) }) + registerCommandList(commands, tree, nil, KeyMaps{ + { + key: tcell.NewEventKey(tcell.KeyRune, 'L', tcell.ModCtrl), + description: "Reduce the width", + }, + { + key: tcell.NewEventKey(tcell.KeyRune, 'H', tcell.ModCtrl), + description: "Expand the width", + }, + { + key: tcell.NewEventKey(tcell.KeyEnter, ' ', tcell.ModNone), + description: "Toggle folding the child nodes", + }, + }) + return tree } diff --git a/tuiexporter/internal/tui/component/trace_test.go b/tuiexporter/internal/tui/component/trace_test.go index 5a7dfcd..aaba760 100644 --- a/tuiexporter/internal/tui/component/trace_test.go +++ b/tuiexporter/internal/tui/component/trace_test.go @@ -148,7 +148,7 @@ func TestGetTraceInfoTree(t *testing.T) { screen.Init() screen.SetSize(sw, sh) - gottree := getTraceInfoTree(spans) + gottree := getTraceInfoTree(nil, spans) gottree.SetRect(0, 0, sw, sh) gottree.Draw(screen) screen.Sync() @@ -192,5 +192,5 @@ func TestGetTraceInfoTree(t *testing.T) { } func TestGetTraceInfoTreeNoSpans(t *testing.T) { - assert.Nil(t, getTraceInfoTree(nil).GetRoot()) + assert.Nil(t, getTraceInfoTree(nil, nil).GetRoot()) }