| @@ -343,7 +343,12 @@ final class HomeViewModel { |
| 343 | 343 | self.accountID = accountID |
| 344 | 344 | } |
| 345 | 345 | |
| 346 | | func loadDashboard() async { |
| 346 | /// Loads the dashboard. |
| 347 | /// |
| 348 | /// `forceRefresh` bypasses the cache. Without it, a pull to refresh returns |
| 349 | /// whatever is already cached and only schedules a background fetch, so new |
| 350 | /// mail cannot show up on the first pull. |
| 351 | func loadDashboard(forceRefresh: Bool = false) async { |
| 347 | 352 | isLoadingProjects = true |
| 348 | 353 | isLoadingAssignedTickets = true |
| 349 | 354 | isLoadingRecentBuilds = true |
| @@ -354,11 +359,11 @@ final class HomeViewModel { |
| 354 | 359 | isShowingStaleSystemStatus = false |
| 355 | 360 | systemStatusErrorMessage = nil |
| 356 | 361 | |
| 357 | | async let projectsTask = loadProjects() |
| 358 | | async let jobsTask = loadRecentJobs() |
| 359 | | async let assignedTicketsTask = loadAssignedTickets() |
| 360 | | async let inboxUnreadTask = loadInboxUnreadSnapshot() |
| 361 | | async let systemStatusTask = loadSystemStatusSnapshot() |
| 362 | async let projectsTask = loadProjects(forceRefresh: forceRefresh) |
| 363 | async let jobsTask = loadRecentJobs(forceRefresh: forceRefresh) |
| 364 | async let assignedTicketsTask = loadAssignedTickets(forceRefresh: forceRefresh) |
| 365 | async let inboxUnreadTask = loadInboxUnreadSnapshot(forceRefresh: forceRefresh) |
| 366 | async let systemStatusTask = loadSystemStatusSnapshot(forceRefresh: forceRefresh) |
| 362 | 367 | |
| 363 | 368 | let projectsResult = await projectsTask |
| 364 | 369 | switch projectsResult { |
| @@ -676,7 +681,7 @@ final class HomeViewModel { |
| 676 | 681 | persistNeedsAttentionSnapshot() |
| 677 | 682 | } |
| 678 | 683 | |
| 679 | | private func loadProjects() async -> Result<[Project], Error> { |
| 684 | private func loadProjects(forceRefresh: Bool) async -> Result<[Project], Error> { |
| 680 | 685 | do { |
| 681 | 686 | return .success(try await projectService.fetchProjects()) |
| 682 | 687 | } catch { |
| @@ -684,7 +689,7 @@ final class HomeViewModel { |
| 684 | 689 | } |
| 685 | 690 | } |
| 686 | 691 | |
| 687 | | private func loadRecentJobs() async -> Result<[HomeJobPayload], Error> { |
| 692 | private func loadRecentJobs(forceRefresh: Bool) async -> Result<[HomeJobPayload], Error> { |
| 688 | 693 | do { |
| 689 | 694 | let cached = try await client.executeCached( |
| 690 | 695 | service: .builds, |
| @@ -693,7 +698,7 @@ final class HomeViewModel { |
| 693 | 698 | cacheKey: APICacheKeys.homeJobs(actor: currentUser.canonicalName), |
| 694 | 699 | resourceType: .buildList, |
| 695 | 700 | ttl: APICacheTTLs.homeDashboard, |
| 696 | | policy: .cacheFirstThenRefresh |
| 701 | policy: forceRefresh ? .refreshIgnoringCache : .cacheFirstThenRefresh |
| 697 | 702 | ) |
| 698 | 703 | return .success(cached.value.jobs.results) |
| 699 | 704 | } catch { |
| @@ -701,15 +706,15 @@ final class HomeViewModel { |
| 701 | 706 | } |
| 702 | 707 | } |
| 703 | 708 | |
| 704 | | private func loadInboxUnreadSnapshot() async -> HomeInboxUnreadSnapshot? { |
| 709 | private func loadInboxUnreadSnapshot(forceRefresh: Bool) async -> HomeInboxUnreadSnapshot? { |
| 705 | 710 | do { |
| 706 | | return try await fetchUnreadInboxSnapshot() |
| 711 | return try await fetchUnreadInboxSnapshot(forceRefresh: forceRefresh) |
| 707 | 712 | } catch { |
| 708 | 713 | return nil |
| 709 | 714 | } |
| 710 | 715 | } |
| 711 | 716 | |
| 712 | | private func loadSystemStatusSnapshot() async -> Result<CachedSystemStatusValue<SystemStatusSnapshot>, Error> { |
| 717 | private func loadSystemStatusSnapshot(forceRefresh: Bool) async -> Result<CachedSystemStatusValue<SystemStatusSnapshot>, Error> { |
| 713 | 718 | do { |
| 714 | 719 | return .success(try await systemStatusRepository.snapshotResult()) |
| 715 | 720 | } catch { |
| @@ -717,8 +722,8 @@ final class HomeViewModel { |
| 717 | 722 | } |
| 718 | 723 | } |
| 719 | 724 | |
| 720 | | private func fetchUnreadInboxSnapshot() async throws -> HomeInboxUnreadSnapshot { |
| 721 | | let mailingLists = try await fetchInboxMailingLists() |
| 725 | private func fetchUnreadInboxSnapshot(forceRefresh: Bool) async throws -> HomeInboxUnreadSnapshot { |
| 726 | let mailingLists = try await fetchInboxMailingLists(forceRefresh: forceRefresh) |
| 722 | 727 | guard !mailingLists.isEmpty else { return HomeInboxUnreadSnapshot(unreadCount: 0, threads: []) } |
| 723 | 728 | |
| 724 | 729 | var startIndex = mailingLists.startIndex |
| @@ -737,7 +742,7 @@ final class HomeViewModel { |
| 737 | 742 | for mailingList in batch { |
| 738 | 743 | group.addTask { |
| 739 | 744 | do { |
| 740 | | return .success(try await self.fetchUnreadThreadSnapshot(for: mailingList)) |
| 745 | return .success(try await self.fetchUnreadThreadSnapshot(for: mailingList, forceRefresh: forceRefresh)) |
| 741 | 746 | } catch { |
| 742 | 747 | return .failure(error) |
| 743 | 748 | } |
| @@ -776,7 +781,7 @@ final class HomeViewModel { |
| 776 | 781 | ) |
| 777 | 782 | } |
| 778 | 783 | |
| 779 | | private func fetchInboxMailingLists() async throws -> [InboxMailingListReference] { |
| 784 | private func fetchInboxMailingLists(forceRefresh: Bool) async throws -> [InboxMailingListReference] { |
| 780 | 785 | var subscriptions: [HomeInboxSubscription] = [] |
| 781 | 786 | var cursor: String? |
| 782 | 787 | |
| @@ -794,7 +799,7 @@ final class HomeViewModel { |
| 794 | 799 | cacheKey: APICacheKeys.inboxSubscriptions(cursor: cursor), |
| 795 | 800 | resourceType: .ticketList, |
| 796 | 801 | ttl: APICacheTTLs.inboxSummary, |
| 797 | | policy: .cacheFirstThenRefresh |
| 802 | policy: forceRefresh ? .refreshIgnoringCache : .cacheFirstThenRefresh |
| 798 | 803 | ) |
| 799 | 804 | let response = cached.value |
| 800 | 805 | |
| @@ -809,7 +814,7 @@ final class HomeViewModel { |
| 809 | 814 | return subscriptions.compactMap(\.list).filter { seen.insert($0.rid).inserted } |
| 810 | 815 | } |
| 811 | 816 | |
| 812 | | private func fetchUnreadThreadSnapshot(for mailingList: InboxMailingListReference) async throws -> HomeInboxUnreadSnapshot { |
| 817 | private func fetchUnreadThreadSnapshot(for mailingList: InboxMailingListReference, forceRefresh: Bool) async throws -> HomeInboxUnreadSnapshot { |
| 813 | 818 | var unreadCount = 0 |
| 814 | 819 | var cursor: String? |
| 815 | 820 | var unreadThreads: [InboxThreadSummary] = [] |
| @@ -828,7 +833,7 @@ final class HomeViewModel { |
| 828 | 833 | cacheKey: APICacheKeys.inboxThreads(listRid: mailingList.rid, cursor: cursor), |
| 829 | 834 | resourceType: .ticketList, |
| 830 | 835 | ttl: APICacheTTLs.inboxSummary, |
| 831 | | policy: .cacheFirstThenRefresh |
| 836 | policy: forceRefresh ? .refreshIgnoringCache : .cacheFirstThenRefresh |
| 832 | 837 | ) |
| 833 | 838 | let response = cached.value |
| 834 | 839 | |
| @@ -871,10 +876,10 @@ final class HomeViewModel { |
| 871 | 876 | ) |
| 872 | 877 | } |
| 873 | 878 | |
| 874 | | private func loadAssignedTickets() async -> Result<[HomeAssignedTicket], Error> { |
| 879 | private func loadAssignedTickets(forceRefresh: Bool) async -> Result<[HomeAssignedTicket], Error> { |
| 875 | 880 | do { |
| 876 | | let trackers = try await fetchAllTrackers() |
| 877 | | let tickets = try await fetchAssignedTickets(for: trackers) |
| 881 | let trackers = try await fetchAllTrackers(forceRefresh: forceRefresh) |
| 882 | let tickets = try await fetchAssignedTickets(for: trackers, forceRefresh: forceRefresh) |
| 878 | 883 | .sorted(by: Self.sortAssignedTicketsForTriage) |
| 879 | 884 | return .success(tickets) |
| 880 | 885 | } catch { |
| @@ -882,7 +887,7 @@ final class HomeViewModel { |
| 882 | 887 | } |
| 883 | 888 | } |
| 884 | 889 | |
| 885 | | private func fetchAllTrackers() async throws -> [TrackerSummary] { |
| 890 | private func fetchAllTrackers(forceRefresh: Bool) async throws -> [TrackerSummary] { |
| 886 | 891 | var allTrackers: [TrackerSummary] = [] |
| 887 | 892 | var cursor: String? |
| 888 | 893 | |
| @@ -900,7 +905,7 @@ final class HomeViewModel { |
| 900 | 905 | cacheKey: APICacheKeys.trackers(cursor: cursor), |
| 901 | 906 | resourceType: .ticketList, |
| 902 | 907 | ttl: APICacheTTLs.ticketList, |
| 903 | | policy: .cacheFirstThenRefresh |
| 908 | policy: forceRefresh ? .refreshIgnoringCache : .cacheFirstThenRefresh |
| 904 | 909 | ) |
| 905 | 910 | let response = cached.value |
| 906 | 911 | |
| @@ -914,7 +919,7 @@ final class HomeViewModel { |
| 914 | 919 | return allTrackers |
| 915 | 920 | } |
| 916 | 921 | |
| 917 | | private func fetchAssignedTickets(for trackers: [TrackerSummary]) async throws -> [HomeAssignedTicket] { |
| 922 | private func fetchAssignedTickets(for trackers: [TrackerSummary], forceRefresh: Bool) async throws -> [HomeAssignedTicket] { |
| 918 | 923 | guard !trackers.isEmpty else { return [] } |
| 919 | 924 | |
| 920 | 925 | var assignedTickets: [HomeAssignedTicket] = [] |
| @@ -927,7 +932,7 @@ final class HomeViewModel { |
| 927 | 932 | let batchTickets = try await withThrowingTaskGroup(of: [HomeAssignedTicket].self) { group in |
| 928 | 933 | for tracker in batch { |
| 929 | 934 | group.addTask { |
| 930 | | try await self.fetchAssignedTickets(for: tracker) |
| 935 | try await self.fetchAssignedTickets(for: tracker, forceRefresh: forceRefresh) |
| 931 | 936 | } |
| 932 | 937 | } |
| 933 | 938 | |
| @@ -945,7 +950,7 @@ final class HomeViewModel { |
| 945 | 950 | return assignedTickets |
| 946 | 951 | } |
| 947 | 952 | |
| 948 | | private func fetchAssignedTickets(for tracker: TrackerSummary) async throws -> [HomeAssignedTicket] { |
| 953 | private func fetchAssignedTickets(for tracker: TrackerSummary, forceRefresh: Bool) async throws -> [HomeAssignedTicket] { |
| 949 | 954 | let cached = try await client.executeCached( |
| 950 | 955 | service: .todo, |
| 951 | 956 | query: Self.trackerTicketsQuery, |
| @@ -964,7 +969,7 @@ final class HomeViewModel { |
| 964 | 969 | ), |
| 965 | 970 | resourceType: .ticketList, |
| 966 | 971 | ttl: APICacheTTLs.ticketList, |
| 967 | | policy: .cacheFirstThenRefresh |
| 972 | policy: forceRefresh ? .refreshIgnoringCache : .cacheFirstThenRefresh |
| 968 | 973 | ) |
| 969 | 974 | let response = cached.value |
| 970 | 975 | |