From 16fa64b7a7426e084dbb3897fe2be7362486ad2f Mon Sep 17 00:00:00 2001 From: David Dworken Date: Wed, 26 Oct 2022 22:11:07 -0700 Subject: [PATCH] Add support for the TUI displaying custom columns --- client/lib/lib.go | 64 +++++++++++++++++++++++++++++------------------ client/lib/tui.go | 45 +++++++++++++++++++++++++++------ 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/client/lib/lib.go b/client/lib/lib.go index 7f5e6eb..9bbb7d0 100644 --- a/client/lib/lib.go +++ b/client/lib/lib.go @@ -397,7 +397,7 @@ func AddToDbIfNew(db *gorm.DB, entry data.HistoryEntry) { } } -func getCustomColumnValue(ctx *context.Context, header string, entry *data.HistoryEntry) (string, error) { +func getCustomColumnValue(ctx *context.Context, header string, entry data.HistoryEntry) (string, error) { for _, c := range entry.CustomColumns { if strings.EqualFold(c.Name, header) { return c.Val, nil @@ -412,6 +412,41 @@ func getCustomColumnValue(ctx *context.Context, header string, entry *data.Histo return "", fmt.Errorf("failed to find a column matching the column name %#v (is there a typo?)", header) } +func buildTableRow(ctx *context.Context, columnNames []string, entry data.HistoryEntry) ([]string, error) { + row := make([]string, 0) + for _, header := range columnNames { + switch header { + case "Hostname": + row = append(row, entry.Hostname) + case "CWD": + row = append(row, entry.CurrentWorkingDirectory) + case "Timestamp": + row = append(row, entry.StartTime.Format("Jan 2 2006 15:04:05 MST")) + case "Runtime": + row = append(row, entry.EndTime.Sub(entry.StartTime).Round(time.Millisecond).String()) + case "Exit Code": + row = append(row, fmt.Sprintf("%d", entry.ExitCode)) + case "Command": + row = append(row, entry.Command) + default: + customColumnValue, err := getCustomColumnValue(ctx, header, entry) + if err != nil { + return nil, err + } + row = append(row, customColumnValue) + } + } + return row, nil +} + +func stringArrayToAnyArray(arr []string) []any { + ret := make([]any, 0) + for _, item := range arr { + ret = append(ret, item) + } + return ret +} + func DisplayResults(ctx *context.Context, results []*data.HistoryEntry) error { config := hctx.GetConf(ctx) headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc() @@ -424,30 +459,11 @@ func DisplayResults(ctx *context.Context, results []*data.HistoryEntry) error { tbl.WithHeaderFormatter(headerFmt) for _, result := range results { - row := make([]any, 0) - for _, header := range config.DisplayedColumns { - switch header { - case "Hostname": - row = append(row, result.Hostname) - case "CWD": - row = append(row, result.CurrentWorkingDirectory) - case "Timestamp": - row = append(row, result.StartTime.Format("Jan 2 2006 15:04:05 MST")) - case "Runtime": - row = append(row, result.EndTime.Sub(result.StartTime).Round(time.Millisecond).String()) - case "Exit Code": - row = append(row, fmt.Sprintf("%d", result.ExitCode)) - case "Command": - row = append(row, result.Command) - default: - customColumnValue, err := getCustomColumnValue(ctx, header, result) - if err != nil { - return err - } - row = append(row, customColumnValue) - } + row, err := buildTableRow(ctx, config.DisplayedColumns, *result) + if err != nil { + return err } - tbl.AddRow(row...) + tbl.AddRow(stringArrayToAnyArray(row)) } tbl.Print() diff --git a/client/lib/tui.go b/client/lib/tui.go index 5fe44f7..063049e 100644 --- a/client/lib/tui.go +++ b/client/lib/tui.go @@ -95,7 +95,7 @@ func (m model) Init() tea.Cmd { func runQueryAndUpdateTable(m model) model { if m.runQuery != nil && *m.runQuery != m.lastQuery { - rows, numEntries, err := getRows(m.ctx, *m.runQuery, PADDED_NUM_ENTRIES) + rows, numEntries, err := getRows(m.ctx, hctx.GetConf(m.ctx).DisplayedColumns, *m.runQuery, PADDED_NUM_ENTRIES) if err != nil { m.searchErr = err return m @@ -169,7 +169,18 @@ func (m model) View() string { return fmt.Sprintf("An unrecoverable error occured: %v\n", m.err) } if m.selected { - selectedRow = m.table.SelectedRow()[4] + indexOfCommand := -1 + for i, columnName := range hctx.GetConf(m.ctx).DisplayedColumns { + if columnName == "Command" { + indexOfCommand = i + break + } + } + if indexOfCommand == -1 { + selectedRow = "Error: Table doesn't have a column named `Command`?" + return "" + } + selectedRow = m.table.SelectedRow()[indexOfCommand] return "" } loadingMessage := "" @@ -186,7 +197,7 @@ func (m model) View() string { return fmt.Sprintf("\n%s\n%s%s\nSearch Query: %s\n\n%s\n", loadingMessage, warning, m.banner, m.queryInput.View(), baseStyle.Render(m.table.View())) } -func getRows(ctx *context.Context, query string, numEntries int) ([]table.Row, int, error) { +func getRows(ctx *context.Context, columnNames []string, query string, numEntries int) ([]table.Row, int, error) { db := hctx.GetDb(ctx) data, err := data.Search(db, query, numEntries) if err != nil { @@ -197,7 +208,10 @@ func getRows(ctx *context.Context, query string, numEntries int) ([]table.Row, i if i < len(data) { entry := data[i] entry.Command = strings.ReplaceAll(entry.Command, "\n", " ") // TODO: handle multi-line commands better here - row := table.Row{entry.Hostname, entry.CurrentWorkingDirectory, entry.StartTime.Format("Jan 2 2006 15:04:05 MST"), fmt.Sprintf("%d", entry.ExitCode), entry.Command} + row, err := buildTableRow(ctx, columnNames, *entry) + if err != nil { + return nil, 0, fmt.Errorf("failed to build row for entry=%#v: %v", entry, err) + } rows = append(rows, row) } else { rows = append(rows, table.Row{}) @@ -230,7 +244,7 @@ func makeTableColumns(ctx *context.Context, columnNames []string, rows []table.R // Calculate the maximum column width that is useful for each column if we search for the empty string if bigQueryResults == nil { - bigRows, _, err := getRows(ctx, "", 1000) + bigRows, _, err := getRows(ctx, columnNames, "", 1000) if err != nil { return nil, err } @@ -243,7 +257,7 @@ func makeTableColumns(ctx *context.Context, columnNames []string, rows []table.R if err != nil { return nil, fmt.Errorf("failed to get terminal size: %v", err) } - for totalWidth < terminalWidth { + for totalWidth < (terminalWidth - len(columnNames)) { prevTotalWidth := totalWidth for i := range columnNames { if columnWidths[i] < maximumColumnWidths[i]+5 { @@ -256,6 +270,20 @@ func makeTableColumns(ctx *context.Context, columnNames []string, rows []table.R } } + // And if we are too large from the initial query, let's shrink things to make the table fit. We'll use the heuristic of always shrinking the widest column. + for totalWidth > terminalWidth { + largestColumnIdx := -1 + largestColumnSize := -1 + for i := range columnNames { + if columnWidths[i] > largestColumnSize { + largestColumnIdx = i + largestColumnSize = columnWidths[i] + } + } + columnWidths[largestColumnIdx] -= 2 + totalWidth -= 2 + } + // And finally, create some actual columns! columns := make([]table.Column, 0) for i, name := range columnNames { @@ -272,7 +300,8 @@ func max(a, b int) int { } func makeTable(ctx *context.Context, rows []table.Row) (table.Model, error) { - columns, err := makeTableColumns(ctx, []string{"Hostname", "CWD", "Timestamp", "Exit Code", "Command"}, rows) + config := hctx.GetConf(ctx) + columns, err := makeTableColumns(ctx, config.DisplayedColumns, rows) if err != nil { return table.Model{}, err } @@ -327,7 +356,7 @@ func makeTable(ctx *context.Context, rows []table.Row) (table.Model, error) { func TuiQuery(ctx *context.Context, gitCommit, initialQuery string) error { lipgloss.SetColorProfile(termenv.ANSI) - rows, numEntries, err := getRows(ctx, initialQuery, PADDED_NUM_ENTRIES) + rows, numEntries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, initialQuery, PADDED_NUM_ENTRIES) if err != nil { return err }