From d4d42e7856276e853bde32a22aca20b8f2e7f00a Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 10 May 2023 10:34:48 -0400 Subject: [PATCH] Report instance type/version in Sentry events --- .../InstanceFeatures/InstanceFeatures.swift | 18 ++++++---- Tusker/Preferences/StatusSwipeActions.swift | 6 ++-- ...equestNotificationCollectionViewCell.swift | 12 +++---- ...nishedNotificationCollectionViewCell.swift | 2 +- ...tatusActionAccountListViewController.swift | 4 +++ Tusker/Screens/Utilities/Previewing.swift | 6 ++-- ...TimelineLikeCollectionViewController.swift | 2 +- Tusker/TuskerNavigationDelegate.swift | 2 +- Tusker/Views/Poll/StatusPollView.swift | 8 ++--- .../Profile Header/ProfileHeaderView.swift | 6 ++-- .../Status/StatusCollectionViewCell.swift | 2 +- Tusker/Views/Toast/ToastConfiguration.swift | 33 ++++++++++++++++--- 12 files changed, 66 insertions(+), 35 deletions(-) diff --git a/Packages/InstanceFeatures/Sources/InstanceFeatures/InstanceFeatures.swift b/Packages/InstanceFeatures/Sources/InstanceFeatures/InstanceFeatures.swift index 48d0580d..cdbcf5ad 100644 --- a/Packages/InstanceFeatures/Sources/InstanceFeatures/InstanceFeatures.swift +++ b/Packages/InstanceFeatures/Sources/InstanceFeatures/InstanceFeatures.swift @@ -17,7 +17,7 @@ public class InstanceFeatures: ObservableObject { private let _featuresUpdated = PassthroughSubject() public var featuresUpdated: some Publisher { _featuresUpdated } - @Published private var instanceType: InstanceType = .mastodon(.vanilla, nil) + @Published @_spi(InstanceType) public private(set) var instanceType: InstanceType = .mastodon(.vanilla, nil) @Published public private(set) var maxStatusChars = 500 @Published public private(set) var charsReservedPerURL = 23 @Published public private(set) var maxPollOptionChars: Int? @@ -190,7 +190,7 @@ public class InstanceFeatures: ObservableObject { } extension InstanceFeatures { - enum InstanceType { + @_spi(InstanceType) public enum InstanceType { case mastodon(MastodonType, Version?) case pleroma(PleromaType) case pixelfed @@ -230,7 +230,7 @@ extension InstanceFeatures { } } - enum MastodonType { + @_spi(InstanceType) public enum MastodonType { case vanilla case hometown(Version?) case glitch @@ -249,7 +249,7 @@ extension InstanceFeatures { } } - enum PleromaType { + @_spi(InstanceType) public enum PleromaType { case vanilla(Version?) case akkoma(Version?) @@ -267,7 +267,7 @@ extension InstanceFeatures { } extension InstanceFeatures { - struct Version: Equatable, Comparable { + @_spi(InstanceType) public struct Version: Equatable, Comparable, CustomStringConvertible { private static let regex = try! NSRegularExpression(pattern: "^(\\d+)\\.(\\d+)\\.(\\d+).*$") let major: Int @@ -298,11 +298,15 @@ extension InstanceFeatures { self.patch = patch } - static func ==(lhs: Version, rhs: Version) -> Bool { + public var description: String { + "\(major).\(minor).\(patch)" + } + + public static func ==(lhs: Version, rhs: Version) -> Bool { return lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch == rhs.patch } - static func < (lhs: InstanceFeatures.Version, rhs: InstanceFeatures.Version) -> Bool { + public static func < (lhs: InstanceFeatures.Version, rhs: InstanceFeatures.Version) -> Bool { if lhs.major < rhs.major { return true } else if lhs.major > rhs.major { diff --git a/Tusker/Preferences/StatusSwipeActions.swift b/Tusker/Preferences/StatusSwipeActions.swift index 73655145..055c3c8d 100644 --- a/Tusker/Preferences/StatusSwipeActions.swift +++ b/Tusker/Preferences/StatusSwipeActions.swift @@ -114,10 +114,8 @@ private func createBookmarkAction(status: StatusMO, container: StatusSwipeAction let (status, _) = try await container.mastodonController.run(request) container.mastodonController.persistentContainer.addOrUpdate(status: status) } catch { - if let toastable = container.toastableViewController { - let config = ToastConfiguration(from: error, with: "Error \(bookmarked ? "Unb" : "B")ookmarking", in: toastable, retryAction: nil) - toastable.showToast(configuration: config, animated: true) - } + let config = ToastConfiguration(from: error, with: "Error \(bookmarked ? "Unb" : "B")ookmarking", in: container.navigationDelegate, retryAction: nil) + container.navigationDelegate.showToast(configuration: config, animated: true) } } } diff --git a/Tusker/Screens/Notifications/FollowRequestNotificationCollectionViewCell.swift b/Tusker/Screens/Notifications/FollowRequestNotificationCollectionViewCell.swift index 2b17f808..c98917ff 100644 --- a/Tusker/Screens/Notifications/FollowRequestNotificationCollectionViewCell.swift +++ b/Tusker/Screens/Notifications/FollowRequestNotificationCollectionViewCell.swift @@ -241,12 +241,12 @@ class FollowRequestNotificationCollectionViewCell: UICollectionViewListCell { } catch let error as Client.Error { acceptButton.isEnabled = true rejectButton.isEnabled = true - if let toastable = delegate?.toastableViewController { - let config = ToastConfiguration(from: error, with: "Rejecting Follow", in: toastable) { [weak self] toast in + if let delegate = delegate { + let config = ToastConfiguration(from: error, with: "Rejecting Follow", in: delegate) { [weak self] toast in toast.dismissToast(animated: true) self?.rejectButtonPressed() } - toastable.showToast(configuration: config, animated: true) + delegate.showToast(configuration: config, animated: true) } } } @@ -268,12 +268,12 @@ class FollowRequestNotificationCollectionViewCell: UICollectionViewListCell { acceptButton.isEnabled = true rejectButton.isEnabled = true - if let toastable = delegate?.toastableViewController { - let config = ToastConfiguration(from: error, with: "Accepting Follow", in: toastable) { [weak self] toast in + if let delegate = delegate { + let config = ToastConfiguration(from: error, with: "Accepting Follow", in: delegate) { [weak self] toast in toast.dismissToast(animated: true) self?.acceptButtonPressed() } - toastable.showToast(configuration: config, animated: true) + delegate.showToast(configuration: config, animated: true) } } } diff --git a/Tusker/Screens/Notifications/PollFinishedNotificationCollectionViewCell.swift b/Tusker/Screens/Notifications/PollFinishedNotificationCollectionViewCell.swift index e2ace13a..d33f62d2 100644 --- a/Tusker/Screens/Notifications/PollFinishedNotificationCollectionViewCell.swift +++ b/Tusker/Screens/Notifications/PollFinishedNotificationCollectionViewCell.swift @@ -132,7 +132,7 @@ class PollFinishedNotificationCollectionViewCell: UICollectionViewListCell { contentLabel.text = try! doc.text() pollView.mastodonController = mastodonController - pollView.toastableViewController = delegate + pollView.delegate = delegate pollView.updateUI(status: status, poll: poll) } diff --git a/Tusker/Screens/Status Action Account List/StatusActionAccountListViewController.swift b/Tusker/Screens/Status Action Account List/StatusActionAccountListViewController.swift index a1be1d32..191a77f3 100644 --- a/Tusker/Screens/Status Action Account List/StatusActionAccountListViewController.swift +++ b/Tusker/Screens/Status Action Account List/StatusActionAccountListViewController.swift @@ -193,3 +193,7 @@ extension StatusActionAccountListViewController: ToastableViewController { } } } + +extension StatusActionAccountListViewController: TuskerNavigationDelegate { + nonisolated var apiController: MastodonController! { mastodonController } +} diff --git a/Tusker/Screens/Utilities/Previewing.swift b/Tusker/Screens/Utilities/Previewing.swift index e10bed66..68d3f186 100644 --- a/Tusker/Screens/Utilities/Previewing.swift +++ b/Tusker/Screens/Utilities/Previewing.swift @@ -409,10 +409,10 @@ extension MenuActionProvider { } private func handleError(_ error: Client.Error, title: String) { - if let toastable = self.toastableViewController { - let config = ToastConfiguration(from: error, with: title, in: toastable, retryAction: nil) + if let navigationDelegate { + let config = ToastConfiguration(from: error, with: title, in: navigationDelegate, retryAction: nil) DispatchQueue.main.async { - toastable.showToast(configuration: config, animated: true) + navigationDelegate.showToast(configuration: config, animated: true) } } } diff --git a/Tusker/Screens/Utilities/TimelineLikeCollectionViewController.swift b/Tusker/Screens/Utilities/TimelineLikeCollectionViewController.swift index f7754539..a75892a6 100644 --- a/Tusker/Screens/Utilities/TimelineLikeCollectionViewController.swift +++ b/Tusker/Screens/Utilities/TimelineLikeCollectionViewController.swift @@ -10,7 +10,7 @@ import UIKit import Combine @MainActor -protocol TimelineLikeCollectionViewController: UIViewController, TimelineLikeControllerDelegate, ToastableViewController { +protocol TimelineLikeCollectionViewController: UIViewController, TimelineLikeControllerDelegate, TuskerNavigationDelegate { associatedtype Section: TimelineLikeCollectionViewSection associatedtype Item: TimelineLikeCollectionViewItem where Item.TimelineItem == Self.TimelineItem associatedtype Error: TimelineLikeCollectionViewError diff --git a/Tusker/TuskerNavigationDelegate.swift b/Tusker/TuskerNavigationDelegate.swift index b4b0d82f..fe1eabbc 100644 --- a/Tusker/TuskerNavigationDelegate.swift +++ b/Tusker/TuskerNavigationDelegate.swift @@ -13,7 +13,7 @@ import ComposeUI @MainActor protocol TuskerNavigationDelegate: UIViewController, ToastableViewController { - var apiController: MastodonController! { get } + nonisolated var apiController: MastodonController! { get } } extension TuskerNavigationDelegate { diff --git a/Tusker/Views/Poll/StatusPollView.swift b/Tusker/Views/Poll/StatusPollView.swift index 9f4309d0..4dffc23e 100644 --- a/Tusker/Views/Poll/StatusPollView.swift +++ b/Tusker/Views/Poll/StatusPollView.swift @@ -21,7 +21,7 @@ class StatusPollView: UIView { }() weak var mastodonController: MastodonController! - weak var toastableViewController: ToastableViewController? + weak var delegate: TuskerNavigationDelegate? private var statusID: String! private(set) var poll: Poll? @@ -158,9 +158,9 @@ class StatusPollView: UIView { DispatchQueue.main.async { self.updateUI(status: self.mastodonController.persistentContainer.status(for: self.statusID)!, poll: self.poll) - if let toastable = self.toastableViewController { - let config = ToastConfiguration(from: error, with: "Error Voting", in: toastable, retryAction: nil) - toastable.showToast(configuration: config, animated: true) + if let delegate = self.delegate { + let config = ToastConfiguration(from: error, with: "Error Voting", in: delegate, retryAction: nil) + delegate.showToast(configuration: config, animated: true) } } diff --git a/Tusker/Views/Profile Header/ProfileHeaderView.swift b/Tusker/Views/Profile Header/ProfileHeaderView.swift index 7ae2e1b7..922258ba 100644 --- a/Tusker/Views/Profile Header/ProfileHeaderView.swift +++ b/Tusker/Views/Profile Header/ProfileHeaderView.swift @@ -392,12 +392,12 @@ class ProfileHeaderView: UIView { } catch { followButton.isEnabled = true followButton.configuration!.showsActivityIndicator = false - if let toastable = delegate?.toastableViewController { - let config = ToastConfiguration(from: error, with: "Error \(action)", in: toastable) { toast in + if let delegate = self.delegate { + let config = ToastConfiguration(from: error, with: "Error \(action)", in: delegate) { toast in toast.dismissToast(animated: true) self.followPressed() } - toastable.showToast(configuration: config, animated: true) + delegate.showToast(configuration: config, animated: true) } } } diff --git a/Tusker/Views/Status/StatusCollectionViewCell.swift b/Tusker/Views/Status/StatusCollectionViewCell.swift index 4fb7483b..5647b0b0 100644 --- a/Tusker/Views/Status/StatusCollectionViewCell.swift +++ b/Tusker/Views/Status/StatusCollectionViewCell.swift @@ -213,7 +213,7 @@ extension StatusCollectionViewCell { contentContainer.pollView.isHidden = status.poll == nil contentContainer.pollView.mastodonController = mastodonController - contentContainer.pollView.toastableViewController = delegate?.toastableViewController + contentContainer.pollView.delegate = delegate contentContainer.pollView.updateUI(status: status, poll: status.poll) } diff --git a/Tusker/Views/Toast/ToastConfiguration.swift b/Tusker/Views/Toast/ToastConfiguration.swift index 3c1f69d8..d50852f3 100644 --- a/Tusker/Views/Toast/ToastConfiguration.swift +++ b/Tusker/Views/Toast/ToastConfiguration.swift @@ -10,6 +10,7 @@ import UIKit import Pachyderm import Sentry import OSLog +@_spi(InstanceType) import InstanceFeatures struct ToastConfiguration { var systemImageName: String? @@ -39,7 +40,7 @@ struct ToastConfiguration { } extension ToastConfiguration { - init(from error: Error, with title: String, in viewController: UIViewController, retryAction: ((ToastView) -> Void)?) { + init(from error: Error, with title: String, in viewController: TuskerNavigationDelegate, retryAction: ((ToastView) -> Void)?) { self.init(title: title) // localizedDescription is statically dispatched, so we need to call it after the downcast if let error = error as? Pachyderm.Client.Error { @@ -59,7 +60,7 @@ extension ToastConfiguration { viewController.present(reporter, animated: true) } // TODO: this is a bizarre place to do this, but code path covers basically all errors - captureError(error, title: title) + captureError(error, in: viewController.apiController, title: title) } else { self.subtitle = error.localizedDescription self.systemImageName = "exclamationmark.triangle" @@ -70,7 +71,7 @@ extension ToastConfiguration { } } - init(from error: Error, with title: String, in viewController: UIViewController, retryAction: @escaping @MainActor (ToastView) async -> Void) { + init(from error: Error, with title: String, in viewController: TuskerNavigationDelegate, retryAction: @escaping @MainActor (ToastView) async -> Void) { self.init(from: error, with: title, in: viewController) { toast in Task { await retryAction(toast) @@ -92,7 +93,7 @@ fileprivate extension Pachyderm.Client.Error { private let toastErrorLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ToastError") -private func captureError(_ error: Client.Error, title: String) { +private func captureError(_ error: Client.Error, in mastodonController: MastodonController, title: String) { let event = Event(error: error) event.message = SentryMessage(formatted: "\(title): \(error)") event.tags = [ @@ -125,6 +126,30 @@ private func captureError(_ error: Client.Error, title: String) { code == "401" || code == "403" || code == "404" || code == "502" { return } + switch mastodonController.instanceFeatures.instanceType { + case .mastodon(let mastodonType, let mastodonVersion): + event.tags!["instance_type"] = "mastodon" + event.tags!["mastodon_version"] = mastodonVersion?.description ?? "unknown" + switch mastodonType { + case .vanilla: + break + case .hometown(_): + event.tags!["mastodon_type"] = "hometown" + case .glitch: + event.tags!["mastodon_type"] = "glitch" + } + case .pleroma(let pleromaType): + event.tags!["instance_type"] = "pleroma" + switch pleromaType { + case .vanilla(let version): + event.tags!["pleroma_version"] = version?.description ?? "unknown" + case .akkoma(let version): + event.tags!["pleroma_type"] = "akkoma" + event.tags!["pleroma_version"] = version?.description ?? "unknown" + } + case .pixelfed: + event.tags!["instance_type"] = "pixelfed" + } SentrySDK.capture(event: event) toastErrorLogger.error("\(title, privacy: .public): \(error), \(event.tags!.debugDescription, privacy: .public)")