-
Notifications
You must be signed in to change notification settings - Fork 0
feat: PopoverMainView redesign (#296) (design-branch-2) #371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
086162e
b05078b
dce66a3
119cae7
93f2695
4556f24
70189fe
87fff9b
114cfa8
4a4a0c5
f998973
e8864f7
b29ac09
668224d
51aadb1
7368e8f
043d484
6c41d6b
0d20819
15021c0
07d6414
9e731bc
75550c9
e00ce1c
888288d
d70d046
43a214c
2da30b8
c8e8118
500bbd3
6a3fe6d
28d973c
0085057
1a143ce
3e1513c
751a4b7
e274658
0e387db
2d98df2
0ee6d84
779ade0
e592344
8a39ad9
8bcadaa
dd3718a
77a7d8e
7162557
4555aa4
8775c11
c80f4fc
64b0fdd
a052859
2668474
c2e3935
1e7c776
dc4a801
8c3619f
ddeb34f
4770a9f
6a6c330
900b8b3
1c79c90
ef19719
e8e3da8
4c5d8a3
6793202
b5f1150
fb2ee8e
dd95aa6
fbbc3e9
07f65bc
4067591
5222fc9
7a33f86
d34de9b
b01f70a
94a5a91
eb84978
1d56db5
f41d3ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| .build | ||
| dist |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,5 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Foundation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // swiftlint:disable opening_brace identifier_name missing_docs orphaned_doc_comment | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // swiftlint:disable opening_brace identifier_name orphaned_doc_comment missing_docs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // MARK: - GroupStatus | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -20,10 +20,15 @@ enum GroupStatus { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Holds only the data needed for display and job fetching — deliberately | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// minimal so the full job list lives on the parent ActionGroup instead. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| struct WorkflowRunRef: Identifiable { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Unique GitHub run identifier. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let id: Int | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let name: String // workflow file name, e.g. "SonarQube", "vitest" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Workflow file name, e.g. "SonarQube", "vitest". | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let name: String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Current run status: `queued`, `in_progress`, or `completed`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let status: String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Final outcome when status is `completed`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let conclusion: String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// URL to the run's page on github.com. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let htmlUrl: String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -36,7 +41,7 @@ struct WorkflowRunRef: Identifiable { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Hierarchy: ActionGroup → jobs (flat across all sibling runs) → JobStep → log. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// `ActionDetailView` drills into the flat job list; `JobDetailView`/`StepLogView` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// are reused unchanged below that. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| struct ActionGroup: Identifiable { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| struct ActionGroup: Identifiable, Equatable { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let headSha: String // head_sha — kept as the underlying group identity | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let label: String // "#1270" if PR, else "d6281b" (sha[:7]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let title: String // commit/PR message first line (≤40 chars) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -58,6 +63,7 @@ struct ActionGroup: Identifiable { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Timestamps derived from job data, not run-level API fields. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Mirrors ci-dash.py's `first_job_started_at` / `last_job_completed_at`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var firstJobStartedAt: Date? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Last job completion time across all runs in this group. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var lastJobCompletedAt: Date? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Fallback creation time from the representative run. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -116,77 +122,57 @@ struct ActionGroup: Identifiable { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Name of the first in-progress job, or first queued, or "—". | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var currentJobName: String { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let j = jobs.first(where: { $0.status == "in_progress" }) { return j.name } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let j = jobs.first(where: { $0.status == "queued" }) { return j.name } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let job = jobs.first(where: { $0.status == "in_progress" }) { return job.name } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let job = jobs.first(where: { $0.status == "queued" }) { return job.name } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "—" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// How long ago the group started, as a short human string, e.g. "3m ago", "1h ago". | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Uses `firstJobStartedAt` when available, falls back to `createdAt`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Returns "—" if neither timestamp is available. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Delegates to `RelativeTimeFormatter` for testable, injectable formatting. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var startedAgo: String { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard let ref = firstJobStartedAt ?? createdAt else { return "—" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return RelativeTimeFormatter.string(from: ref) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Elapsed time derived from min(job.startedAt) → max(job.completedAt). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var elapsed: String { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let start = firstJobStartedAt { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let end = lastJobCompletedAt ?? Date() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let sec = Int(end.timeIntervalSince(start)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard sec >= 0 else { return "00:00" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let m = sec / 60; let s = sec % 60 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return String(format: "%02d:%02d", m, s) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let minutes = sec / 60; let seconds = sec % 60 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return String(format: "%02d:%02d", minutes, seconds) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard let start = createdAt else { return "00:00" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let sec = Int(Date().timeIntervalSince(start)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard sec >= 0 else { return "00:00" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let m = sec / 60; let s = sec % 60 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return String(format: "%02d:%02d", m, s) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let minutes = sec / 60; let seconds = sec % 60 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return String(format: "%02d:%02d", minutes, seconds) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // MARK: - Codable helpers (private to this file) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private struct ActionRunsResponse: Codable { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let workflowRuns: [RunPayload] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enum CodingKeys: String, CodingKey { case workflowRuns = "workflow_runs" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private struct RunPayload: Codable { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let id: Int | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let name: String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let status: String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let conclusion: String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let headBranch: String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let headSha: String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let displayTitle: String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let createdAt: String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let updatedAt: String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let htmlUrl: String? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let headCommit: HeadCommit? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let pullRequests: [PRRef]? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enum CodingKeys: String, CodingKey { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case id, name, status, conclusion | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case headBranch = "head_branch" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case headSha = "head_sha" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case displayTitle = "display_title" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case createdAt = "created_at" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case updatedAt = "updated_at" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case htmlUrl = "html_url" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case headCommit = "head_commit" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case pullRequests = "pull_requests" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // MARK: - Progress fraction (model layer — keeps view bodies clean) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Completion fraction 0.0–1.0 for the pie-progress dot, or `nil` (indeterminate) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// when status is queued or no job data is available. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// - `nil` → indeterminate (queued / no jobs loaded yet) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// - `1.0` → completed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// - `0..<1` → partial (jobsDone / jobsTotal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var progressFraction: Double? { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch groupStatus { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case .queued: return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case .completed: return 1.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case .inProgress: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard jobsTotal > 0 else { return nil } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Double(jobsDone) / Double(jobsTotal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+157
to
+169
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Count all concluded jobs in the progress math.
Suggested fix var progressFraction: Double? {
switch groupStatus {
case .queued: return nil
case .completed: return 1.0
case .inProgress:
- guard jobsTotal > 0 else { return nil }
- return Double(jobsDone) / Double(jobsTotal)
+ let completedJobs = jobs.filter { $0.conclusion != nil }.count
+ guard jobsTotal > 0 else { return nil }
+ return Double(completedJobs) / Double(jobsTotal)
}
}I’d drive 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private struct HeadCommit: Codable { let message: String } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private struct PRRef: Codable { let number: Int } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // MARK: - PR label | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Derives the short identifier for an action group row. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Priority: PR number → branch-embedded number → sha[:7]. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private func prLabel(from run: RunPayload) -> String { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let pr = run.pullRequests?.first { return "#\(pr.number)" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let branch = run.headBranch, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let range = branch.range(of: #"/(\d+)/"#, options: .regularExpression) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let digits = branch[range].filter { $0.isNumber } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "#\(digits)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static func == (lhs: ActionGroup, rhs: ActionGroup) -> Bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lhs.id == rhs.id && lhs.isDimmed == rhs.isDimmed && lhs.jobs == rhs.jobs && lhs.runs.map({ $0.id }) == rhs.runs.map({ $0.id }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return String(run.headSha.prefix(7)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // MARK: - Fetch + Group | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -299,27 +285,27 @@ func fetchActionGroups(for scope: String, cache: [String: ActionGroup] = [:]) -> | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // MARK: - Private helpers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Constructs an `ActiveJob` from a decoded `JobPayload`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func makeActiveJob(from j: JobPayload, iso: ISO8601DateFormatter, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func makeActiveJob(from payload: JobPayload, iso: ISO8601DateFormatter, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isDimmed: Bool = false) -> ActiveJob { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let steps: [JobStep] = (j.steps ?? []).enumerated().map { idx, s in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let steps: [JobStep] = (payload.steps ?? []).enumerated().map { idx, step in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JobStep( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: idx + 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: s.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: s.status, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conclusion: s.conclusion, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| startedAt: s.startedAt.flatMap { iso.date(from: $0) }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| completedAt: s.completedAt.flatMap { iso.date(from: $0) } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: step.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: step.status, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conclusion: step.conclusion, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| startedAt: step.startedAt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| completedAt: step.completedAt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ActiveJob( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: j.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: j.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: j.status, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conclusion: j.conclusion, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| startedAt: j.startedAt.flatMap { iso.date(from: $0) }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createdAt: j.createdAt.flatMap { iso.date(from: $0) }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| completedAt: j.completedAt.flatMap { iso.date(from: $0) }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| htmlUrl: j.htmlUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: payload.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: payload.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: payload.status, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conclusion: payload.conclusion, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| startedAt: payload.startedAt.flatMap { iso.date(from: $0) }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createdAt: payload.createdAt.flatMap { iso.date(from: $0) }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| completedAt: payload.completedAt.flatMap { iso.date(from: $0) }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| htmlUrl: payload.htmlUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isDimmed: isDimmed, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| steps: steps | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -381,4 +367,4 @@ private func statusPriority(_ status: GroupStatus) -> Int { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case .completed: return 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // swiftlint:enable opening_brace identifier_name missing_docs orphaned_doc_comment | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // swiftlint:enable opening_brace identifier_name orphaned_doc_comment missing_docs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: The completion fraction uses
jobsDone, butjobsDoneonly countssuccess/skippedjobs and excludes concludedfailure/cancelledjobs. This makes progress stay artificially low during runs that already have failed/cancelled completed jobs. Compute the fraction from all concluded jobs (for exampleconclusion != nil) so the pie reflects real completion. [incorrect variable usage]Severity Level: Major⚠️
Steps of Reproduction ✅
Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖