mirror of
https://codeberg.org/secana/Forji.git
synced 2026-06-16 05:13:55 -07:00
refactor: small name changes
This commit is contained in:
parent
2e07f6da4d
commit
57bde0934f
6 changed files with 46 additions and 28 deletions
|
|
@ -35,10 +35,11 @@ struct WorkflowStatusStyle {
|
|||
let isAnimated: Bool
|
||||
let label: String
|
||||
|
||||
/// Maps Forgejo's single status value to a UI style. Forgejo's known
|
||||
/// Maps Forgejo's single status value to a UI style. Used for runs, jobs,
|
||||
/// and steps — they share the same status vocabulary. Forgejo's known
|
||||
/// statuses: success, failure, cancelled, skipped, running, waiting,
|
||||
/// blocked, unknown. Anything else falls back to a question-mark glyph.
|
||||
static func forRun(status: String) -> WorkflowStatusStyle {
|
||||
static func forStatus(_ status: String) -> WorkflowStatusStyle {
|
||||
switch status {
|
||||
case "success":
|
||||
.init(symbol: "checkmark.circle.fill", tint: .green, isAnimated: false, label: "Success")
|
||||
|
|
@ -66,7 +67,7 @@ struct WorkflowStatusIcon: View {
|
|||
let status: String
|
||||
|
||||
var body: some View {
|
||||
let style = WorkflowStatusStyle.forRun(status: status)
|
||||
let style = WorkflowStatusStyle.forStatus(status)
|
||||
Image(systemName: style.symbol)
|
||||
.foregroundStyle(style.tint)
|
||||
.symbolEffect(
|
||||
|
|
@ -79,7 +80,7 @@ struct WorkflowStatusIcon: View {
|
|||
|
||||
extension WorkflowRun {
|
||||
var statusStyle: WorkflowStatusStyle {
|
||||
WorkflowStatusStyle.forRun(status: status)
|
||||
WorkflowStatusStyle.forStatus(status)
|
||||
}
|
||||
|
||||
var shortCommitSha: String? {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct WorkflowJobView: View {
|
|||
@State private var authService: AuthenticationService
|
||||
|
||||
@State private var view: WorkflowRunView?
|
||||
@State private var stepLogs: [Int: [WorkflowRunViewLogLine]] = [:]
|
||||
@State private var isLoading = false
|
||||
@State private var errorMessage: String?
|
||||
@State private var showError = false
|
||||
|
|
@ -37,7 +38,7 @@ struct WorkflowJobView: View {
|
|||
var body: some View {
|
||||
List {
|
||||
if let view {
|
||||
stepsSection(steps: view.state.currentJob.steps, logs: view.logs.stepsLog)
|
||||
stepsSection(steps: view.state.currentJob.steps)
|
||||
} else if isLoading {
|
||||
LoadingListSection()
|
||||
} else {
|
||||
|
|
@ -64,7 +65,7 @@ struct WorkflowJobView: View {
|
|||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func stepsSection(steps: [WorkflowRunViewStep], logs: [WorkflowRunViewStepLog]) -> some View {
|
||||
private func stepsSection(steps: [WorkflowRunViewStep]) -> some View {
|
||||
if steps.isEmpty {
|
||||
Section {
|
||||
ContentUnavailableView {
|
||||
|
|
@ -92,7 +93,7 @@ struct WorkflowJobView: View {
|
|||
}
|
||||
},
|
||||
),
|
||||
logLines: logs.first(where: { $0.step == index })?.lines ?? [],
|
||||
logLines: stepLogs[index] ?? [],
|
||||
)
|
||||
.accessibilityIdentifier("workflow-step-\(index)")
|
||||
}
|
||||
|
|
@ -101,7 +102,7 @@ struct WorkflowJobView: View {
|
|||
} footer: {
|
||||
Text(
|
||||
"Steps and logs come from Forgejo's web UI endpoint, not the public API. "
|
||||
+ "Output may change between Forgejo versions.",
|
||||
+ "Output may change between Forgejo versions.",
|
||||
)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.tertiary)
|
||||
|
|
@ -125,13 +126,15 @@ struct WorkflowJobView: View {
|
|||
errorMessage = nil
|
||||
|
||||
do {
|
||||
view = try await workflowService.fetchRunView(
|
||||
let fetched = try await workflowService.fetchRunView(
|
||||
owner: repository.owner,
|
||||
repo: repository.repoName,
|
||||
runIndex: runIndex,
|
||||
jobIndex: jobIndex,
|
||||
logCursors: [],
|
||||
)
|
||||
view = fetched
|
||||
mergeLogs(from: fetched)
|
||||
hasLoaded = true
|
||||
} catch is CancellationError {
|
||||
// Ignore cancellation
|
||||
|
|
@ -146,17 +149,19 @@ struct WorkflowJobView: View {
|
|||
private func loadLogs(for step: Int) async {
|
||||
guard let workflowService else { return }
|
||||
// Already loaded?
|
||||
if view?.logs.stepsLog.contains(where: { $0.step == step }) == true { return }
|
||||
if stepLogs[step] != nil { return }
|
||||
|
||||
do {
|
||||
let updated = try await workflowService.fetchRunView(
|
||||
let fetched = try await workflowService.fetchRunView(
|
||||
owner: repository.owner,
|
||||
repo: repository.repoName,
|
||||
runIndex: runIndex,
|
||||
jobIndex: jobIndex,
|
||||
logCursors: [WorkflowLogCursor(step: step, cursor: 0, expanded: true)],
|
||||
)
|
||||
view = updated
|
||||
// Each fetch returns logs only for the requested cursor(s); merge
|
||||
// into our running map so previously-expanded steps don't go blank.
|
||||
mergeLogs(from: fetched)
|
||||
} catch is CancellationError {
|
||||
// Ignore
|
||||
} catch {
|
||||
|
|
@ -164,6 +169,12 @@ struct WorkflowJobView: View {
|
|||
showError = true
|
||||
}
|
||||
}
|
||||
|
||||
private func mergeLogs(from runView: WorkflowRunView) {
|
||||
for stepLog in runView.logs.stepsLog {
|
||||
stepLogs[stepLog.step] = stepLog.lines
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct StepDisclosureRow: View {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ struct WorkflowRunDetailView: View {
|
|||
.errorAlert(message: $errorMessage, isPresented: $showError)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func headerSection(run: WorkflowRun) -> some View {
|
||||
Section {
|
||||
HStack(spacing: 10) {
|
||||
|
|
@ -89,7 +88,6 @@ struct WorkflowRunDetailView: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func metadataSection(run: WorkflowRun) -> some View {
|
||||
Section("Details") {
|
||||
if let workflow = run.workflowId {
|
||||
|
|
|
|||
|
|
@ -7,51 +7,51 @@ import Testing
|
|||
struct WorkflowStatusIconTests {
|
||||
|
||||
@Test func successShowsCheckmark() {
|
||||
let style = WorkflowStatusStyle.forRun(status: "success")
|
||||
let style = WorkflowStatusStyle.forStatus("success")
|
||||
#expect(style.symbol == "checkmark.circle.fill")
|
||||
#expect(style.isAnimated == false)
|
||||
#expect(style.label == "Success")
|
||||
}
|
||||
|
||||
@Test func failureShowsXmark() {
|
||||
let style = WorkflowStatusStyle.forRun(status: "failure")
|
||||
let style = WorkflowStatusStyle.forStatus("failure")
|
||||
#expect(style.symbol == "xmark.circle.fill")
|
||||
#expect(style.label == "Failed")
|
||||
}
|
||||
|
||||
@Test func cancelledShowsSlash() {
|
||||
let style = WorkflowStatusStyle.forRun(status: "cancelled")
|
||||
let style = WorkflowStatusStyle.forStatus("cancelled")
|
||||
#expect(style.symbol == "slash.circle.fill")
|
||||
}
|
||||
|
||||
@Test func skippedShowsForward() {
|
||||
let style = WorkflowStatusStyle.forRun(status: "skipped")
|
||||
let style = WorkflowStatusStyle.forStatus("skipped")
|
||||
#expect(style.symbol == "forward.circle.fill")
|
||||
}
|
||||
|
||||
@Test func runningIsAnimated() {
|
||||
let style = WorkflowStatusStyle.forRun(status: "running")
|
||||
let style = WorkflowStatusStyle.forStatus("running")
|
||||
#expect(style.symbol == "circle.dotted")
|
||||
#expect(style.isAnimated == true)
|
||||
}
|
||||
|
||||
@Test func waitingShowsClock() {
|
||||
let style = WorkflowStatusStyle.forRun(status: "waiting")
|
||||
let style = WorkflowStatusStyle.forStatus("waiting")
|
||||
#expect(style.symbol == "clock")
|
||||
}
|
||||
|
||||
@Test func blockedShowsHand() {
|
||||
let style = WorkflowStatusStyle.forRun(status: "blocked")
|
||||
let style = WorkflowStatusStyle.forStatus("blocked")
|
||||
#expect(style.symbol == "hand.raised.fill")
|
||||
}
|
||||
|
||||
@Test func unknownStatusShowsQuestionMark() {
|
||||
let style = WorkflowStatusStyle.forRun(status: "unknown")
|
||||
let style = WorkflowStatusStyle.forStatus("unknown")
|
||||
#expect(style.symbol == "questionmark.circle")
|
||||
}
|
||||
|
||||
@Test func arbitraryStatusFallsBackToQuestionMark() {
|
||||
let style = WorkflowStatusStyle.forRun(status: "weird")
|
||||
let style = WorkflowStatusStyle.forStatus("weird")
|
||||
#expect(style.symbol == "questionmark.circle")
|
||||
#expect(style.label == "Weird")
|
||||
}
|
||||
|
|
@ -92,6 +92,14 @@ struct WorkflowStatusIconTests {
|
|||
#expect(run.displayName == "ci.yml")
|
||||
}
|
||||
|
||||
@Test func displayNameFallsBackWhenTitleIsEmpty() {
|
||||
let run = WorkflowRun(
|
||||
id: 1, title: "", status: "running",
|
||||
workflowId: "ci.yml", indexInRepo: 5,
|
||||
)
|
||||
#expect(run.displayName == "ci.yml")
|
||||
}
|
||||
|
||||
@Test func displayNameFallsBackToIndex() {
|
||||
let run = WorkflowRun(
|
||||
id: 1, title: nil, status: "running", indexInRepo: 7,
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ final class ActionsUITests: ForgejoReadOnlyUITestBase {
|
|||
}
|
||||
|
||||
/// The Actions tab is only present for repos with `has_actions: true`.
|
||||
/// Until the seed enables actions on a test repo, this checks that — when
|
||||
/// the tab appears at all — selecting it renders the section picker.
|
||||
/// The seed leaves `test-repo` at the Forgejo-15 default (actions enabled),
|
||||
/// so the tab is expected to render and produce the workflow status filter.
|
||||
@MainActor
|
||||
func testActionsTabRendersWhenAvailable() throws {
|
||||
navigateToRepoDetail("test-repo")
|
||||
|
|
@ -18,9 +18,9 @@ final class ActionsUITests: ForgejoReadOnlyUITestBase {
|
|||
XCTAssertTrue(tabPicker.waitForExistence(timeout: 10), "Tab picker should appear")
|
||||
|
||||
let actionsTab = tabPicker.buttons["Actions"]
|
||||
try XCTSkipUnless(
|
||||
XCTAssertTrue(
|
||||
actionsTab.waitForExistence(timeout: 3),
|
||||
"Actions tab not exposed on this seed repo (has_actions disabled)",
|
||||
"Actions tab should be exposed for test-repo (Forgejo 15 default has_actions: true)",
|
||||
)
|
||||
|
||||
actionsTab.tap()
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ Foji is availbe on the Apple App Store for free: [Forji App Store](https://apps.
|
|||
|
||||
### Actions
|
||||
- Browse Forgejo Actions workflows defined in a repository
|
||||
- View workflow runs filtered by status (running, completed, queued)
|
||||
- View workflow runs filtered by status (running, success, failed)
|
||||
- Inspect a run's jobs with status and duration
|
||||
- Read job logs
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue