4.2 KiB · text History db86591
.pragma library

// CI status vocabulary, shared by every view. Mirrors the Android
// client's StatusChip.kt so the widget and the phone agree on what a
// colour means.

var ACTIVE = ["queued", "running", "pending", "blocked"]

// `conclusion` when the thing has finished, else its raw `status`.
function effective(status, conclusion) {
    if (status === "completed" || status === "finished") return conclusion || status
    return status || "unknown"
}

function isActive(status) {
    return ACTIVE.indexOf(status) !== -1
}

// success | failure | running | queued | dim
function category(status) {
    switch (status) {
    case "success":
        return "success"
    case "failure":
    case "cancelled":
    case "timed_out":
        return "failure"
    case "running":
        return "running"
    case "queued":
    case "pending":
    case "blocked":
        return "queued"
    default:
        return "dim"
    }
}

function color(status, theme) {
    switch (category(status)) {
    case "success": return theme.positiveTextColor
    case "failure": return theme.negativeTextColor
    case "running": return theme.neutralTextColor
    case "queued": return theme.neutralTextColor
    default: return theme.disabledTextColor
    }
}

function icon(status) {
    switch (status) {
    case "success": return "emblem-success"
    case "failure":
    case "timed_out": return "emblem-error"
    case "cancelled": return "dialog-cancel"
    case "running": return "media-playback-start"
    case "queued":
    case "pending":
    case "blocked": return "chronometer"
    case "skipped": return "go-next-skip"
    default: return "question"
    }
}

// Worst-first ordering for folding several repos into one circle: a
// run in flight is the most interesting thing, then a broken build.
var PRIORITY = { running: 5, queued: 4, failure: 3, success: 2, dim: 1 }

function aggregate(statuses) {
    var best = null
    var bestScore = 0
    for (var i = 0; i < statuses.length; i++) {
        var score = PRIORITY[category(statuses[i])] || 0
        if (score > bestScore) {
            best = statuses[i]
            bestScore = score
        }
    }
    return best || "unknown"
}

// ── formatting ────────────────────────────────────────────────────────

function shortSha(sha) {
    return sha ? sha.substring(0, 7) : ""
}

function shortRef(ref) {
    if (!ref) return ""
    return ref.replace(/^refs\/(heads|tags)\//, "")
}

function relativeTime(iso) {
    if (!iso) return ""
    var then = new Date(iso).getTime()
    if (isNaN(then)) return ""
    var secs = Math.max(0, Math.round((Date.now() - then) / 1000))
    if (secs < 60) return "just now"
    var mins = Math.round(secs / 60)
    if (mins < 60) return mins + "m ago"
    var hours = Math.round(mins / 60)
    if (hours < 24) return hours + "h ago"
    var days = Math.round(hours / 24)
    return days + "d ago"
}

// Elapsed between two timestamps; a missing end means "still going".
function duration(startIso, endIso) {
    if (!startIso) return ""
    var start = new Date(startIso).getTime()
    var end = endIso ? new Date(endIso).getTime() : Date.now()
    if (isNaN(start) || isNaN(end)) return ""
    var secs = Math.max(0, Math.round((end - start) / 1000))
    if (secs < 60) return secs + "s"
    var mins = Math.floor(secs / 60)
    if (mins < 60) return mins + "m " + (secs % 60) + "s"
    return Math.floor(mins / 60) + "h " + (mins % 60) + "m"
}

function runTitle(run) {
    return run.workflow_name || run.workflow_path || "workflow"
}

function runMeta(run) {
    var parts = []
    if (run.head_ref) parts.push(shortRef(run.head_ref))
    if (run.head_sha) parts.push(shortSha(run.head_sha))
    if (run.trigger) parts.push(run.trigger)
    if (run.triggered_by && run.triggered_by.handle) parts.push("by " + run.triggered_by.handle)
    return parts.join(" · ")
}

function jobLabel(job) {
    var label = job.name || job.job_id || ("job " + job.id)
    if (job.matrix && typeof job.matrix === "object") {
        var values = []
        for (var k in job.matrix) values.push(job.matrix[k])
        if (values.length > 0 && label.indexOf("(") === -1) label += " (" + values.join(", ") + ")"
    }
    return label
}