Android: warning jobs read as an amber warning, and the run counts them

ebe084c · Gabriel Morell · 2026-10-31 03:54

22 files +54 -1
Message
{commit_body(@commit)}

Files changed

modified android/app/src/main/java/org/neiam/gitgud/app/ui/RunScreen.kt
+14 −0
@@ -328,6 +328,20 @@ private fun RunHeader(d: RunDetailResponse) {
328 328 color = MaterialTheme.colorScheme.onSurfaceVariant,
329 329 modifier = Modifier.padding(top = 2.dp),
330 330 )
331 + // A run its warning jobs passed is `success`; the count says
332 + // there's still something to look at.
333 + warningsLabel(d.jobs)?.let { label ->
334 + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 6.dp)) {
335 + Icon(
336 + statusIcon("warning"),
337 + contentDescription = null,
338 + tint = LocalAppTheme.current.warning,
339 + modifier = Modifier.size(15.dp),
340 + )
341 + Spacer(Modifier.width(4.dp))
342 + Text(label, style = MaterialTheme.typography.labelMedium, color = LocalAppTheme.current.warning)
343 + }
344 + }
331 345 }
332 346 }
333 347 }
modified android/app/src/main/java/org/neiam/gitgud/app/ui/StatusChip.kt
+20 −1
@@ -16,6 +16,7 @@ import androidx.compose.material.icons.automirrored.filled.HelpOutline
16 16 import androidx.compose.material.icons.filled.HourglassEmpty
17 17 import androidx.compose.material.icons.filled.PlayCircle
18 18 import androidx.compose.material.icons.filled.RemoveCircle
19 +import androidx.compose.material.icons.filled.Warning
19 20 import androidx.compose.material3.Icon
20 21 import androidx.compose.material3.MaterialTheme
21 22 import androidx.compose.material3.Surface
@@ -26,6 +27,7 @@ import androidx.compose.ui.Modifier
26 27 import androidx.compose.ui.graphics.Color
27 28 import androidx.compose.ui.graphics.vector.ImageVector
28 29 import androidx.compose.ui.unit.dp
30 +import org.neiam.gitgud.app.data.Job
29 31 import org.neiam.gitgud.app.ui.theme.LocalAppTheme
30 32
31 33 /**
@@ -41,13 +43,16 @@ import org.neiam.gitgud.app.ui.theme.LocalAppTheme
41 43 * (`Run.displayStatus` — the server says `queued`) and `superseded`, a
42 44 * job cancelled because a newer one in its concurrency group replaced
43 45 * it (`Job.displayStatus`). Superseded is grey, not red: the newer run
44 − * decides, as its commit status (`cancelled`) says.
46 + * decides, as its commit status (`cancelled`) says. A `warning` job
47 + * passed but wants a look — it failed under `continue-on-error`, or
48 + * reported `gitgud-conclusion: warning` — so it's amber, not red.
45 49 */
46 50 @Composable
47 51 fun statusColor(status: String?): Color {
48 52 val theme = LocalAppTheme.current
49 53 return when (status) {
50 54 "success" -> theme.success
55 + "warning" -> theme.warning
51 56 "failure", "cancelled", "timed_out" -> theme.danger
52 57 "queued", "running", "pending", "blocked", "waiting", "awaiting_approval" -> theme.pending
53 58 "skipped", "superseded" -> theme.dim
@@ -57,6 +62,7 @@ fun statusColor(status: String?): Color {
57 62
58 63 fun statusIcon(status: String?): ImageVector = when (status) {
59 64 "success" -> Icons.Default.CheckCircle
65 + "warning" -> Icons.Default.Warning
60 66 "failure", "timed_out" -> Icons.Default.Error
61 67 "cancelled", "superseded" -> Icons.Default.Cancel
62 68 "running" -> Icons.Default.PlayCircle
@@ -91,6 +97,19 @@ fun triggerLabel(trigger: String?): String? = when (trigger) {
91 97 else -> trigger.replace('_', ' ')
92 98 }
93 99
100 +/**
101 + * "1 warning" / "3 warnings" for a run whose jobs include `warning`
102 + * ones, null for a run without. The run itself still reads `success`,
103 + * so this is what says there's something to look at, as the run page's
104 + * header and `gg ci show` do.
105 + */
106 +fun warningsLabel(jobs: List<Job>): String? =
107 + when (val n = jobs.count { it.displayStatus == "warning" }) {
108 + 0 -> null
109 + 1 -> "1 warning"
110 + else -> "$n warnings"
111 + }
112 +
94 113 /** `conclusion` when the thing has finished, else its raw `status`. */
95 114 fun effectiveStatus(status: String?, conclusion: String?): String? =
96 115 if (status == "completed" || status == "finished") conclusion ?: status else status
modified android/app/src/main/java/org/neiam/gitgud/app/ui/theme/AppTheme.kt
+4 −0
@@ -56,6 +56,8 @@ data class AppTheme(
56 56 val danger: Color,
57 57 /** CI queued or running. */
58 58 val pending: Color,
59 + /** A job that passed but wants a look: amber, apart from pending's yellow. */
60 + val warning: Color,
59 61 val isLight: Boolean = false,
60 62 ) {
61 63 fun toColorScheme() = if (isLight) {
@@ -132,6 +134,7 @@ data class AppTheme(
132 134 private val SUCCESS = oklchToColor(0.75f, 0.15f, 145f)
133 135 private val DANGER = oklchToColor(0.68f, 0.19f, 25f)
134 136 private val PENDING = oklchToColor(0.80f, 0.14f, 85f)
137 +private val WARNING = oklchToColor(0.76f, 0.16f, 60f)
135 138
136 139 private fun oklchTheme(
137 140 name: String, key: String,
@@ -153,6 +156,7 @@ private fun oklchTheme(
153 156 success = SUCCESS,
154 157 danger = DANGER,
155 158 pending = PENDING,
159 + warning = WARNING,
156 160 isLight = isLight,
157 161 )
158 162
modified android/app/src/test/java/org/neiam/gitgud/app/data/CiModelsTest.kt
+16 −0
@@ -7,6 +7,7 @@ import org.junit.Assert.assertTrue
7 7 import org.junit.Test
8 8 import org.neiam.gitgud.app.ui.statusLabel
9 9 import org.neiam.gitgud.app.ui.triggerLabel
10 +import org.neiam.gitgud.app.ui.warningsLabel
10 11 import org.neiam.gitgud.app.widget.CiWidgetModel
11 12 import org.neiam.gitgud.app.widget.StatusCategory
12 13
@@ -51,6 +52,21 @@ class CiModelsTest {
51 52 assertEquals(StatusCategory.DIM, CiWidgetModel.category(job.displayStatus))
52 53 }
53 54
55 + @Test
56 + fun warningJobsAreCountedAndNotFailures() {
57 + val warned = json.decodeFromString<Job>(
58 + """{"id": 2, "status": "warning", "conclusion": "failure",
59 + "status_reason": "Failed, but continue-on-error allows it"}""",
60 + )
61 + val jobs = listOf(Job(id = 1, status = "success"), warned, warned.copy(id = 3))
62 +
63 + assertEquals("warning", warned.displayStatus)
64 + assertEquals("warning", statusLabel(warned.displayStatus))
65 + assertEquals("2 warnings", warningsLabel(jobs))
66 + assertEquals("1 warning", warningsLabel(jobs.take(2)))
67 + assertEquals(null, warningsLabel(jobs.take(1)))
68 + }
69 +
54 70 @Test
55 71 fun aRunWaitingForApprovalSaysSo() {
56 72 val run = json.decodeFromString<Run>(

Parents: 81bfaa9