Commit c5854942bc
c5854942bc4afa296913b0f1bab526dd68b0668e
parent: 6ba2e50325
Unsigned
cmc <hello@cleberg.net> · 2026-04-12 16:49 UTC
feat: add prioritization of polling hutch-stats for logged in user
Layout: unified · split
Hutch.xcodeproj/project.pbxproj
+6 −6
| @@ -532,7 +532,7 @@ |
| 532 | 532 | "$(inherited)", |
| 533 | 533 | "@executable_path/Frameworks", |
| 534 | 534 | ); |
| 535 | | MARKETING_VERSION = 2.15.1; |
| 535 | MARKETING_VERSION = 2.15.2; |
| 536 | 536 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; |
| 537 | 537 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 538 | 538 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
| @@ -569,7 +569,7 @@ |
| 569 | 569 | "$(inherited)", |
| 570 | 570 | "@executable_path/Frameworks", |
| 571 | 571 | ); |
| 572 | | MARKETING_VERSION = 2.15.1; |
| 572 | MARKETING_VERSION = 2.15.2; |
| 573 | 573 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; |
| 574 | 574 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 575 | 575 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
| @@ -632,7 +632,7 @@ |
| 632 | 632 | APPLICATION_EXTENSION_API_ONLY = YES; |
| 633 | 633 | CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; |
| 634 | 634 | CODE_SIGN_STYLE = Automatic; |
| 635 | | CURRENT_PROJECT_VERSION = 33; |
| 635 | CURRENT_PROJECT_VERSION = 34; |
| 636 | 636 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 637 | 637 | GENERATE_INFOPLIST_FILE = NO; |
| 638 | 638 | INFOPLIST_FILE = HutchWidgetExtension/Info.plist; |
| @@ -642,7 +642,7 @@ |
| 642 | 642 | "@executable_path/Frameworks", |
| 643 | 643 | "@executable_path/../../Frameworks", |
| 644 | 644 | ); |
| 645 | | MARKETING_VERSION = 2.15.1; |
| 645 | MARKETING_VERSION = 2.15.2; |
| 646 | 646 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; |
| 647 | 647 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 648 | 648 | SKIP_INSTALL = YES; |
| @@ -661,7 +661,7 @@ |
| 661 | 661 | APPLICATION_EXTENSION_API_ONLY = YES; |
| 662 | 662 | CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; |
| 663 | 663 | CODE_SIGN_STYLE = Automatic; |
| 664 | | CURRENT_PROJECT_VERSION = 33; |
| 664 | CURRENT_PROJECT_VERSION = 34; |
| 665 | 665 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 666 | 666 | GENERATE_INFOPLIST_FILE = NO; |
| 667 | 667 | INFOPLIST_FILE = HutchWidgetExtension/Info.plist; |
| @@ -671,7 +671,7 @@ |
| 671 | 671 | "@executable_path/Frameworks", |
| 672 | 672 | "@executable_path/../../Frameworks", |
| 673 | 673 | ); |
| 674 | | MARKETING_VERSION = 2.15.1; |
| 674 | MARKETING_VERSION = 2.15.2; |
| 675 | 675 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; |
| 676 | 676 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 677 | 677 | SKIP_INSTALL = YES; |
Hutch/Networking/HutchStatsService.swift
+14 −5
| @@ -9,20 +9,23 @@ struct HutchStatsService: ContributionCalendarServing { |
| 9 | 9 | private let session: URLSession |
| 10 | 10 | private let decoder: JSONDecoder |
| 11 | 11 | private let baseURL: URL |
| 12 | private let currentActor: String? |
| 12 | 13 | |
| 13 | 14 | init( |
| 14 | 15 | session: URLSession = .shared, |
| 15 | | configuration: AppConfiguration |
| 16 | configuration: AppConfiguration, |
| 17 | currentActor: String? = nil |
| 16 | 18 | ) { |
| 17 | 19 | self.session = session |
| 18 | 20 | self.baseURL = configuration.hutchStatsBaseURL |
| 19 | 21 | self.decoder = JSONDecoder() |
| 22 | self.currentActor = currentActor |
| 20 | 23 | } |
| 21 | 24 | |
| 22 | 25 | func fetchContributionCalendar(actor: String, endingOn endDate: Date) async throws -> ContributionCalendarResponse { |
| 23 | 26 | return try await fetch( |
| 24 | 27 | path: "api/contributions/\(actor)", |
| 25 | | queryItems: trailingYearQueryItems(endingOn: endDate), |
| 28 | queryItems: contributionQueryItems(actor: actor, endingOn: endDate), |
| 26 | 29 | responseType: ContributionCalendarResponse.self |
| 27 | 30 | ) |
| 28 | 31 | } |
| @@ -30,7 +33,7 @@ struct HutchStatsService: ContributionCalendarServing { |
| 30 | 33 | func fetchContributionStats(actor: String, endingOn endDate: Date) async throws -> ContributionStatsResponse { |
| 31 | 34 | return try await fetch( |
| 32 | 35 | path: "api/contributions/\(actor)/stats", |
| 33 | | queryItems: trailingYearQueryItems(endingOn: endDate), |
| 36 | queryItems: contributionQueryItems(actor: actor, endingOn: endDate), |
| 34 | 37 | responseType: ContributionStatsResponse.self |
| 35 | 38 | ) |
| 36 | 39 | } |
| @@ -86,15 +89,21 @@ struct HutchStatsService: ContributionCalendarServing { |
| 86 | 89 | return normalizedStartDate...normalizedEndDate |
| 87 | 90 | } |
| 88 | 91 | |
| 89 | | private func trailingYearQueryItems(endingOn endDate: Date) -> [URLQueryItem] { |
| 92 | func contributionQueryItems(actor: String, endingOn endDate: Date) -> [URLQueryItem] { |
| 90 | 93 | let range = trailingRange(endingOn: endDate) |
| 91 | 94 | let start = Self.rangeFormatter.string(from: range.lowerBound) |
| 92 | 95 | let end = Self.rangeFormatter.string(from: range.upperBound) |
| 93 | 96 | |
| 94 | | return [ |
| 97 | var queryItems = [ |
| 95 | 98 | URLQueryItem(name: "from", value: start), |
| 96 | 99 | URLQueryItem(name: "to", value: end) |
| 97 | 100 | ] |
| 101 | |
| 102 | if actor == currentActor { |
| 103 | queryItems.append(URLQueryItem(name: "prioritize", value: "self")) |
| 104 | } |
| 105 | |
| 106 | return queryItems |
| 98 | 107 | } |
| 99 | 108 | |
| 100 | 109 | private static let rangeFormatter: DateFormatter = { |
Hutch/Views/Lookup/UserProfileView.swift
+4 −1
| @@ -175,7 +175,10 @@ struct UserProfileView: View { |
| 175 | 175 | ownerUsername: owner, |
| 176 | 176 | actor: actor, |
| 177 | 177 | client: appState.client, |
| 178 | | statsService: HutchStatsService(configuration: appState.configuration) |
| 178 | statsService: HutchStatsService( |
| 179 | configuration: appState.configuration, |
| 180 | currentActor: appState.currentUser?.canonicalName |
| 181 | ) |
| 179 | 182 | ) |
| 180 | 183 | profileViewModel = newViewModel |
| 181 | 184 | vm = newViewModel |
Hutch/Views/More/ProfileView.swift
+4 −1
| @@ -46,7 +46,10 @@ struct ProfileView: View { |
| 46 | 46 | ownerUsername: owner, |
| 47 | 47 | actor: actor, |
| 48 | 48 | client: appState.client, |
| 49 | | statsService: HutchStatsService(configuration: appState.configuration) |
| 49 | statsService: HutchStatsService( |
| 50 | configuration: appState.configuration, |
| 51 | currentActor: appState.currentUser?.canonicalName |
| 52 | ) |
| 50 | 53 | ) |
| 51 | 54 | contributionViewModel = newViewModel |
| 52 | 55 | vm = newViewModel |
HutchTests/ContributionCalendarTests.swift
+24
| @@ -93,6 +93,30 @@ struct ContributionCalendarTests { |
| 93 | 93 | #expect(range.upperBound == endDate) |
| 94 | 94 | } |
| 95 | 95 | |
| 96 | @Test |
| 97 | func selfContributionRequestsIncludeExplicitPrioritySignal() { |
| 98 | let service = HutchStatsService( |
| 99 | configuration: AppConfiguration(), |
| 100 | currentActor: "~alice" |
| 101 | ) |
| 102 | let endDate = ContributionDateParser.parse("2026-04-11")! |
| 103 | let queryItems = service.contributionQueryItems(actor: "~alice", endingOn: endDate) |
| 104 | |
| 105 | #expect(queryItems.contains(URLQueryItem(name: "prioritize", value: "self"))) |
| 106 | } |
| 107 | |
| 108 | @Test |
| 109 | func otherContributionRequestsDoNotIncludePrioritySignal() { |
| 110 | let service = HutchStatsService( |
| 111 | configuration: AppConfiguration(), |
| 112 | currentActor: "~alice" |
| 113 | ) |
| 114 | let endDate = ContributionDateParser.parse("2026-04-11")! |
| 115 | let queryItems = service.contributionQueryItems(actor: "~bob", endingOn: endDate) |
| 116 | |
| 117 | #expect(queryItems.contains(URLQueryItem(name: "prioritize", value: "self")) == false) |
| 118 | } |
| 119 | |
| 96 | 120 | @Test |
| 97 | 121 | func contributionIntensityBucketsMatchProductRules() { |
| 98 | 122 | #expect(ContributionIntensity(count: 0) == .empty) |