Files
steipete--codexbar/Sources/CodexBar/UsageStoreSupport.swift
T
2026-07-13 12:22:33 +08:00

152 lines
4.9 KiB
Swift

import CodexBarCore
import Foundation
enum ProviderStatusIndicator: String {
case none
case minor
case major
case critical
case maintenance
case unknown
var hasIssue: Bool {
switch self {
case .none: false
default: true
}
}
var label: String {
switch self {
case .none: L("status_operational")
case .minor: L("status_partial_outage")
case .major: L("status_major_outage")
case .critical: L("status_critical_issue")
case .maintenance: L("status_maintenance")
case .unknown: L("status_unknown")
}
}
}
struct ProviderStatus {
let indicator: ProviderStatusIndicator
let description: String?
let updatedAt: Date?
}
/// A single component/service row on a statuspage.io-style status page
/// (e.g. "Codex API", "CLI", "FedRAMP") with its current state. A row with non-empty
/// `children` is a component group and renders as an expandable dropdown.
struct ProviderStatusComponent: Identifiable, Equatable {
let id: String
let name: String
let indicator: ProviderStatusIndicator
/// Raw provider status. The display label is localized when the row renders so changing
/// the app language does not require another network refresh.
let status: String
/// Child rows for a component group; empty for leaf components.
var children: [ProviderStatusComponent] = []
var isGroup: Bool {
!self.children.isEmpty
}
var statusLabel: String {
Self.label(forStatuspageStatus: self.status)
}
/// Maps a statuspage.io component `status` string to our indicator + display label.
static func indicator(forStatuspageStatus status: String) -> ProviderStatusIndicator {
switch status {
case "operational": .none
case "degraded_performance": .minor
case "partial_outage": .major
case "major_outage", "full_outage": .critical
case "under_maintenance": .maintenance
default: .unknown
}
}
static func label(forStatuspageStatus status: String) -> String {
switch status {
case "operational": L("status_operational")
case "degraded_performance": L("status_degraded")
case "partial_outage": L("status_partial_outage")
case "major_outage", "full_outage": L("status_major_outage")
case "under_maintenance": L("status_maintenance")
default: L("status_unknown")
}
}
}
/// Tracks consecutive failures so we can ignore a single flake when we previously had fresh data.
struct ConsecutiveFailureGate {
private(set) var streak: Int = 0
mutating func recordSuccess() {
self.streak = 0
}
mutating func reset() {
self.streak = 0
}
/// Returns true when the caller should surface the error to the UI.
mutating func shouldSurfaceError(onFailureWithPriorData hadPriorData: Bool) -> Bool {
self.streak += 1
if hadPriorData, self.streak == 1 { return false }
return true
}
}
#if DEBUG
extension UsageStore {
func _setSnapshotForTesting(_ snapshot: UsageSnapshot?, provider: UsageProvider) {
self.snapshots[provider] = snapshot?.scoped(to: provider)
}
func _setTokenSnapshotForTesting(_ snapshot: CostUsageTokenSnapshot?, provider: UsageProvider) {
self.tokenSnapshots[provider] = snapshot
}
func _setTokenErrorForTesting(_ error: String?, provider: UsageProvider) {
self.tokenErrors[provider] = error
}
func _setErrorForTesting(_ error: String?, provider: UsageProvider) {
self.errors[provider] = error
}
func _setKnownLimitsAvailabilityForTesting(
_ availability: UsageLimitsAvailability?,
provider: UsageProvider)
{
self.knownLimitsAvailabilityByProvider[provider] = availability
}
func _setCodexHistoricalDatasetForTesting(_ dataset: CodexHistoricalDataset?, accountKey: String? = nil) {
self.codexHistoricalDataset = dataset
self.codexHistoricalDatasetAccountKey = accountKey
self.historicalPaceRevision += 1
}
/// Cancels the one-shot persisted plan-utilization load and treats the
/// in-memory dictionary as "loaded" so callers can assign state directly
/// without racing the background decode. Used by test helpers that
/// intentionally seed history from scratch.
func _cancelPlanUtilizationHistoryLoadForTesting() {
self.planUtilizationHistoryLoadTask?.cancel()
self.planUtilizationHistoryLoadTask = nil
self.planUtilizationHistoryLoaded = true
}
/// Awaits the background plan-utilization load task to completion. Used
/// by tests that write history files to disk before constructing
/// `UsageStore` and then expect the dictionary to be populated by the
/// time assertions run.
func _waitForPlanUtilizationHistoryLoadForTesting() async {
await self.planUtilizationHistoryLoadTask?.value
}
}
#endif