Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Apps can now manually show and hide the included feedback widget button (#5236)

## 8.50.2

### Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@ public enum SentrySDKOverrides {

public var boolValue: Bool {
get {
switch self {
case .disableAutoInject: return getBoolOverride(for: rawValue)
default: return getBoolOverride(for: rawValue)
}
return getBoolOverride(for: rawValue)
}
set(newValue) {
setBoolOverride(for: rawValue, value: newValue)
Expand Down
12 changes: 12 additions & 0 deletions Samples/iOS-Swift/iOS-Swift-UITests/UserFeedbackUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,8 @@ extension UserFeedbackUITests {
XCTAssertEqual(try dictionaryFromSuccessHookFile(), ["name": testName, "message": "UITest user feedback", "email": testContactEmail])
}

// MARK: Alternative widget control

func testFormShowsAndDismissesProperlyWithCustomButton() {
launchApp(args: ["--io.sentry.feedback.use-custom-feedback-button"])

Expand All @@ -512,6 +514,16 @@ extension UserFeedbackUITests {
XCTAssert(customButton.isHittable)
XCTAssertFalse(widgetButton.isHittable)
}

func testManuallyDisplayingWidget() {
launchApp(args: ["--io.sentry.feedback.no-auto-inject-widget"])
XCTAssertFalse(widgetButton.isHittable)
extrasAreaTabBarButton.tap()
app.buttons["io.sentry.ui-test.button.show-widget"].tap()
XCTAssert(widgetButton.isHittable)
app.buttons["io.sentry.ui-test.button.hide-widget"].tap()
XCTAssertFalse(widgetButton.isHittable)
}
}

// MARK: UI Element access
Expand Down
90 changes: 59 additions & 31 deletions Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions Samples/iOS-Swift/iOS-Swift/ExtraViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,20 @@ class ExtraViewController: UIViewController {
result["item_header_type"] = json["type"]
}
}

@IBAction func showFeedbackWidget(_ sender: Any) {
if #available(iOS 13.0, *) {
SentrySDK.feedback.showWidget()
} else {
showToast(in: self, type: .warning, message: "Feedback widget only available in iOS 13 or later.")
}
}

@IBAction func hideFeedbackWidget(_ sender: Any) {
if #available(iOS 13.0, *) {
SentrySDK.feedback.hideWidget()
} else {
showToast(in: self, type: .warning, message: "Feedback widget only available in iOS 13 or later.")
}
}
}
8 changes: 8 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,8 @@
845C16D52A622A5B00EC9519 /* SentryTracer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 845C16D42A622A5B00EC9519 /* SentryTracer+Private.h */; };
845CEAEF2D83F79500B6B325 /* SentryProfilingPublicAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845CEAEE2D83F79500B6B325 /* SentryProfilingPublicAPITests.swift */; };
845CEB172D8A979700B6B325 /* SentryAppStartProfilingConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845CEB162D8A979700B6B325 /* SentryAppStartProfilingConfigurationTests.swift */; };
8482FA9B2DD7C397000E9283 /* SentryFeedbackAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 8482FA992DD7C397000E9283 /* SentryFeedbackAPI.h */; settings = {ATTRIBUTES = (Public, ); }; };
8482FA9C2DD7C397000E9283 /* SentryFeedbackAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = 8482FA9A2DD7C397000E9283 /* SentryFeedbackAPI.m */; };
848A45192BBF8D33006AAAEC /* SentryContinuousProfiler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 848A45182BBF8D33006AAAEC /* SentryContinuousProfiler.mm */; };
848A451A2BBF8D33006AAAEC /* SentryContinuousProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 848A45172BBF8D33006AAAEC /* SentryContinuousProfiler.h */; };
848A451D2BBF9504006AAAEC /* SentryProfilerTestHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 848A451C2BBF9504006AAAEC /* SentryProfilerTestHelpers.m */; };
Expand Down Expand Up @@ -1864,6 +1866,8 @@
845CEAEE2D83F79500B6B325 /* SentryProfilingPublicAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryProfilingPublicAPITests.swift; sourceTree = "<group>"; };
845CEB162D8A979700B6B325 /* SentryAppStartProfilingConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryAppStartProfilingConfigurationTests.swift; sourceTree = "<group>"; };
846F90332D56F59D009E86C1 /* Brewfile-ci-deploy */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = "Brewfile-ci-deploy"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
8482FA992DD7C397000E9283 /* SentryFeedbackAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryFeedbackAPI.h; sourceTree = "<group>"; };
8482FA9A2DD7C397000E9283 /* SentryFeedbackAPI.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryFeedbackAPI.m; sourceTree = "<group>"; };
848A45172BBF8D33006AAAEC /* SentryContinuousProfiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryContinuousProfiler.h; path = ../include/SentryContinuousProfiler.h; sourceTree = "<group>"; };
848A45182BBF8D33006AAAEC /* SentryContinuousProfiler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryContinuousProfiler.mm; sourceTree = "<group>"; };
848A451B2BBF9504006AAAEC /* SentryProfilerTestHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryProfilerTestHelpers.h; path = ../include/SentryProfilerTestHelpers.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3770,6 +3774,8 @@
children = (
849B8F9E2C70091A00148E1F /* Configuration */,
849B8F962C6E906900148E1F /* SentryUserFeedbackIntegrationDriver.swift */,
8482FA992DD7C397000E9283 /* SentryFeedbackAPI.h */,
8482FA9A2DD7C397000E9283 /* SentryFeedbackAPI.m */,
84DBC62B2CE82F0E000C4904 /* SentryFeedback.swift */,
84CFA4CB2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h */,
84CFA4CC2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m */,
Expand Down Expand Up @@ -4348,6 +4354,7 @@
0A9BF4E428A114B50068D266 /* SentryViewHierarchyIntegration.h in Headers */,
D8BBD32728FD9FC00011F850 /* SentrySwift.h in Headers */,
84CFA4CE2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h in Headers */,
8482FA9B2DD7C397000E9283 /* SentryFeedbackAPI.h in Headers */,
8E4E7C7425DAAB49006AB9E2 /* SentrySpanProtocol.h in Headers */,
8EC4CF4A25C38DAA0093DEE9 /* SentrySpanStatus.h in Headers */,
8ECC673D25C23996000E2BF6 /* SentrySpanId.h in Headers */,
Expand Down Expand Up @@ -5260,6 +5267,7 @@
63FE710B20DA4C1000CDBAE8 /* SentryCrashMach.c in Sources */,
63FE707720DA4C1000CDBAE8 /* SentryDictionaryDeepSearch.m in Sources */,
621D9F2F2B9B0320003D94DE /* SentryCurrentDateProvider.swift in Sources */,
8482FA9C2DD7C397000E9283 /* SentryFeedbackAPI.m in Sources */,
7BC9A20628F41781001E7C4C /* SentryMeasurementUnit.m in Sources */,
63FE71A020DA4C1100CDBAE8 /* SentryCrashInstallation.m in Sources */,
63FE713520DA4C1100CDBAE8 /* SentryCrashMemory.c in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Sources/Sentry/Public/Sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ FOUNDATION_EXPORT const unsigned char SentryVersionString[];
# import <Sentry/SentryError.h>
# import <Sentry/SentryEvent.h>
# import <Sentry/SentryException.h>
# import <Sentry/SentryFeedbackAPI.h>
# import <Sentry/SentryFrame.h>
# import <Sentry/SentryGeo.h>
# import <Sentry/SentryHttpStatusCodeRange.h>
Expand Down
1 change: 1 addition & 0 deletions Sources/Sentry/Public/SentryOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@ typedef void (^SentryProfilingConfigurationBlock)(SentryProfileOptions *_Nonnull
* either this block to configure a widget and UI form to gather feedback, or directly submits
* feedback you've gathered using your own UI by calling the method @c SentrySDK.captureFeedback
* (se https://docs.sentry.io/platforms/apple/user-feedback/configuration/).
* @note User feedback widget is only available for iOS 13 or later.
*/
@property (nonatomic, copy, nullable)
SentryUserFeedbackConfigurationBlock configureUserFeedback API_AVAILABLE(ios(13.0));
Expand Down
8 changes: 8 additions & 0 deletions Sources/Sentry/Public/SentrySDK.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
@class SentryBreadcrumb;
@class SentryEvent;
@class SentryFeedback;
@class SentryFeedbackAPI;
@class SentryId;
@class SentryMetricsAPI;
@class SentryOptions;
Expand Down Expand Up @@ -263,13 +264,20 @@ SENTRY_NO_INIT

/**
* Captures user feedback that was manually gathered and sends it to Sentry.
* @warning This is an experimental feature and may still have bugs.
* @param feedback The feedback to send to Sentry.
* @note If you'd prefer not to have to build the UI required to gather the feedback from the user,
* see @c SentryOptions.configureUserFeedback to customize a fully managed integration. See
* https://docs.sentry.io/platforms/apple/user-feedback/ for more information.
*/
+ (void)captureFeedback:(SentryFeedback *)feedback NS_SWIFT_NAME(capture(feedback:));

#if TARGET_OS_IOS && SENTRY_HAS_UIKIT

@property (nonatomic, class, readonly) SentryFeedbackAPI *feedback API_AVAILABLE(ios(13.0));

#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT

/**
* Adds a Breadcrumb to the current Scope of the current Hub. If the total number of breadcrumbs
* exceeds the @c SentryOptions.maxBreadcrumbs the SDK removes the oldest breadcrumb.
Expand Down
16 changes: 16 additions & 0 deletions Sources/Sentry/SentrySDK.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#import "SentrySerialization.h"
#import "SentrySwift.h"
#import "SentryTransactionContext.h"
#import "SentryUserFeedbackIntegration.h"

#if TARGET_OS_OSX
# import "SentryCrashExceptionApplication.h"
Expand All @@ -34,6 +35,9 @@
#if SENTRY_HAS_UIKIT
# import "SentryUIDeviceWrapper.h"
# import "SentryUIViewControllerPerformanceTracker.h"
# if TARGET_OS_IOS
# import "SentryFeedbackAPI.h"
# endif // TARGET_OS_IOS
#endif // SENTRY_HAS_UIKIT

#if SENTRY_TARGET_PROFILING_SUPPORTED
Expand Down Expand Up @@ -431,6 +435,18 @@
[SentrySDK.currentHub captureFeedback:feedback];
}

#if TARGET_OS_IOS && SENTRY_HAS_UIKIT

+ (SentryFeedbackAPI *)feedback
{
static SentryFeedbackAPI *feedbackAPI;
static dispatch_once_t onceToken;

Check warning on line 443 in Sources/Sentry/SentrySDK.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentrySDK.m#L442-L443

Added lines #L442 - L443 were not covered by tests
dispatch_once(&onceToken, ^{ feedbackAPI = [[SentryFeedbackAPI alloc] init]; });
return feedbackAPI;
}

Check warning on line 446 in Sources/Sentry/SentrySDK.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentrySDK.m#L445-L446

Added lines #L445 - L446 were not covered by tests

#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT

+ (void)addBreadcrumb:(SentryBreadcrumb *)crumb
{
[SentrySDK.currentHub addBreadcrumb:crumb];
Expand Down
10 changes: 10 additions & 0 deletions Sources/Sentry/SentryUserFeedbackIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@
return YES;
}

- (void)showWidget
{
[_driver showWidget];
}

Check warning on line 33 in Sources/Sentry/SentryUserFeedbackIntegration.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryUserFeedbackIntegration.m#L32-L33

Added lines #L32 - L33 were not covered by tests

- (void)hideWidget
{
[_driver hideWidget];
}

Check warning on line 38 in Sources/Sentry/SentryUserFeedbackIntegration.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryUserFeedbackIntegration.m#L37-L38

Added lines #L37 - L38 were not covered by tests

// MARK: SentryUserFeedbackIntegrationDriverDelegate

- (void)captureWithFeedback:(SentryFeedback *)feedback
Expand Down
3 changes: 2 additions & 1 deletion Sources/Sentry/include/SentryUserFeedbackIntegration.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ NS_ASSUME_NONNULL_BEGIN

API_AVAILABLE(ios(13.0))
@interface SentryUserFeedbackIntegration : SentryBaseIntegration

- (void)showWidget;
- (void)hideWidget;
@end

NS_ASSUME_NONNULL_END
Expand Down
36 changes: 36 additions & 0 deletions Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#if __has_include(<Sentry/SentryDefines.h>)
# import <Sentry/SentryDefines.h>
#else
# import <SentryWithoutUIKit/SentryDefines.h>
#endif

#if TARGET_OS_IOS && SENTRY_HAS_UIKIT

# import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

API_AVAILABLE(ios(13.0))
@interface SentryFeedbackAPI : NSObject

/**
* Show the feedback widget button.
* @warning This is an experimental feature and may still have bugs.
* @seealso See @c SentryOptions.configureUserFeedback to configure the widget.
* @note User feedback widget is only available for iOS 13 or later.
*/
- (void)showWidget API_AVAILABLE(ios(13.0));

/**
* Hide the feedback widget button.
* @warning This is an experimental feature and may still have bugs.
* @seealso See @c SentryOptions.configureUserFeedback to configure the widget.
* @note User feedback widget is only available for iOS 13 or later.
*/
- (void)hideWidget API_AVAILABLE(ios(13.0));

@end

NS_ASSUME_NONNULL_END

#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT
41 changes: 41 additions & 0 deletions Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#if __has_include(<Sentry/SentryDefines.h>)
# import <Sentry/SentryDefines.h>
#else
# import <SentryWithoutUIKit/SentryDefines.h>
#endif

#if TARGET_OS_IOS && SENTRY_HAS_UIKIT

# import "SentryFeedbackAPI.h"
# import "SentryHub+Private.h"
# import "SentryLog.h"
# import "SentrySDK+Private.h"
# import "SentryUserFeedbackIntegration.h"

@implementation SentryFeedbackAPI

- (void)showWidget
{
if (@available(iOS 13.0, *)) {
SentryUserFeedbackIntegration *feedback =
[[SentrySDK currentHub] getInstalledIntegration:[SentryUserFeedbackIntegration class]];
[feedback showWidget];

Check warning on line 22 in Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.m

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.m#L20-L22

Added lines #L20 - L22 were not covered by tests
} else {
SENTRY_LOG_WARN(@"Sentry User Feedback is only available on iOS 13 or later.");
}
}

Check warning on line 26 in Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.m

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.m#L25-L26

Added lines #L25 - L26 were not covered by tests

- (void)hideWidget
{
if (@available(iOS 13.0, *)) {
SentryUserFeedbackIntegration *feedback =
[SentrySDK.currentHub getInstalledIntegration:[SentryUserFeedbackIntegration class]];
[feedback hideWidget];

Check warning on line 33 in Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.m

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.m#L31-L33

Added lines #L31 - L33 were not covered by tests
} else {
SENTRY_LOG_WARN(@"Sentry User Feedback is only available on iOS 13 or later.");
}
}

Check warning on line 37 in Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.m

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.m#L36-L37

Added lines #L36 - L37 were not covered by tests

@end

#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@
customButton?.removeTarget(self, action: #selector(showForm(sender:)), for: .touchUpInside)
}

@objc public func showWidget() {
if widget == nil {
widget = SentryUserFeedbackWidget(config: configuration, delegate: self)

Check warning on line 59 in Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift#L59

Added line #L59 was not covered by tests
} else {
widget?.rootVC.setWidget(visible: true, animated: configuration.animations)

Check warning on line 61 in Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift#L61

Added line #L61 was not covered by tests
}
}

Check warning on line 63 in Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift#L63

Added line #L63 was not covered by tests

@objc public func hideWidget() {
widget?.rootVC.setWidget(visible: false, animated: configuration.animations)
}

Check warning on line 67 in Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift#L66-L67

Added lines #L66 - L67 were not covered by tests

@objc func showForm(sender: UIButton) {
presenter?.present(SentryUserFeedbackFormController(config: configuration, delegate: self, screenshot: nil), animated: configuration.animations) {
self.configuration.onFormOpen?()
Expand All @@ -72,6 +84,7 @@
self.configuration.onFormClose?()
}
widget?.rootVC.setWidget(visible: true, animated: configuration.animations)
displayingForm = false

Check warning on line 87 in Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift#L87

Added line #L87 was not covered by tests
}
}

Expand All @@ -88,6 +101,7 @@
extension SentryUserFeedbackIntegrationDriver: UIAdaptivePresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
widget?.rootVC.setWidget(visible: true, animated: configuration.animations)
displayingForm = false

Check warning on line 104 in Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift#L104

Added line #L104 was not covered by tests
configuration.onFormClose?()
}
}
Expand All @@ -99,6 +113,7 @@
let form = SentryUserFeedbackFormController(config: configuration, delegate: self, screenshot: screenshot)
form.presentationController?.delegate = self
widget?.rootVC.setWidget(visible: false, animated: configuration.animations)
displayingForm = true

Check warning on line 116 in Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift#L116

Added line #L116 was not covered by tests
presenter?.present(form, animated: configuration.animations) {
self.configuration.onFormOpen?()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,6 @@ class SentryUserFeedbackWidget {
} else {
button?.isHidden = !visible
}

displayingForm = !visible
}
}
}
Expand Down
Loading