| @@ -244,7 +244,13 @@ func runBuildShow(c *Ctx, args []string) int { |
| 244 | 244 | } |
| 245 | 245 | d := buildToOut(b) |
| 246 | 246 | json.Unmarshal([]byte(b.Steps), &d.Steps) |
| 247 | | return c.emit(d, func(w io.Writer) { |
| 247 | var mr *store.MR |
| 248 | if c.Term.Cols > 0 && !c.JSON { |
| 249 | if m, ok, err := c.Store.OpenMRBySource(repo.ID, d.Ref); err == nil && ok { |
| 250 | mr = &m |
| 251 | } |
| 252 | } |
| 253 | return c.emitView(d, func(w io.Writer) { |
| 248 | 254 | failedStep, failed := "", "" |
| 249 | 255 | if d.FailedStep > 0 && d.FailedStep <= len(d.Steps) { |
| 250 | 256 | step, _, _ := strings.Cut(d.Steps[d.FailedStep-1], "\n") |
| @@ -259,15 +265,6 @@ func runBuildShow(c *Ctx, args []string) int { |
| 259 | 265 | if d.DurationS > 0 { |
| 260 | 266 | duration = (time.Duration(d.DurationS) * time.Second).String() |
| 261 | 267 | } |
| 262 | | // At a terminal: the open merge request the build ran for, and |
| 263 | | // the command that prints its log. |
| 264 | | mr, logCmd := "", "" |
| 265 | | if c.Term.Cols > 0 { |
| 266 | | if m, ok, err := c.Store.OpenMRBySource(repo.ID, d.Ref); err == nil && ok { |
| 267 | | mr = fmt.Sprintf("!%d %s", m.Number, m.Title) |
| 268 | | } |
| 269 | | logCmd = fmt.Sprintf("gitbay build log %s %d", repo.Path(), d.Number) |
| 270 | | } |
| 271 | 268 | v := c.view(w) |
| 272 | 269 | v.title(fmt.Sprintf("#%d", d.Number), d.Job, d.Status) |
| 273 | 270 | v.fields( |
| @@ -278,20 +275,44 @@ func runBuildShow(c *Ctx, args []string) int { |
| 278 | 275 | "duration", duration, |
| 279 | 276 | "failed step", failedStep, |
| 280 | 277 | "failed", failed, |
| 281 | | "mr", mr, |
| 282 | | "log", logCmd, |
| 283 | 278 | "url", c.siteURL(repo.Path(), "builds", strconv.FormatInt(d.Number, 10)), |
| 284 | 279 | ) |
| 285 | | if c.Term.Cols > 0 && len(d.Steps) > 0 { |
| 286 | | v.section("steps") |
| 287 | | tb := c.table(w, "#", "STEP", "STATE") |
| 288 | | for i, step := range d.Steps { |
| 289 | | line, _, _ := strings.Cut(step, "\n") |
| 290 | | tb.row(cNum(int64(i+1)), cFlex(line), cState(stepState(d.Status, d.FailedStep, i+1))) |
| 291 | | } |
| 292 | | tb.flush() |
| 280 | }, func() screen { return buildShowScreen(c, repo, d, mr) }) |
| 281 | } |
| 282 | |
| 283 | // buildShowScreen is build show at a terminal: the build's outcome, what |
| 284 | // it ran on, each step's outcome, and the command for its log. |
| 285 | func buildShowScreen(c *Ctx, repo store.Repo, d BuildOut, mr *store.MR) screen { |
| 286 | n := strconv.FormatInt(d.Number, 10) |
| 287 | path := repo.Path() |
| 288 | var s screen |
| 289 | s.fields = append(s.fields, |
| 290 | field{"Build", []cell{cLink(n, c.siteURL(path, "builds", n)), cText(d.Job)}}, |
| 291 | field{"State", []cell{cGlyph(d.Status), cState(d.Status)}}, |
| 292 | field{"Commit", []cell{cRef(fmt.Sprintf("%.10s", d.SHA)), cText(d.Subject)}}, |
| 293 | field{"Ref", []cell{cText(d.Ref)}}, |
| 294 | ) |
| 295 | if mr != nil { |
| 296 | s.fields = append(s.fields, field{"MR", []cell{cRef(fmt.Sprintf("!%d", mr.Number)), cText(mr.Title)}}) |
| 297 | } |
| 298 | if d.DurationS > 0 { |
| 299 | s.fields = append(s.fields, field{"Duration", []cell{cText(c.Term.dur(d.DurationS))}}) |
| 300 | } |
| 301 | if !c.Term.Links { |
| 302 | s.fields = append(s.fields, field{"URL", []cell{cText(c.siteURL(path, "builds", n))}}) |
| 303 | } |
| 304 | steps := section{title: "Steps", n: len(d.Steps)} |
| 305 | for i, step := range d.Steps { |
| 306 | line, _, _ := strings.Cut(step, "\n") |
| 307 | meta := "" |
| 308 | if i+1 == d.FailedStep { |
| 309 | meta = d.FailedReason |
| 293 | 310 | } |
| 294 | | }) |
| 311 | steps.rows = append(steps.rows, rowOf(cGlyph(stepState(d.Status, d.FailedStep, i+1)), cFlex(line), cMeta(meta))) |
| 312 | } |
| 313 | s.sections = []section{steps} |
| 314 | s.actions = []action{{"Read", []string{"build", "log", path, n}}} |
| 315 | return s |
| 295 | 316 | } |
| 296 | 317 | |
| 297 | 318 | // stepState is what a finished build says about one of its steps: those |