OXYGENHUB
◆ v0.11.0 oxygenhub-vscode / src / flowview.ts
3.4 KiB · last changed Flow view and a live Flow status bar item (0.8.0) 1 month ago source blame history raw
263de209 Andy McCall ↶ 1
// OxygenFlow in the editor, without VS Code: how runs and jobs look (icon,
2
// colour, labels) and which runs just finished. Shared by the Flow view, the
3
// status bar item and the tests.
4
5
import type { FlowRun } from "./hubapi";
6
7
/** A codicon and a theme colour id for a run's or job's status. */
8
export interface Look {
9
  /** Codicon id; `~spin` makes it turn. */
10
  icon: string;
11
  /** A theme colour id, e.g. `testing.iconPassed`. */
12
  color: string;
13
  /** A short word for screen readers and tooltips. */
14
  word: string;
15
}
16
17
export function look(status: string): Look {
18
  switch (status) {
19
    case "passed":
20
      return { icon: "pass-filled", color: "testing.iconPassed", word: "passed" };
21
    case "failed":
22
      return { icon: "error", color: "testing.iconFailed", word: "failed" };
23
    case "error":
24
      return { icon: "warning", color: "testing.iconErrored", word: "could not run" };
25
    case "running":
26
      return { icon: "sync~spin", color: "oxygenhub.flowRunning", word: "running" };
27
    case "queued":
28
      return { icon: "clock", color: "testing.iconQueued", word: "queued" };
29
    case "skipped":
30
      return { icon: "debug-step-over", color: "testing.iconSkipped", word: "skipped" };
31
    default:
32
      return { icon: "circle-large-outline", color: "descriptionForeground", word: status };
33
  }
34
}
35
36
export function live(status: string): boolean {
37
  return status === "queued" || status === "running";
38
}
39
40
/** "3.2 s", "4 min 10 s". */
41
export function duration(ms: number): string {
42
  const s = Math.max(0, ms) / 1000;
43
  if (s < 60) {
44
    return `${s.toFixed(1)} s`;
45
  }
46
  return `${Math.floor(s / 60)} min ${Math.floor(s % 60)} s`;
47
}
48
49
/** "just now", "5 minutes ago", as the hub says it. */
50
export function ago(ms: number, now = Date.now()): string {
51
  const s = Math.max(0, Math.floor((now - ms) / 1000));
52
  const unit = (n: number, w: string) => `${n} ${w}${n === 1 ? "" : "s"} ago`;
53
  if (s < 45) {
54
    return "just now";
55
  }
56
  if (s < 90) {
57
    return "a minute ago";
58
  }
59
  if (s < 2700) {
60
    return unit(Math.ceil(s / 60), "minute");
61
  }
62
  if (s < 5400) {
63
    return "an hour ago";
64
  }
65
  if (s < 79200) {
66
    return unit(Math.ceil(s / 3600), "hour");
67
  }
68
  return unit(Math.ceil(s / 86400), "day");
69
}
70
71
/** What a run ran on: its bookmark, or the change for a run on one change. */
72
export function subject(run: FlowRun): string {
73
  return run.bookmark || `change ${(run.heads[0] ?? "").slice(0, 12)}`;
74
}
75
76
/** "#12 main". */
77
export function runLabel(run: FlowRun): string {
78
  return `#${run.number} ${subject(run)}`;
79
}
80
81
/** "passed in 2 min 3 s · push by andy · 5 minutes ago". */
82
export function runDescription(run: FlowRun, now = Date.now()): string {
83
  const took = run.finished_ms ? ` in ${duration(run.finished_ms - run.created_ms)}` : "";
84
  const queued = run.status === "queued" && run.queue_position ? ` (#${run.queue_position} in line)` : "";
85
  const trigger = run.trigger.startsWith("rerun:") ? `re-run of #${run.trigger.slice(6)}` : run.trigger;
86
  return `${look(run.status).word}${took}${queued} · ${trigger} by ${run.actor} · ${ago(run.created_ms, now)}`;
87
}
88
89
/**
90
 * Runs that were live at the last look and are finished now, so the editor
91
 * can say so once. `before` maps a run's key to its last status.
92
 */
93
export function newlyFinished(before: Map<string, string>, runs: { key: string; run: FlowRun }[]): { key: string; run: FlowRun }[] {
94
  return runs.filter(({ key, run }) => {
95
    const was = before.get(key);
96
    return was !== undefined && live(was) && !live(run.status);
97
  });
98
}