Skip to content

Add PR detail expansion and per-workflow CI status - #7

Merged
xiaocang merged 5 commits into
masterfrom
claude/pr-detail-expansion-zPVEQ
Mar 20, 2026
Merged

Add PR detail expansion and per-workflow CI status#7
xiaocang merged 5 commits into
masterfrom
claude/pr-detail-expansion-zPVEQ

Conversation

@xiaocang

Copy link
Copy Markdown
Collaborator
  • PR cards now expand in-place on tap (like early Twitter) showing:
    • Per-workflow CI breakdown (failed/passed/pending counts)
    • Retry job status with remaining/total retries
    • Action buttons: Open PR, Retry, Retry x3, Cancel
  • CI badge shows compact workflow format (e.g. "2/5wf·3")
  • GraphQL query fetches individual check runs via statusCheckRollup contexts
  • Enrich RetryFlakyJob with workflowAttempts and totalRetries
  • Runner settings now show pending retry count and recent retry results

https://claude.ai/code/session_01PqW1eLdPDdSmtQnCfG1HCn

claude and others added 4 commits March 20, 2026 10:44
- PR cards now expand in-place on tap (like early Twitter) showing:
  - Per-workflow CI breakdown (failed/passed/pending counts)
  - Retry job status with remaining/total retries
  - Action buttons: Open PR, Retry, Retry x3, Cancel
- CI badge shows compact workflow format (e.g. "2/5wf·3")
- GraphQL query fetches individual check runs via statusCheckRollup contexts
- Enrich RetryFlakyJob with workflowAttempts and totalRetries
- Runner settings now show pending retry count and recent retry results

https://claude.ai/code/session_01PqW1eLdPDdSmtQnCfG1HCn
- Add JUnit 5 test infrastructure to app module (build.gradle.kts)
- Extract parseCIContexts to top-level internal function for testability
- Change ciStatusText to internal visibility for testability
- CIWorkflowInfoTest: status/totalCount computed properties
- RetryFlakyJobTest: totalRetries, workflowAttempts, defaults
- ParseCIContextsTest: CheckRun/StatusContext parsing, grouping, sorting
- CiStatusTextTest: compact workflow count formatting for all CI states
- OpenPrsUiStateTest: expand/collapse state, prKey, swipe disable logic
- SettingsRetryStatsTest: pending count, recent results filtering/sorting

https://claude.ai/code/session_01PqW1eLdPDdSmtQnCfG1HCn
…es, consolidate ActionPill into StatusBadge

Extract duplicated AnimatedVisibility enter/exit transitions into file-level
constants, hoist RoundedCornerShape allocations out of composables, and replace
the private ActionPill with a clickable StatusBadge overload to eliminate
near-identical code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@xiaocang
xiaocang requested a review from Copilot March 20, 2026 14:27
@xiaocang

Copy link
Copy Markdown
Collaborator Author

@codex pls review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an in-place expandable PR card UI that surfaces per-context/workflow CI status details and retry-job status, and wires new GraphQL data to support that richer CI display.

Changes:

  • Add expandable PR cards with an expanded detail section and compact CI badge text.
  • Extend GraphQL PR fetching to retrieve statusCheckRollup.contexts and parse them into per-group CI counts/workflows.
  • Add retry-job stats surfaced in Settings and introduce JUnit 5-based unit tests for new parsing/formatting logic.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
android/app/src/main/kotlin/com/ghpr/app/ui/openprs/OpenPrsScreen.kt Adds expand/collapse UI, expanded detail actions, and compact CI badge formatting.
android/app/src/main/kotlin/com/ghpr/app/ui/openprs/OpenPrsViewModel.kt Adds expandedPrKey state and toggle handler.
android/app/src/main/kotlin/com/ghpr/app/ui/components/StatusBadge.kt Adds a clickable StatusBadge overload with enabled/disabled behavior.
android/app/src/main/kotlin/com/ghpr/app/data/OpenPullRequest.kt Extends PR model with check counts, workflow/context info, and running flag; adds CIWorkflowInfo.
android/app/src/main/kotlin/com/ghpr/app/data/GitHubGraphQLClient.kt Expands GraphQL fragment to fetch check contexts and adds parseCIContexts() aggregation logic.
android/app/src/main/kotlin/com/ghpr/app/data/GhprApiClient.kt Extends RetryFlakyJob with workflow attempts/timestamps and exposes totalRetries.
android/app/src/main/kotlin/com/ghpr/app/ui/settings/SettingsViewModel.kt Fetches retry jobs and computes pending + recent retry stats into SettingsUiState.
android/app/src/main/kotlin/com/ghpr/app/ui/settings/SettingsScreen.kt Displays pending retry count and recent retry results in Settings UI.
android/app/src/test/kotlin/com/ghpr/app/ui/settings/SettingsRetryStatsTest.kt Unit tests for settings retry-stats filtering/counting behavior.
android/app/src/test/kotlin/com/ghpr/app/ui/openprs/OpenPrsUiStateTest.kt Unit tests for expansion key behavior and swipe enablement logic.
android/app/src/test/kotlin/com/ghpr/app/ui/openprs/CiStatusTextTest.kt Unit tests for compact CI badge text formatting.
android/app/src/test/kotlin/com/ghpr/app/data/ParseCIContextsTest.kt Unit tests for parsing/grouping CI contexts into workflow/context aggregates.
android/app/src/test/kotlin/com/ghpr/app/data/CIWorkflowInfoTest.kt Unit tests for CIWorkflowInfo derived properties.
android/app/src/test/kotlin/com/ghpr/app/data/RetryFlakyJobTest.kt Unit tests for new RetryFlakyJob fields/defaults.
android/app/build.gradle.kts Enables JUnit Platform and adds JUnit 5 + test dependencies for new unit tests.
.gitignore Ignores Rust target/ and a local Cloudflare Wrangler config file.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +547 to +564
internal fun ciStatusText(pr: OpenPullRequest): String {
val ci = pr.ciState?.uppercase() ?: return "ci"
val workflows = pr.ciWorkflows
if (workflows.isEmpty()) return ci.lowercase()
val totalWf = workflows.size
return when (ci) {
"FAILURE", "ERROR" -> {
val failedWf = workflows.count { it.failureCount > 0 }
val totalFailedTasks = workflows.sumOf { it.failureCount }
"${failedWf}/${totalWf}wf\u00B7${totalFailedTasks}"
}
"PENDING" -> {
val doneWf = workflows.count { it.status == "SUCCESS" || it.status == "FAILURE" }
"${doneWf}/${totalWf}wf"
}
"SUCCESS" -> "${totalWf}wf"
else -> ci.lowercase()
}

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ciStatusText() labels the count as wf, but it uses pr.ciWorkflows.size (which includes non-workflow grouped contexts where CIWorkflowInfo.isWorkflow == false, e.g. StatusContext checks). This can produce misleading output like 3wf even when only 1 entry is an actual GitHub Actions workflow. Consider either counting only isWorkflow entries when using the wf suffix, or changing the suffix/logic to reflect that these are generic CI contexts.

Copilot uses AI. Check for mistakes.
Comment on lines +627 to +634
// Only show failed workflows
if (failedWorkflows.isNotEmpty()) {
Column(verticalArrangement = Arrangement.spacedBy(3.dp)) {
failedWorkflows.forEach { wf ->
WorkflowStatusRow(wf)
}
}
} else if (pr.ciState != null && pr.ciWorkflows.isEmpty()) {

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expanded detail view currently filters to failedWorkflows only (pr.ciWorkflows.filter { it.failureCount > 0 }). This contradicts the PR description’s “per-workflow CI breakdown (failed/passed/pending counts)” and prevents users from seeing pending/success workflows when expanded. Consider rendering all workflows with a status indicator and counts, and optionally visually prioritizing failures.

Copilot uses AI. Check for mistakes.
context
state
}
}

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GraphQL query requests statusCheckRollup.contexts(first: 100) but does not fetch pageInfo or paginate. PRs with >100 check contexts will have truncated workflow/context counts and potentially incorrect ciIsRunning/badge output. Consider adding pagination (e.g., pageInfo { hasNextPage endCursor } and follow-up queries) or a clear UI fallback when truncation occurs.

Suggested change
}
}
pageInfo {
hasNextPage
endCursor
}

Copilot uses AI. Check for mistakes.
Comment on lines +460 to +465
val groupName = workflowName ?: ctx.optString("name", "check")
val isWf = workflowName != null

val counts = workflowMap.getOrPut(groupName) { intArrayOf(0, 0, 0) }
workflowIsWf.putIfAbsent(groupName, isWf)

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

workflowIsWf.putIfAbsent(groupName, isWf) never upgrades an existing entry from false to true. If a non-workflow context and a real workflow happen to share the same groupName (e.g., a StatusContext/check named the same as a workflow), isWorkflow can be permanently incorrect. Consider setting workflowIsWf[groupName] = (workflowIsWf[groupName] == true) || isWf (or equivalent) so true wins.

Copilot uses AI. Check for mistakes.
Comment on lines +150 to +162
val state: StateFlow<SettingsUiState> = combine(
baseState,
_retryJobs,
) { base, jobs ->
val pendingCount = jobs.count { it.status == "active" }
val recentResults = jobs
.filter { it.status in listOf("completed", "exhausted", "cancelled") }
.sortedByDescending { it.updatedAt }
.take(5)
base.copy(
retryPendingCount = pendingCount,
recentRetryResults = recentResults,
)

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_retryJobs is never cleared when GitHub auth transitions to SignedOut, and the state combine derives retryPendingCount/recentRetryResults from _retryJobs regardless of auth state. This can leave retry stats from a previous session visible after sign-out. Consider clearing _retryJobs on sign-out (e.g., in an authState collector) and/or returning 0/empty when gitHubAuthState is not SignedIn.

Copilot uses AI. Check for mistakes.
…sWorkflow, sign-out

- Change misleading "wf" suffix to "ci" in ciStatusText() since counts
  include non-workflow StatusContext checks
- Show all workflows in expanded detail (not just failed), with
  status-appropriate icons (cross/hourglass/checkmark)
- Detect GraphQL pagination truncation (>100 checks) via
  pageInfo.hasNextPage and append "+" to CI counts
- Fix workflowIsWf so true wins over false when same name appears as
  both workflow and non-workflow entry
- Clear _retryJobs on sign-out alongside runnerStatus

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@xiaocang
xiaocang merged commit 68c43ca into master Mar 20, 2026
1 check passed
@xiaocang
xiaocang deleted the claude/pr-detail-expansion-zPVEQ branch March 20, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants