Commit c5854942bc
Unsigned
Layout: unified · split
Hutch.xcodeproj/project.pbxproj +6 −6
| @@ -532,7 +532,7 @@ | |||
| 532 | "$(inherited)", | 532 | "$(inherited)", |
| 533 | "@executable_path/Frameworks", | 533 | "@executable_path/Frameworks", |
| 534 | ); | 534 | ); |
| 535 | MARKETING_VERSION = 2.15.1; | 535 | MARKETING_VERSION = 2.15.2; |
| 536 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; | 536 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; |
| 537 | PRODUCT_NAME = "$(TARGET_NAME)"; | 537 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 538 | STRING_CATALOG_GENERATE_SYMBOLS = YES; | 538 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
| @@ -569,7 +569,7 @@ | |||
| 569 | "$(inherited)", | 569 | "$(inherited)", |
| 570 | "@executable_path/Frameworks", | 570 | "@executable_path/Frameworks", |
| 571 | ); | 571 | ); |
| 572 | MARKETING_VERSION = 2.15.1; | 572 | MARKETING_VERSION = 2.15.2; |
| 573 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; | 573 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; |
| 574 | PRODUCT_NAME = "$(TARGET_NAME)"; | 574 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 575 | STRING_CATALOG_GENERATE_SYMBOLS = YES; | 575 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
| @@ -632,7 +632,7 @@ | |||
| 632 | APPLICATION_EXTENSION_API_ONLY = YES; | 632 | APPLICATION_EXTENSION_API_ONLY = YES; |
| 633 | CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; | 633 | CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; |
| 634 | CODE_SIGN_STYLE = Automatic; | 634 | CODE_SIGN_STYLE = Automatic; |
| 635 | CURRENT_PROJECT_VERSION = 33; | 635 | CURRENT_PROJECT_VERSION = 34; |
| 636 | DEVELOPMENT_TEAM = ZCNAX3VL9D; | 636 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 637 | GENERATE_INFOPLIST_FILE = NO; | 637 | GENERATE_INFOPLIST_FILE = NO; |
| 638 | INFOPLIST_FILE = HutchWidgetExtension/Info.plist; | 638 | INFOPLIST_FILE = HutchWidgetExtension/Info.plist; |
| @@ -642,7 +642,7 @@ | |||
| 642 | "@executable_path/Frameworks", | 642 | "@executable_path/Frameworks", |
| 643 | "@executable_path/../../Frameworks", | 643 | "@executable_path/../../Frameworks", |
| 644 | ); | 644 | ); |
| 645 | MARKETING_VERSION = 2.15.1; | 645 | MARKETING_VERSION = 2.15.2; |
| 646 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; | 646 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; |
| 647 | PRODUCT_NAME = "$(TARGET_NAME)"; | 647 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 648 | SKIP_INSTALL = YES; | 648 | SKIP_INSTALL = YES; |
| @@ -661,7 +661,7 @@ | |||
| 661 | APPLICATION_EXTENSION_API_ONLY = YES; | 661 | APPLICATION_EXTENSION_API_ONLY = YES; |
| 662 | CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; | 662 | CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; |
| 663 | CODE_SIGN_STYLE = Automatic; | 663 | CODE_SIGN_STYLE = Automatic; |
| 664 | CURRENT_PROJECT_VERSION = 33; | 664 | CURRENT_PROJECT_VERSION = 34; |
| 665 | DEVELOPMENT_TEAM = ZCNAX3VL9D; | 665 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 666 | GENERATE_INFOPLIST_FILE = NO; | 666 | GENERATE_INFOPLIST_FILE = NO; |
| 667 | INFOPLIST_FILE = HutchWidgetExtension/Info.plist; | 667 | INFOPLIST_FILE = HutchWidgetExtension/Info.plist; |
| @@ -671,7 +671,7 @@ | |||
| 671 | "@executable_path/Frameworks", | 671 | "@executable_path/Frameworks", |
| 672 | "@executable_path/../../Frameworks", | 672 | "@executable_path/../../Frameworks", |
| 673 | ); | 673 | ); |
| 674 | MARKETING_VERSION = 2.15.1; | 674 | MARKETING_VERSION = 2.15.2; |
| 675 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; | 675 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; |
| 676 | PRODUCT_NAME = "$(TARGET_NAME)"; | 676 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 677 | SKIP_INSTALL = YES; | 677 | SKIP_INSTALL = YES; |
Hutch/Networking/HutchStatsService.swift +14 −5
| @@ -9,20 +9,23 @@ struct HutchStatsService: ContributionCalendarServing { | |||
| 9 | private let session: URLSession | 9 | private let session: URLSession |
| 10 | private let decoder: JSONDecoder | 10 | private let decoder: JSONDecoder |
| 11 | private let baseURL: URL | 11 | private let baseURL: URL |
| 12 | private let currentActor: String? | ||
| 12 | 13 | ||
| 13 | init( | 14 | init( |
| 14 | session: URLSession = .shared, | 15 | session: URLSession = .shared, |
| 15 | configuration: AppConfiguration | 16 | configuration: AppConfiguration, |
| 17 | currentActor: String? = nil | ||
| 16 | ) { | 18 | ) { |
| 17 | self.session = session | 19 | self.session = session |
| 18 | self.baseURL = configuration.hutchStatsBaseURL | 20 | self.baseURL = configuration.hutchStatsBaseURL |
| 19 | self.decoder = JSONDecoder() | 21 | self.decoder = JSONDecoder() |
| 22 | self.currentActor = currentActor | ||
| 20 | } | 23 | } |
| 21 | 24 | ||
| 22 | func fetchContributionCalendar(actor: String, endingOn endDate: Date) async throws -> ContributionCalendarResponse { | 25 | func fetchContributionCalendar(actor: String, endingOn endDate: Date) async throws -> ContributionCalendarResponse { |
| 23 | return try await fetch( | 26 | return try await fetch( |
| 24 | path: "api/contributions/\(actor)", | 27 | path: "api/contributions/\(actor)", |
| 25 | queryItems: trailingYearQueryItems(endingOn: endDate), | 28 | queryItems: contributionQueryItems(actor: actor, endingOn: endDate), |
| 26 | responseType: ContributionCalendarResponse.self | 29 | responseType: ContributionCalendarResponse.self |
| 27 | ) | 30 | ) |
| 28 | } | 31 | } |
| @@ -30,7 +33,7 @@ struct HutchStatsService: ContributionCalendarServing { | |||
| 30 | func fetchContributionStats(actor: String, endingOn endDate: Date) async throws -> ContributionStatsResponse { | 33 | func fetchContributionStats(actor: String, endingOn endDate: Date) async throws -> ContributionStatsResponse { |
| 31 | return try await fetch( | 34 | return try await fetch( |
| 32 | path: "api/contributions/\(actor)/stats", | 35 | path: "api/contributions/\(actor)/stats", |
| 33 | queryItems: trailingYearQueryItems(endingOn: endDate), | 36 | queryItems: contributionQueryItems(actor: actor, endingOn: endDate), |
| 34 | responseType: ContributionStatsResponse.self | 37 | responseType: ContributionStatsResponse.self |
| 35 | ) | 38 | ) |
| 36 | } | 39 | } |
| @@ -86,15 +89,21 @@ struct HutchStatsService: ContributionCalendarServing { | |||
| 86 | return normalizedStartDate...normalizedEndDate | 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 | let range = trailingRange(endingOn: endDate) | 93 | let range = trailingRange(endingOn: endDate) |
| 91 | let start = Self.rangeFormatter.string(from: range.lowerBound) | 94 | let start = Self.rangeFormatter.string(from: range.lowerBound) |
| 92 | let end = Self.rangeFormatter.string(from: range.upperBound) | 95 | let end = Self.rangeFormatter.string(from: range.upperBound) |
| 93 | 96 | ||
| 94 | return [ | 97 | var queryItems = [ |
| 95 | URLQueryItem(name: "from", value: start), | 98 | URLQueryItem(name: "from", value: start), |
| 96 | URLQueryItem(name: "to", value: end) | 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 | private static let rangeFormatter: DateFormatter = { | 109 | private static let rangeFormatter: DateFormatter = { |
Hutch/Views/Lookup/UserProfileView.swift +4 −1
| @@ -175,7 +175,10 @@ struct UserProfileView: View { | |||
| 175 | ownerUsername: owner, | 175 | ownerUsername: owner, |
| 176 | actor: actor, | 176 | actor: actor, |
| 177 | client: appState.client, | 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 | profileViewModel = newViewModel | 183 | profileViewModel = newViewModel |
| 181 | vm = newViewModel | 184 | vm = newViewModel |
Hutch/Views/More/ProfileView.swift +4 −1
| @@ -46,7 +46,10 @@ struct ProfileView: View { | |||
| 46 | ownerUsername: owner, | 46 | ownerUsername: owner, |
| 47 | actor: actor, | 47 | actor: actor, |
| 48 | client: appState.client, | 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 | contributionViewModel = newViewModel | 54 | contributionViewModel = newViewModel |
| 52 | vm = newViewModel | 55 | vm = newViewModel |
HutchTests/ContributionCalendarTests.swift +24
| @@ -93,6 +93,30 @@ struct ContributionCalendarTests { | |||
| 93 | #expect(range.upperBound == endDate) | 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 | @Test | 120 | @Test |
| 97 | func contributionIntensityBucketsMatchProductRules() { | 121 | func contributionIntensityBucketsMatchProductRules() { |
| 98 | #expect(ContributionIntensity(count: 0) == .empty) | 122 | #expect(ContributionIntensity(count: 0) == .empty) |