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 @@
532532 "$(inherited)",
533533 "@executable_path/Frameworks",
534534 );
535 MARKETING_VERSION = 2.15.1;
535 MARKETING_VERSION = 2.15.2;
536536 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
537537 PRODUCT_NAME = "$(TARGET_NAME)";
538538 STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -569,7 +569,7 @@
569569 "$(inherited)",
570570 "@executable_path/Frameworks",
571571 );
572 MARKETING_VERSION = 2.15.1;
572 MARKETING_VERSION = 2.15.2;
573573 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
574574 PRODUCT_NAME = "$(TARGET_NAME)";
575575 STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -632,7 +632,7 @@
632632 APPLICATION_EXTENSION_API_ONLY = YES;
633633 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements;
634634 CODE_SIGN_STYLE = Automatic;
635 CURRENT_PROJECT_VERSION = 33;
635 CURRENT_PROJECT_VERSION = 34;
636636 DEVELOPMENT_TEAM = ZCNAX3VL9D;
637637 GENERATE_INFOPLIST_FILE = NO;
638638 INFOPLIST_FILE = HutchWidgetExtension/Info.plist;
@@ -642,7 +642,7 @@
642642 "@executable_path/Frameworks",
643643 "@executable_path/../../Frameworks",
644644 );
645 MARKETING_VERSION = 2.15.1;
645 MARKETING_VERSION = 2.15.2;
646646 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension;
647647 PRODUCT_NAME = "$(TARGET_NAME)";
648648 SKIP_INSTALL = YES;
@@ -661,7 +661,7 @@
661661 APPLICATION_EXTENSION_API_ONLY = YES;
662662 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements;
663663 CODE_SIGN_STYLE = Automatic;
664 CURRENT_PROJECT_VERSION = 33;
664 CURRENT_PROJECT_VERSION = 34;
665665 DEVELOPMENT_TEAM = ZCNAX3VL9D;
666666 GENERATE_INFOPLIST_FILE = NO;
667667 INFOPLIST_FILE = HutchWidgetExtension/Info.plist;
@@ -671,7 +671,7 @@
671671 "@executable_path/Frameworks",
672672 "@executable_path/../../Frameworks",
673673 );
674 MARKETING_VERSION = 2.15.1;
674 MARKETING_VERSION = 2.15.2;
675675 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension;
676676 PRODUCT_NAME = "$(TARGET_NAME)";
677677 SKIP_INSTALL = YES;
Hutch/Networking/HutchStatsService.swift +14 −5
@@ -9,20 +9,23 @@ struct HutchStatsService: ContributionCalendarServing {
99 private let session: URLSession
1010 private let decoder: JSONDecoder
1111 private let baseURL: URL
12 private let currentActor: String?
1213
1314 init(
1415 session: URLSession = .shared,
15 configuration: AppConfiguration
16 configuration: AppConfiguration,
17 currentActor: String? = nil
1618 ) {
1719 self.session = session
1820 self.baseURL = configuration.hutchStatsBaseURL
1921 self.decoder = JSONDecoder()
22 self.currentActor = currentActor
2023 }
2124
2225 func fetchContributionCalendar(actor: String, endingOn endDate: Date) async throws -> ContributionCalendarResponse {
2326 return try await fetch(
2427 path: "api/contributions/\(actor)",
25 queryItems: trailingYearQueryItems(endingOn: endDate),
28 queryItems: contributionQueryItems(actor: actor, endingOn: endDate),
2629 responseType: ContributionCalendarResponse.self
2730 )
2831 }
@@ -30,7 +33,7 @@ struct HutchStatsService: ContributionCalendarServing {
3033 func fetchContributionStats(actor: String, endingOn endDate: Date) async throws -> ContributionStatsResponse {
3134 return try await fetch(
3235 path: "api/contributions/\(actor)/stats",
33 queryItems: trailingYearQueryItems(endingOn: endDate),
36 queryItems: contributionQueryItems(actor: actor, endingOn: endDate),
3437 responseType: ContributionStatsResponse.self
3538 )
3639 }
@@ -86,15 +89,21 @@ struct HutchStatsService: ContributionCalendarServing {
8689 return normalizedStartDate...normalizedEndDate
8790 }
8891
89 private func trailingYearQueryItems(endingOn endDate: Date) -> [URLQueryItem] {
92 func contributionQueryItems(actor: String, endingOn endDate: Date) -> [URLQueryItem] {
9093 let range = trailingRange(endingOn: endDate)
9194 let start = Self.rangeFormatter.string(from: range.lowerBound)
9295 let end = Self.rangeFormatter.string(from: range.upperBound)
9396
94 return [
97 var queryItems = [
9598 URLQueryItem(name: "from", value: start),
9699 URLQueryItem(name: "to", value: end)
97100 ]
101
102 if actor == currentActor {
103 queryItems.append(URLQueryItem(name: "prioritize", value: "self"))
104 }
105
106 return queryItems
98107 }
99108
100109 private static let rangeFormatter: DateFormatter = {
Hutch/Views/Lookup/UserProfileView.swift +4 −1
@@ -175,7 +175,10 @@ struct UserProfileView: View {
175175 ownerUsername: owner,
176176 actor: actor,
177177 client: appState.client,
178 statsService: HutchStatsService(configuration: appState.configuration)
178 statsService: HutchStatsService(
179 configuration: appState.configuration,
180 currentActor: appState.currentUser?.canonicalName
181 )
179182 )
180183 profileViewModel = newViewModel
181184 vm = newViewModel
Hutch/Views/More/ProfileView.swift +4 −1
@@ -46,7 +46,10 @@ struct ProfileView: View {
4646 ownerUsername: owner,
4747 actor: actor,
4848 client: appState.client,
49 statsService: HutchStatsService(configuration: appState.configuration)
49 statsService: HutchStatsService(
50 configuration: appState.configuration,
51 currentActor: appState.currentUser?.canonicalName
52 )
5053 )
5154 contributionViewModel = newViewModel
5255 vm = newViewModel
HutchTests/ContributionCalendarTests.swift +24
@@ -93,6 +93,30 @@ struct ContributionCalendarTests {
9393 #expect(range.upperBound == endDate)
9494 }
9595
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
96120 @Test
97121 func contributionIntensityBucketsMatchProductRules() {
98122 #expect(ContributionIntensity(count: 0) == .empty)