9.3 KiB · text History 010513e
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.plasma.components as PlasmaComponents3
import org.kde.kirigami as Kirigami

import "Status.js" as Status

// A job inside an expanded run. Header toggles its steps; each step
// toggles its log slice. Logs are fetched once per job on first open
// (the endpoint returns every step's lines at once) and failed steps
// open themselves — that's the one you came to read.
ColumnLayout {
    id: jobItem

    required property var store
    required property string fullName
    required property int runNumber
    required property var job
    property bool canManage: false

    readonly property bool expanded: store.expandedJobs[job.id] === true
    readonly property var logs: store.jobLogs[job.id] || null
    readonly property string status: Status.jobStatus(job)
    readonly property string reason: Status.jobReason(job)

    spacing: 0

    function logsForStep(stepNumber) {
        if (!logs) return null
        for (var i = 0; i < logs.steps.length; i++) {
            if (logs.steps[i].step.number === stepNumber) return logs.steps[i]
        }
        return null
    }

    PlasmaComponents3.ItemDelegate {
        Layout.fillWidth: true
        onClicked: jobItem.store.toggleJob(jobItem.fullName, jobItem.job)

        contentItem: RowLayout {
            spacing: Kirigami.Units.smallSpacing

            Kirigami.Icon {
                source: jobItem.expanded ? "arrow-down" : "arrow-right"
                Layout.preferredWidth: Kirigami.Units.iconSizes.small
                Layout.preferredHeight: Kirigami.Units.iconSizes.small
            }

            StatusCircle {
                status: jobItem.status
                Layout.preferredWidth: Kirigami.Units.iconSizes.small
                Layout.preferredHeight: Kirigami.Units.iconSizes.small
            }

            ColumnLayout {
                Layout.fillWidth: true
                spacing: 0

                PlasmaComponents3.Label {
                    Layout.fillWidth: true
                    elide: Text.ElideRight
                    text: Status.jobLabel(jobItem.job)
                }

                PlasmaComponents3.Label {
                    Layout.fillWidth: true
                    elide: Text.ElideRight
                    font: Kirigami.Theme.smallFont
                    opacity: 0.7
                    text: (jobItem.store.now, [Status.label(jobItem.status),
                           jobItem.job.attempt > 1 ? "attempt " + jobItem.job.attempt : "",
                           Status.duration(jobItem.job.started_at, jobItem.job.completed_at),
                           (jobItem.job.needs && jobItem.job.needs.length > 0) ? "needs " + jobItem.job.needs.join(", ") : ""]
                          .filter(function(s) { return s !== "" }).join(" · "))
                }

                // Why it's waiting, skipped or cancelled — or, for a
                // matrix placeholder, when it expands.
                PlasmaComponents3.Label {
                    visible: jobItem.reason !== ""
                    Layout.fillWidth: true
                    wrapMode: Text.WordWrap
                    maximumLineCount: 3
                    elide: Text.ElideRight
                    font: Kirigami.Theme.smallFont
                    opacity: 0.7
                    text: jobItem.reason
                }
            }

            PlasmaComponents3.ToolButton {
                // A placeholder isn't a job yet: its legs replace it.
            visible: jobItem.canManage && !!jobItem.job.job_id && !jobItem.job.placeholder
                enabled: !jobItem.store.busy
                icon.name: "view-refresh"
                onClicked: jobItem.store.rerunJob(jobItem.fullName, jobItem.runNumber, jobItem.job.job_id)

                PlasmaComponents3.ToolTip { text: i18n("Re-run this job") }
            }
        }
    }

    ColumnLayout {
        visible: jobItem.expanded
        Layout.fillWidth: true
        Layout.leftMargin: Kirigami.Units.gridUnit
        spacing: 0

        PlasmaComponents3.Label {
            visible: jobItem.job.steps.length === 0
            text: jobItem.job.placeholder ? i18n("Matrix not expanded yet — its jobs appear here once it does")
                                          : i18n("No steps yet")
            opacity: 0.7
            font: Kirigami.Theme.smallFont
        }

        Repeater {
            model: jobItem.expanded ? jobItem.job.steps : []

            delegate: ColumnLayout {
                id: stepItem

                required property var modelData
                readonly property string stepKey: jobItem.job.id + ":" + modelData.number
                readonly property string stepStatus: Status.effective(modelData.status, modelData.conclusion)
                readonly property bool open: jobItem.store.stepOpen(stepKey, stepStatus)
                readonly property var stepLogs: jobItem.logsForStep(modelData.number)

                Layout.fillWidth: true
                spacing: 0

                PlasmaComponents3.ItemDelegate {
                    Layout.fillWidth: true
                    onClicked: jobItem.store.toggleStep(stepItem.stepKey, stepItem.stepStatus)

                    contentItem: RowLayout {
                        spacing: Kirigami.Units.smallSpacing

                        Kirigami.Icon {
                            source: stepItem.open ? "arrow-down" : "arrow-right"
                            Layout.preferredWidth: Kirigami.Units.iconSizes.small
                            Layout.preferredHeight: Kirigami.Units.iconSizes.small
                        }

                        StatusCircle {
                            status: stepItem.stepStatus
                            Layout.preferredWidth: Kirigami.Units.iconSizes.small * 0.75
                            Layout.preferredHeight: Kirigami.Units.iconSizes.small * 0.75
                        }

                        PlasmaComponents3.Label {
                            Layout.fillWidth: true
                            elide: Text.ElideRight
                            font: Kirigami.Theme.smallFont
                            text: stepItem.modelData.name || i18n("step %1", stepItem.modelData.number)
                        }

                        PlasmaComponents3.Label {
                            font: Kirigami.Theme.smallFont
                            opacity: 0.7
                            text: (jobItem.store.now, Status.duration(stepItem.modelData.started_at, stepItem.modelData.completed_at))
                        }
                    }
                }

                PlasmaComponents3.BusyIndicator {
                    visible: stepItem.open && !jobItem.logs
                    Layout.alignment: Qt.AlignHCenter
                    Layout.preferredHeight: Kirigami.Units.iconSizes.small
                }

                // Build output is written for a terminal: no wrapping,
                // scroll sideways instead.
                PlasmaComponents3.ScrollView {
                    id: logScroll
                    visible: stepItem.open && !!jobItem.logs
                    Layout.fillWidth: true
                    Layout.leftMargin: Kirigami.Units.gridUnit
                    Layout.preferredHeight: Math.min(logText.implicitHeight + Kirigami.Units.smallSpacing * 2,
                                                     Kirigami.Units.gridUnit * 16)
                    QQC2.ScrollBar.vertical.policy: QQC2.ScrollBar.AsNeeded

                    background: Rectangle {
                        color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g,
                                       Kirigami.Theme.textColor.b, 0.07)
                        radius: Kirigami.Units.cornerRadius
                    }

                    PlasmaComponents3.TextArea {
                        id: logText
                        readOnly: true
                        color: Kirigami.Theme.textColor
                        selectByMouse: true
                        wrapMode: TextEdit.NoWrap
                        textFormat: TextEdit.PlainText
                        font.family: Kirigami.Theme.fixedWidthFont.family
                        font.pointSize: Kirigami.Theme.smallFont.pointSize
                        background: null
                        text: {
                            var sl = stepItem.stepLogs
                            if (!sl || sl.lines.length === 0) {
                                return jobItem.logs.truncated ? i18n("(past the line limit — open the run in the browser)")
                                                              : i18n("(no output)")
                            }
                            return sl.lines.map(function(l) { return l.body }).join("\n")
                        }
                        // Land on the tail: that's where failures are.
                        onTextChanged: Qt.callLater(function() {
                            var flick = logScroll.contentItem
                            flick.contentY = Math.max(0, flick.contentHeight - flick.height)
                        })
                    }
                }
            }
        }

        PlasmaComponents3.Label {
            visible: !!jobItem.logs && jobItem.logs.truncated
            font: Kirigami.Theme.smallFont
            opacity: 0.7
            text: i18n("Output truncated — open the run in the browser for the full log")
        }
    }
}