| @@ -14,7 +14,7 @@ import ( |
| 14 | 14 | |
| 15 | 15 | func init() { |
| 16 | 16 | register(Command{Path: []string{"dashboard"}, |
| 17 | | Summary: "one read for the account dashboard: pinned repos, open MRs, assigned issues, recent builds", |
| 17 | Summary: "one read for the account dashboard: review queue, assigned and open work, pins, activity, builds", |
| 18 | 18 | ReadOnly: true, Run: runDashboard}) |
| 19 | 19 | register(Command{Path: []string{"feed"}, |
| 20 | 20 | Summary: "activity on repositories you can reach: feed [--limit <n>] [--cursor <c>]", |
| @@ -53,12 +53,18 @@ func runDashboard(c *Ctx, args []string) int { |
| 53 | 53 | FinishedAt string `json:"finished_at,omitempty"` |
| 54 | 54 | } |
| 55 | 55 | type out struct { |
| 56 | | Pinned []pinnedOut `json:"pinned"` |
| 57 | | MRs []dashboardItem `json:"open_mrs"` |
| 56 | Reviews []dashboardItem `json:"review_queue"` |
| 58 | 57 | Assigned []dashboardItem `json:"assigned_issues"` |
| 58 | MRs []dashboardItem `json:"open_mrs"` |
| 59 | Issues []dashboardItem `json:"open_issues"` |
| 60 | Pinned []pinnedOut `json:"pinned"` |
| 61 | Activity []feedOut `json:"recent_activity"` |
| 59 | 62 | Builds []buildOut `json:"builds"` |
| 60 | 63 | } |
| 61 | | d := out{Pinned: []pinnedOut{}, MRs: []dashboardItem{}, Assigned: []dashboardItem{}, Builds: []buildOut{}} |
| 64 | d := out{ |
| 65 | Reviews: []dashboardItem{}, Assigned: []dashboardItem{}, MRs: []dashboardItem{}, |
| 66 | Issues: []dashboardItem{}, Pinned: []pinnedOut{}, Activity: []feedOut{}, Builds: []buildOut{}, |
| 67 | } |
| 62 | 68 | |
| 63 | 69 | pinned, err := c.Store.PinnedRepos(c.User.ID) |
| 64 | 70 | if err != nil { |
| @@ -84,6 +90,14 @@ func runDashboard(c *Ctx, args []string) int { |
| 84 | 90 | d.MRs = append(d.MRs, dashboardItem{m.RepoPath, m.Number, m.Title, m.Author, m.State, m.UpdatedAt}) |
| 85 | 91 | } |
| 86 | 92 | |
| 93 | reviews, err := c.Store.ReviewQueue(c.User.ID) |
| 94 | if err != nil { |
| 95 | return c.fail(protocol.ExitFailure, "%v", err) |
| 96 | } |
| 97 | for _, m := range reviews { |
| 98 | d.Reviews = append(d.Reviews, dashboardItem{m.RepoPath, m.Number, m.Title, m.Author, m.State, m.UpdatedAt}) |
| 99 | } |
| 100 | |
| 87 | 101 | assigned, err := c.Store.AssignedIssues(c.User.ID) |
| 88 | 102 | if err != nil { |
| 89 | 103 | return c.fail(protocol.ExitFailure, "%v", err) |
| @@ -92,6 +106,20 @@ func runDashboard(c *Ctx, args []string) int { |
| 92 | 106 | d.Assigned = append(d.Assigned, dashboardItem{i.RepoPath, i.Number, i.Title, i.Author, i.State, i.UpdatedAt}) |
| 93 | 107 | } |
| 94 | 108 | |
| 109 | issues, err := c.Store.DashboardIssues(c.User.ID) |
| 110 | if err != nil { |
| 111 | return c.fail(protocol.ExitFailure, "%v", err) |
| 112 | } |
| 113 | for _, i := range issues { |
| 114 | d.Issues = append(d.Issues, dashboardItem{i.RepoPath, i.Number, i.Title, i.Author, i.State, i.UpdatedAt}) |
| 115 | } |
| 116 | |
| 117 | events, err := c.Store.RecentEvents(c.User.ID, 20, 0) |
| 118 | if err != nil { |
| 119 | return c.fail(protocol.ExitFailure, "%v", err) |
| 120 | } |
| 121 | d.Activity = feedOutputs(events) |
| 122 | |
| 95 | 123 | builds, err := c.Store.RecentBuilds(c.User.ID, 20) |
| 96 | 124 | if err != nil { |
| 97 | 125 | return c.fail(protocol.ExitFailure, "%v", err) |
| @@ -101,6 +129,14 @@ func runDashboard(c *Ctx, args []string) int { |
| 101 | 129 | } |
| 102 | 130 | |
| 103 | 131 | return c.emit(d, func(w io.Writer) { |
| 132 | fmt.Fprintln(w, "waiting on your review:") |
| 133 | printDashboardItems(w, d.Reviews, "!") |
| 134 | fmt.Fprintln(w, "assigned to you:") |
| 135 | printDashboardItems(w, d.Assigned, "#") |
| 136 | fmt.Fprintln(w, "open merge requests:") |
| 137 | printDashboardItems(w, d.MRs, "!") |
| 138 | fmt.Fprintln(w, "open issues:") |
| 139 | printDashboardItems(w, d.Issues, "#") |
| 104 | 140 | fmt.Fprintln(w, "pinned:") |
| 105 | 141 | for _, p := range d.Pinned { |
| 106 | 142 | mark := "" |
| @@ -109,13 +145,9 @@ func runDashboard(c *Ctx, args []string) int { |
| 109 | 145 | } |
| 110 | 146 | fmt.Fprintf(w, " %s\t%s\t%s%s\n", p.Path, p.Visibility, p.Description, mark) |
| 111 | 147 | } |
| 112 | | fmt.Fprintln(w, "open merge requests:") |
| 113 | | for _, m := range d.MRs { |
| 114 | | fmt.Fprintf(w, " %s!%d\t%s\t%s\n", m.Repo, m.Number, m.Title, m.Author) |
| 115 | | } |
| 116 | | fmt.Fprintln(w, "assigned issues:") |
| 117 | | for _, i := range d.Assigned { |
| 118 | | fmt.Fprintf(w, " %s#%d\t%s\t%s\n", i.Repo, i.Number, i.Title, i.Author) |
| 148 | fmt.Fprintln(w, "recent activity:") |
| 149 | for _, e := range d.Activity { |
| 150 | fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\n", e.CreatedAt, e.Actor, e.Kind, e.Repo, string(e.Data)) |
| 119 | 151 | } |
| 120 | 152 | fmt.Fprintln(w, "builds:") |
| 121 | 153 | for _, b := range d.Builds { |
| @@ -124,10 +156,37 @@ func runDashboard(c *Ctx, args []string) int { |
| 124 | 156 | }) |
| 125 | 157 | } |
| 126 | 158 | |
| 159 | func printDashboardItems(w io.Writer, items []dashboardItem, marker string) { |
| 160 | for _, item := range items { |
| 161 | fmt.Fprintf(w, " %s%s%d\t%s\t%s\n", item.Repo, marker, item.Number, item.Title, item.Author) |
| 162 | } |
| 163 | } |
| 164 | |
| 127 | 165 | // feedDefaultLimit caps a bare `feed` call; pagination reaches further |
| 128 | 166 | // back. |
| 129 | 167 | const feedDefaultLimit = 50 |
| 130 | 168 | |
| 169 | type feedOut struct { |
| 170 | ID int64 `json:"id"` |
| 171 | Repo string `json:"repo"` |
| 172 | Actor string `json:"actor,omitempty"` |
| 173 | Kind string `json:"kind"` |
| 174 | Data json.RawMessage `json:"data,omitempty"` |
| 175 | CreatedAt string `json:"created_at"` |
| 176 | } |
| 177 | |
| 178 | func feedOutputs(events []store.FeedEvent) []feedOut { |
| 179 | ds := make([]feedOut, 0, len(events)) |
| 180 | for _, e := range events { |
| 181 | d := feedOut{ID: e.ID, Repo: e.RepoPath, Actor: e.Actor, Kind: e.Kind, CreatedAt: e.CreatedAt} |
| 182 | if json.Valid([]byte(e.Data)) { |
| 183 | d.Data = json.RawMessage(e.Data) |
| 184 | } |
| 185 | ds = append(ds, d) |
| 186 | } |
| 187 | return ds |
| 188 | } |
| 189 | |
| 131 | 190 | func runFeed(c *Ctx, args []string) int { |
| 132 | 191 | rest, p, code := parsePageFlags(c, args, "feed", true) |
| 133 | 192 | if code >= 0 { |
| @@ -146,22 +205,7 @@ func runFeed(c *Ctx, args []string) int { |
| 146 | 205 | events, next := trimPage(p, events, "feed", func(e store.FeedEvent) string { |
| 147 | 206 | return strconv.FormatInt(e.ID, 10) |
| 148 | 207 | }) |
| 149 | | type feedOut struct { |
| 150 | | ID int64 `json:"id"` |
| 151 | | Repo string `json:"repo"` |
| 152 | | Actor string `json:"actor,omitempty"` |
| 153 | | Kind string `json:"kind"` |
| 154 | | Data json.RawMessage `json:"data,omitempty"` |
| 155 | | CreatedAt string `json:"created_at"` |
| 156 | | } |
| 157 | | var ds []feedOut |
| 158 | | for _, e := range events { |
| 159 | | d := feedOut{ID: e.ID, Repo: e.RepoPath, Actor: e.Actor, Kind: e.Kind, CreatedAt: e.CreatedAt} |
| 160 | | if json.Valid([]byte(e.Data)) { |
| 161 | | d.Data = json.RawMessage(e.Data) |
| 162 | | } |
| 163 | | ds = append(ds, d) |
| 164 | | } |
| 208 | ds := feedOutputs(events) |
| 165 | 209 | return c.emitPage(p, ds, next, func(w io.Writer) { |
| 166 | 210 | for _, d := range ds { |
| 167 | 211 | fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", d.CreatedAt, d.Actor, d.Kind, d.Repo, string(d.Data)) |