From 7c704a1572a4c033eb643175e09de5c0dabda4fb Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Mon, 2 Mar 2026 23:10:09 +0100 Subject: [PATCH] add metrics to tui --- pkg/tui/views/dashboard/keys.go | 14 +++++++++++ pkg/tui/views/dashboard/model.go | 19 +++++++++++---- pkg/tui/views/dashboard/operations.go | 34 +++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/pkg/tui/views/dashboard/keys.go b/pkg/tui/views/dashboard/keys.go index 34760f5..557a64e 100644 --- a/pkg/tui/views/dashboard/keys.go +++ b/pkg/tui/views/dashboard/keys.go @@ -87,47 +87,61 @@ func (d *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { switch { case key.Matches(msg, d.keyMap.Help): d.helpBar.ToggleHelp() + d.logAction("tui toggled help") case key.Matches(msg, d.keyMap.Quit): + d.logAction("tui quit requested") return tea.Quit case key.Matches(msg, d.keyMap.SwitchWindow): d.ToggleFocusedWindow() + d.logAction("tui switched issue window") case d.IsFocusedOnList() && key.Matches(msg, d.keyMap.SelectIssue): d.FocusDetail() + d.logAction("tui opened issue detail") case d.IsFocusedOnDetail() && key.Matches(msg, d.keyMap.BackToList): d.FocusList() + d.logAction("tui returned to issue list") case d.IsFocusedOnDetail() && key.Matches(msg, d.keyMap.ScrollUp): d.issueDetail.ScrollUp(1) + d.logAction("tui scrolled issue detail up") case d.IsFocusedOnDetail() && key.Matches(msg, d.keyMap.ScrollDown): d.issueDetail.ScrollDown(1) + d.logAction("tui scrolled issue detail down") case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.EditTitle): if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { d.startEditTitle(selected) cmd = d.titleInput.Focus() + d.logAction("tui started editing issue title") } case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.EditDescription): if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { d.startEditDescription(selected) cmd = d.descriptionInput.Focus() + d.logAction("tui started editing issue description") } case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.ChangeStatus): if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { d.startChooseStatus(selected) + d.logAction("tui opened status picker") } case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.ChangePriority): if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { d.startChoosePriority(selected) + d.logAction("tui opened priority picker") } case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.ChangeType): if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { d.startChooseType(selected) + d.logAction("tui opened type picker") } case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.AddIssue): d.startCreateIssue() cmd = d.createTitleInput.Focus() + d.logAction("tui started creating issue") case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.DeleteIssue): fl := d.FocusedIssueList() if selected := fl.SelectedItem(); selected.ID != "" { d.startConfirmDelete(selected.ID, fl.Index()) + d.logAction("tui opened delete confirmation") } } diff --git a/pkg/tui/views/dashboard/model.go b/pkg/tui/views/dashboard/model.go index dde0979..4b98ec0 100644 --- a/pkg/tui/views/dashboard/model.go +++ b/pkg/tui/views/dashboard/model.go @@ -46,11 +46,11 @@ type Model struct { choosingPriority bool // true while choosing a priority priorityIssueID string choosingType bool // true while choosing a type - typeIssueID string - feedbackChan chan models.ValidationFeedback - quitChan chan bool - currentFeedback models.ValidationFeedback - showComplete bool + typeIssueID string + feedbackChan chan models.ValidationFeedback + quitChan chan bool + currentFeedback models.ValidationFeedback + showComplete bool } func NewDashboard(app *app.App, feedbackChan chan models.ValidationFeedback, quitChan chan bool) *Model { @@ -98,6 +98,15 @@ func NewDashboard(app *app.App, feedbackChan chan models.ValidationFeedback, qui return m } +func (m *Model) logAction(action string) { + if m.app != nil { + m.app.LogAction(models.EncodeActionEvent(models.ActionEvent{ + Source: "tui", + Action: action, + })) + } +} + func (m *Model) startEditTitle(selected ListIssue) { m.editingTitle = true m.editingIssueID = selected.ID diff --git a/pkg/tui/views/dashboard/operations.go b/pkg/tui/views/dashboard/operations.go index 8d53ee9..6a5a038 100644 --- a/pkg/tui/views/dashboard/operations.go +++ b/pkg/tui/views/dashboard/operations.go @@ -135,8 +135,10 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.editingIssueID = "" m.titleInput.Blur() if msg.Err != nil { + m.logAction("tui failed to update issue title") return m, nil } + m.logAction("tui updated issue title") return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) case issueDescriptionUpdatedMsg: @@ -144,32 +146,40 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.editingDescIssueID = "" m.descriptionInput.Blur() if msg.Err != nil { + m.logAction("tui failed to update issue description") return m, nil } + m.logAction("tui updated issue description") return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) case issueStatusUpdatedMsg: m.choosingStatus = false m.statusIssueID = "" if msg.Err != nil { + m.logAction("tui failed to update issue status") return m, nil } + m.logAction("tui updated issue status") return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) case issuePriorityUpdatedMsg: m.choosingPriority = false m.priorityIssueID = "" if msg.Err != nil { + m.logAction("tui failed to update issue priority") return m, nil } + m.logAction("tui updated issue priority") return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) case issueTypeUpdatedMsg: m.choosingType = false m.typeIssueID = "" if msg.Err != nil { + m.logAction("tui failed to update issue type") return m, nil } + m.logAction("tui updated issue type") return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) case selectIssueMsg: @@ -182,6 +192,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.createTitleInput.Blur() m.createTitleInput.Reset() if msg.Err != nil || msg.Issue == nil { + m.logAction("tui failed to create issue") return m, nil } issues, err := m.app.Issues.SearchIssues(context.Background(), "", models.IssueFilter{}) @@ -204,13 +215,16 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } m.issueDetail.SetIssue(*selectedIssue) + m.logAction("tui created issue") return m, tea.Sequence(setItemsCmd, closedSetCmd, func() tea.Msg { return selectIssueMsg{IssueID: selectedIssue.ID} }) case issueDeletedMsg: m.confirmingDelete = false m.deleteConfirmID = "" if msg.Err != nil { + m.logAction("tui failed to delete issue") return m, nil } + m.logAction("tui deleted issue") issues, err := m.app.Issues.SearchIssues(context.Background(), "", models.IssueFilter{}) if err != nil { return m, nil @@ -263,12 +277,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.confirmingDelete { switch msg.String() { case "y", "Y": + m.logAction("tui confirmed issue deletion") issueID := m.deleteConfirmID idx := m.deleteConfirmIndex m.confirmingDelete = false m.deleteConfirmID = "" return m, deleteIssueCmd(m.app, issueID, idx) case "n", "N", "esc": + m.logAction("tui canceled issue deletion") m.confirmingDelete = false m.deleteConfirmID = "" return m, nil @@ -278,21 +294,25 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.choosingStatus { switch msg.String() { case "o": + m.logAction("tui selected issue status open") issueID := m.statusIssueID m.choosingStatus = false m.statusIssueID = "" return m, updateIssueStatusCmd(m.app, issueID, string(models.StatusOpen)) case "i": + m.logAction("tui selected issue status in_progress") issueID := m.statusIssueID m.choosingStatus = false m.statusIssueID = "" return m, updateIssueStatusCmd(m.app, issueID, string(models.StatusInProgress)) case "c": + m.logAction("tui selected issue status closed") issueID := m.statusIssueID m.choosingStatus = false m.statusIssueID = "" return m, updateIssueStatusCmd(m.app, issueID, string(models.StatusClosed)) case "esc": + m.logAction("tui canceled status picker") m.choosingStatus = false m.statusIssueID = "" return m, nil @@ -302,12 +322,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.choosingPriority { switch msg.String() { case "0", "1", "2", "3", "4": + m.logAction("tui selected issue priority") issueID := m.priorityIssueID priority := int(msg.String()[0] - '0') m.choosingPriority = false m.priorityIssueID = "" return m, updateIssuePriorityCmd(m.app, issueID, priority) case "esc": + m.logAction("tui canceled priority picker") m.choosingPriority = false m.priorityIssueID = "" return m, nil @@ -319,31 +341,37 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.choosingType { switch msg.String() { case "b": + m.logAction("tui selected issue type bug") issueID := m.typeIssueID m.choosingType = false m.typeIssueID = "" return m, updateIssueTypeCmd(m.app, issueID, models.TypeBug) case "f": + m.logAction("tui selected issue type feature") issueID := m.typeIssueID m.choosingType = false m.typeIssueID = "" return m, updateIssueTypeCmd(m.app, issueID, models.TypeFeature) case "t": + m.logAction("tui selected issue type task") issueID := m.typeIssueID m.choosingType = false m.typeIssueID = "" return m, updateIssueTypeCmd(m.app, issueID, models.TypeTask) case "e": + m.logAction("tui selected issue type epic") issueID := m.typeIssueID m.choosingType = false m.typeIssueID = "" return m, updateIssueTypeCmd(m.app, issueID, models.TypeEpic) case "c": + m.logAction("tui selected issue type chore") issueID := m.typeIssueID m.choosingType = false m.typeIssueID = "" return m, updateIssueTypeCmd(m.app, issueID, models.TypeChore) case "esc": + m.logAction("tui canceled type picker") m.choosingType = false m.typeIssueID = "" return m, nil @@ -356,10 +384,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.String() == "enter" { title := m.createTitleInput.Value() if title != "" { + m.logAction("tui submitted new issue") return m, createIssueCmd(m.app, title) } } if msg.String() == "esc" { + m.logAction("tui canceled issue creation") m.creatingIssue = false m.createTitleInput.Blur() m.createTitleInput.Reset() @@ -374,10 +404,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.String() == "enter" { newTitle := m.titleInput.Value() if newTitle != "" { + m.logAction("tui submitted issue title edit") return m, updateIssueTitleCmd(m.app, m.editingIssueID, newTitle) } } if msg.String() == "esc" { + m.logAction("tui canceled issue title edit") m.editingTitle = false m.editingIssueID = "" m.titleInput.Blur() @@ -390,6 +422,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.editingDescription { if msg.String() == "ctrl+s" { + m.logAction("tui submitted issue description edit") issueID := m.editingDescIssueID newDesc := m.descriptionInput.Value() m.editingDescription = false @@ -398,6 +431,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, updateIssueDescriptionCmd(m.app, issueID, newDesc) } if msg.String() == "esc" { + m.logAction("tui canceled issue description edit") m.editingDescription = false m.editingDescIssueID = "" m.descriptionInput.Blur()