From b94717bb7419d23a0069f3b08b4e2b611122e2b4 Mon Sep 17 00:00:00 2001 From: Cyril David Date: Tue, 26 Jan 2021 14:11:46 -0800 Subject: [PATCH] Finalize --- internal/cli/cli_test.go | 2 +- internal/cli/logs.go | 118 ++++++++++++++++++---------------- internal/display/actions.go | 2 +- internal/display/display.go | 45 ++++++++++--- internal/display/logs.go | 22 ++++++- internal/display/try_login.go | 2 +- 6 files changed, 122 insertions(+), 69 deletions(-) diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index cf5c8e8bb..dfc439607 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/olekukonko/tablewriter" + "github.com/jsanda/tablewriter" ) // TODO(cyx): think about whether we should extract this function in the diff --git a/internal/cli/logs.go b/internal/cli/logs.go index 4dcaba830..3870b75d6 100644 --- a/internal/cli/logs.go +++ b/internal/cli/logs.go @@ -2,54 +2,27 @@ package cli import ( "fmt" - "sort" "time" - "github.com/auth0/auth0-cli/internal/ansi" "github.com/spf13/cobra" "gopkg.in/auth0.v5/management" ) -func getLatestLogs(cli *cli, n int) (result []*management.Log, err error) { - var list []*management.Log +func getLatestLogs(cli *cli, n int) ([]*management.Log, error) { page := 0 - perPage := 100 - var count int - if count = n; n > 1000 { + perPage := n + + if perPage > 1000 { // Pagination max out at 1000 entries in total // https://auth0.com/docs/logs/retrieve-log-events-using-mgmt-api#limitations - count = 1000 - } - if perPage > count { - perPage = count + perPage = 1000 } - err = ansi.Spinner("Getting logs", func() error { - for count > len(result) { - var err error - list, err = cli.api.Log.List( - management.Parameter("sort", "date:-1"), - management.Parameter("page", fmt.Sprintf("%d", page)), - management.Parameter("per_page", fmt.Sprintf("%d", perPage)), - ) - if err != nil { - return err - } - - sort.Slice(list, func(i, j int) bool { - return list[i].GetDate().Before(list[j].GetDate()) - }) - result = append(list, result...) - if len(list) < perPage { - // We've got all - break - } - page++ - } - return err - }) - - return + return cli.api.Log.List( + management.Parameter("sort", "date:-1"), + management.Parameter("page", fmt.Sprintf("%d", page)), + management.Parameter("per_page", fmt.Sprintf("%d", perPage)), + ) } func logsCmd(cli *cli) *cobra.Command { @@ -68,29 +41,52 @@ Show the tenant logs. if err != nil { return err } + + // TODO(cyx): This is a hack for now to make the + // streaming work faster. + // + // Create a `set` to detect duplicates clientside. + set := make(map[string]struct{}) + list = dedupLogs(list, set) + if len(list) > 0 { lastLogID = list[len(list)-1].GetLogID() - cli.renderer.LogList(list, noColor) } + + var logsCh chan []*management.Log if follow { - for { - list, err = cli.api.Log.List( - management.Parameter("from", lastLogID), - management.Parameter("take", "100"), - ) - if err != nil { - return err - } - if len(list) > 0 { - cli.renderer.LogList(list, noColor) - lastLogID = list[len(list)-1].GetLogID() - } - if len(list) < 90 { - // Not a lot is happening, sleep on it - time.Sleep(1 * time.Second) + logsCh = make(chan []*management.Log) + + go func() { + // This is pretty important and allows + // us to close / terminate the command. + defer close(logsCh) + + for { + list, err = cli.api.Log.List( + management.Query(fmt.Sprintf("log_id:[%s TO *]", lastLogID)), + management.Parameter("sort", "date:-1"), + management.Parameter("take", "100"), + ) + if err != nil { + return + } + + if len(list) > 0 { + logsCh <- dedupLogs(list, set) + lastLogID = list[len(list)-1].GetLogID() + } + + if len(list) < 90 { + // Not a lot is happening, sleep on it + time.Sleep(1 * time.Second) + } } - } + + }() } + + cli.renderer.LogList(list, logsCh, noColor) return nil }, } @@ -101,3 +97,17 @@ Show the tenant logs. return cmd } + +func dedupLogs(list []*management.Log, set map[string]struct{}) []*management.Log { + res := make([]*management.Log, 0, len(list)) + + for _, l := range list { + if _, ok := set[l.GetID()]; !ok { + // It's not a duplicate, track it, and take it. + set[l.GetID()] = struct{}{} + res = append(res, l) + } + } + + return res +} diff --git a/internal/display/actions.go b/internal/display/actions.go index 95dab9588..d1f761c4f 100644 --- a/internal/display/actions.go +++ b/internal/display/actions.go @@ -62,7 +62,7 @@ func (r *Renderer) ActionList(actions []*management.Action) { func (r *Renderer) ActionTest(payload management.Object) { r.Heading(ansi.Bold(r.Tenant), "Actions test result\n") - r.JSONResult(payload) + r.JSONResult(payload, nil) } func (r *Renderer) ActionCreate(action *management.Action) { diff --git a/internal/display/display.go b/internal/display/display.go index 9461641ef..9f70f150e 100644 --- a/internal/display/display.go +++ b/internal/display/display.go @@ -63,7 +63,7 @@ type View interface { AsTableRow() []string } -func (r *Renderer) JSONResult(data interface{}) { +func (r *Renderer) JSONResult(data interface{}, ch <-chan View) { b, err := json.MarshalIndent(data, "", " ") if err != nil { r.Errorf("couldn't marshal results as JSON: %v", err) @@ -73,24 +73,28 @@ func (r *Renderer) JSONResult(data interface{}) { } func (r *Renderer) Results(data []View) { + r.Stream(data, nil) +} + +func (r *Renderer) Stream(data []View, ch <-chan View) { if len(data) > 0 { switch r.Format { case OutputFormatJSON: - r.JSONResult(data) + r.JSONResult(data, ch) default: rows := make([][]string, len(data)) for i, d := range data { rows[i] = d.AsTableRow() } - writeTable(r.ResultWriter, data[0].AsTableHeader(), rows) + writeTable(r.ResultWriter, data[0].AsTableHeader(), rows, ch) } } } -func writeTable(w io.Writer, header []string, data [][]string) { - tableString := &strings.Builder{} - table := tablewriter.NewWriter(tableString) +func writeTable(w io.Writer, header []string, data [][]string, ch <-chan View) { + // tableString := &strings.Builder{} + table := tablewriter.NewWriter(w) table.SetHeader(header) table.SetAutoWrapText(false) @@ -103,12 +107,33 @@ func writeTable(w io.Writer, header []string, data [][]string) { table.SetHeaderLine(false) table.SetBorder(false) - for _, v := range data { - table.Append(v) + if ch == nil { + for _, v := range data { + table.Append(v) + } + table.Render() + return } - table.Render() - fmt.Fprint(w, tableString.String()) + done := make(chan struct{}) + strCh := make(chan []string) + go func() { + defer close(done) + + for _, v := range data { + strCh <- v + } + + for v := range ch { + strCh <- v.AsTableRow() + } + }() + + go func() { + table.ContinuousRender(strCh) + }() + + <-done } func timeAgo(ts time.Time) string { diff --git a/internal/display/logs.go b/internal/display/logs.go index ef395e7fa..4308d0c4b 100644 --- a/internal/display/logs.go +++ b/internal/display/logs.go @@ -84,11 +84,29 @@ func typeDescFor(l *management.Log, noColor bool) (typ, desc string) { return typ, desc } -func (r *Renderer) LogList(logs []*management.Log, noColor bool) { +func (r *Renderer) LogList(logs []*management.Log, ch <-chan []*management.Log, noColor bool) { + r.Heading(ansi.Bold(r.Tenant), "logs\n") + var res []View for _, l := range logs { res = append(res, &logView{Log: l}) } - r.Results(res) + var viewChan chan View + + if ch != nil { + viewChan = make(chan View) + + go func() { + defer close(viewChan) + + for list := range ch { + for _, l := range list { + viewChan <- &logView{Log: l} + } + } + }() + } + + r.Stream(res, viewChan) } diff --git a/internal/display/try_login.go b/internal/display/try_login.go index 8b83745e6..8850dd196 100644 --- a/internal/display/try_login.go +++ b/internal/display/try_login.go @@ -130,6 +130,6 @@ func (r *Renderer) TryLogin(u *auth.UserInfo, t *auth.TokenResponse, reveal bool } tableHeader := []string{"Field", "Value"} - writeTable(r.ResultWriter, tableHeader, rows) + writeTable(r.ResultWriter, tableHeader, rows, nil) } }