8.0 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

// One run as an accordion: the header toggles the job list, each job
// toggles its steps, and each step toggles its log slice — the same
// run → job → step drill-down the Android client uses. Expansion state
// and fetched details live on `store` (the root item) so they survive
// the list refreshing underneath.
ColumnLayout {
    id: runItem

    required property var store
    required property string fullName
    required property var run
    property bool showRepo: true

    readonly property string runKey: fullName + "#" + run.number
    readonly property bool expanded: store.expandedRuns[runKey] === true
    readonly property var detail: store.runDetails[runKey] || null
    readonly property string status: Status.runStatus(run)
    readonly property bool active: Status.isActive(run.status)
    // The detail's copy is fresher than the list row's once it's loaded.
    readonly property var current: detail ? detail.run : run

    spacing: 0

    // ── header ──────────────────────────────────────────────────────────
    PlasmaComponents3.ItemDelegate {
        Layout.fillWidth: true
        onClicked: runItem.store.toggleRun(runItem.fullName, runItem.run.number)

        contentItem: RowLayout {
            spacing: Kirigami.Units.smallSpacing

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

            StatusCircle {
                status: runItem.status
                Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
                Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
            }

            ColumnLayout {
                Layout.fillWidth: true
                spacing: 0

                PlasmaComponents3.Label {
                    Layout.fillWidth: true
                    elide: Text.ElideRight
                    textFormat: Text.StyledText
                    text: (runItem.showRepo ? "<b>" + runItem.fullName + "</b> · " : "")
                          + Status.runTitle(runItem.run) + " #" + runItem.run.number
                }

                PlasmaComponents3.Label {
                    Layout.fillWidth: true
                    elide: Text.ElideRight
                    font: Kirigami.Theme.smallFont
                    opacity: 0.7
                    text: Status.runMeta(runItem.run)
                }
            }

            ColumnLayout {
                spacing: 0

                PlasmaComponents3.Label {
                    Layout.alignment: Qt.AlignRight
                    font: Kirigami.Theme.smallFont
                    color: Status.color(runItem.status, Kirigami.Theme)
                    text: Status.label(runItem.status)
                }

                PlasmaComponents3.Label {
                    Layout.alignment: Qt.AlignRight
                    font: Kirigami.Theme.smallFont
                    opacity: 0.7
                    // `store.now` ticks so relative times stay fresh.
                    text: (runItem.store.now, [Status.relativeTime(runItem.run.inserted_at),
                           Status.duration(runItem.run.started_at, runItem.run.completed_at)]
                          .filter(function(s) { return s !== "" }).join(" · "))
                }
            }
        }
    }

    // ── body ────────────────────────────────────────────────────────────
    ColumnLayout {
        visible: runItem.expanded
        Layout.fillWidth: true
        Layout.leftMargin: Kirigami.Units.gridUnit
        Layout.bottomMargin: Kirigami.Units.smallSpacing
        spacing: Kirigami.Units.smallSpacing

        PlasmaComponents3.BusyIndicator {
            visible: runItem.expanded && !runItem.detail
            Layout.alignment: Qt.AlignHCenter
            Layout.preferredHeight: Kirigami.Units.iconSizes.medium
        }

        // A fork's run waits for someone with write access to approve
        // it, and that happens on the pull request — say so, link there.
        ColumnLayout {
            visible: !!runItem.current.awaiting_approval
            Layout.fillWidth: true
            spacing: 0

            PlasmaComponents3.Label {
                Layout.fillWidth: true
                color: Kirigami.Theme.neutralTextColor
                text: i18n("Waiting for approval")
            }

            PlasmaComponents3.Label {
                Layout.fillWidth: true
                wrapMode: Text.WordWrap
                font: Kirigami.Theme.smallFont
                opacity: 0.7
                text: Status.approvalNotice(runItem.current)
            }

            PlasmaComponents3.ToolButton {
                visible: !!runItem.current.pull_request_number
                icon.name: "internet-web-browser"
                text: i18n("Open pull request #%1", runItem.current.pull_request_number)
                display: QQC2.AbstractButton.TextBesideIcon
                onClicked: Qt.openUrlExternally(runItem.store.api.pullUrl(runItem.fullName, runItem.current.pull_request_number))
            }
        }

        RowLayout {
            visible: !!runItem.detail
            Layout.fillWidth: true
            spacing: Kirigami.Units.smallSpacing

            PlasmaComponents3.ToolButton {
                visible: !!runItem.detail && runItem.detail.can_manage
                enabled: !runItem.store.busy
                icon.name: "view-refresh"
                text: i18n("Re-run all")
                display: QQC2.AbstractButton.TextBesideIcon
                onClicked: runItem.store.rerunRun(runItem.fullName, runItem.run.number)
            }

            PlasmaComponents3.ToolButton {
                visible: !!runItem.detail && runItem.detail.can_manage && runItem.active
                enabled: !runItem.store.busy
                icon.name: "dialog-cancel"
                text: i18n("Cancel")
                display: QQC2.AbstractButton.TextBesideIcon
                onClicked: runItem.store.cancelRun(runItem.fullName, runItem.run.number)
            }

            Item { Layout.fillWidth: true }

            PlasmaComponents3.ToolButton {
                icon.name: "internet-web-browser"
                text: i18n("Open")
                display: QQC2.AbstractButton.TextBesideIcon
                onClicked: Qt.openUrlExternally(runItem.store.api.webUrl(runItem.fullName, runItem.run.number))
            }
        }

        Repeater {
            model: runItem.detail ? runItem.detail.jobs : []

            delegate: JobItem {
                required property var modelData
                Layout.fillWidth: true
                store: runItem.store
                fullName: runItem.fullName
                runNumber: runItem.run.number
                job: modelData
                canManage: !!runItem.detail && runItem.detail.can_manage
            }
        }

        PlasmaComponents3.Label {
            visible: !!runItem.detail && runItem.detail.jobs.length === 0
            text: i18n("No jobs")
            opacity: 0.7
        }

        Repeater {
            model: runItem.detail ? runItem.detail.artifacts : []

            delegate: PlasmaComponents3.Label {
                required property var modelData
                Layout.fillWidth: true
                elide: Text.ElideRight
                font: Kirigami.Theme.smallFont
                opacity: 0.7
                text: "📦 " + (modelData.name || "artifact")
                      + (modelData.size ? " · " + Math.round(modelData.size / 1024) + " KiB" : "")
            }
        }
    }

    Kirigami.Separator {
        Layout.fillWidth: true
        opacity: 0.5
    }
}