Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(feedback): capture envelopes #4535

Merged
merged 29 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
97ab954
prefill user info
armcknight Dec 12, 2024
8a9fe49
fix build and add ui test
armcknight Dec 12, 2024
fc35119
dont assert nil value bc it uses the placeholder string when empty
armcknight Dec 13, 2024
a2764d8
default user.name in addition to user.username
armcknight Dec 13, 2024
237f9b4
these should not have been in the public SentrySDK header
armcknight Dec 13, 2024
71f473a
capture envelopes
armcknight Nov 15, 2024
9220f58
just send from the form, no need for delegation
armcknight Nov 15, 2024
f66ad3b
put back integration objc
armcknight Nov 15, 2024
bc430c5
add new SentryFeedback interface and thread callpath to capture in en…
armcknight Nov 16, 2024
b78655c
fix headerdoc in SentrySDK.h; use new SDK capture method in form subm…
armcknight Nov 16, 2024
ec031c4
Format code
getsentry-bot Nov 16, 2024
0ac677a
pr feedback and updates
armcknight Nov 19, 2024
de56160
remove hints; ordering in init
armcknight Nov 20, 2024
c128d60
fix bad conflict resolution
armcknight Dec 5, 2024
9dc92aa
refactor method to reuse for grabbing attachments for an event
armcknight Dec 12, 2024
1299f1a
wip implementing new feedback envelope capture
armcknight Dec 12, 2024
00ef3dd
necessary tweaks to events/envelopes to get feedback events ingested …
armcknight Dec 13, 2024
5cb9ada
remove some comments to pr/issue
armcknight Dec 13, 2024
4dcee7a
ios-only build requirements needed some places, but actually not for …
armcknight Dec 13, 2024
64ab660
add serialization test
armcknight Dec 13, 2024
df7f177
pr feedback
armcknight Dec 18, 2024
5d1ff6e
wip: intercepting cached envelopes to validate in UI tests
armcknight Dec 18, 2024
f593556
Merge remote-tracking branch 'origin/main' into armcknight/feat(user-…
armcknight Dec 18, 2024
623673c
moving to other branch
armcknight Dec 19, 2024
4b88e4b
remove TestStoreOnlyTransport
armcknight Dec 19, 2024
0c35b26
Merge remote-tracking branch 'origin/main' into armcknight/feat(user-…
armcknight Dec 20, 2024
d5923cf
Update Sentry.xcodeproj/project.pbxproj
armcknight Dec 20, 2024
1e4f7a3
fix pbxproj issue
armcknight Dec 20, 2024
ded938e
test(feedback): validate envelopes (#4652)
armcknight Dec 24, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ class UserFeedbackUITests: BaseUITest {
XCTAssertEqual(try XCTUnwrap(emailField.value as? String), "[email protected]")

XCTAssertEqual(try XCTUnwrap(messageTextView.value as? String), "", "The UITextView shouldn't have any initial text functioning as a placeholder; as UITextView has no placeholder property, the \"placeholder\" is a label on top of it.")

// TODO: go to Extras view
app.buttons["io.sentry.ui-test.button.get-feedback-envelope"].tap()
// TODO: pull contents out of text field
// TODO: validate contents
}

func testSubmitWithOnlyRequiredFieldsFilled() {
Expand Down
42 changes: 26 additions & 16 deletions Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions Samples/iOS-Swift/iOS-Swift/ExtraViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,31 @@ class ExtraViewController: UIViewController {

return pi
}

// copied from ProfilingViewController.withProfile
@IBAction func getAllEnvelopes(_ sender: Any) {
let cachesDirectory = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!
let fm = FileManager.default
let dir = "\(cachesDirectory)/io.sentry/profiles"
let count = try! fm.contentsOfDirectory(atPath: dir).count
//swiftlint:disable empty_count
// guard first || count > 0 else {
// //swiftlint:enable empty_count
// profilingUITestDataMarshalingTextField.text = "<missing>"
// return
// }
// let fileName = "profile\(first ? 0 : count - 1)"
// let fullPath = "\(dir)/\(fileName)"
//
// if fm.fileExists(atPath: fullPath) {
// let url = NSURL.fileURL(withPath: fullPath)
// block(url)
// do {
// try fm.removeItem(atPath: fullPath)
// } catch {
// SentrySDK.capture(error: error)
// }
// return
// }
}
}
14 changes: 13 additions & 1 deletion Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objectVersion = 70;
armcknight marked this conversation as resolved.
Show resolved Hide resolved
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -714,6 +714,7 @@
84CFA4CA2C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CFA4C92C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift */; };
84CFA4CD2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 84CFA4CC2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m */; };
84CFA4CE2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 84CFA4CB2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h */; };
84DBC62C2CE82F12000C4904 /* SentryFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DBC62B2CE82F0E000C4904 /* SentryFeedback.swift */; };
84DEE86B2B686BD400A7BC17 /* SentrySamplerDecision.h in Headers */ = {isa = PBXBuildFile; fileRef = 84DEE86A2B686BD400A7BC17 /* SentrySamplerDecision.h */; };
84DEE8762B69AD6400A7BC17 /* SentryLaunchProfiling.h in Headers */ = {isa = PBXBuildFile; fileRef = 84DEE8752B69AD6400A7BC17 /* SentryLaunchProfiling.h */; };
84E13B842CBF1D91003B52EC /* SentryUserFeedbackWidgetButtonMegaphoneIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E13B832CBF1D91003B52EC /* SentryUserFeedbackWidgetButtonMegaphoneIconView.swift */; };
Expand Down Expand Up @@ -1789,6 +1790,7 @@
84CFA4C92C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUserFeedbackWidget.swift; sourceTree = "<group>"; };
84CFA4CB2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryUserFeedbackIntegration.h; path = ../../../Sentry/include/SentryUserFeedbackIntegration.h; sourceTree = "<group>"; };
84CFA4CC2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SentryUserFeedbackIntegration.m; path = ../../../Sentry/SentryUserFeedbackIntegration.m; sourceTree = "<group>"; };
84DBC62B2CE82F0E000C4904 /* SentryFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFeedback.swift; sourceTree = "<group>"; };
84DEE86A2B686BD400A7BC17 /* SentrySamplerDecision.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySamplerDecision.h; path = include/SentrySamplerDecision.h; sourceTree = "<group>"; };
84DEE8752B69AD6400A7BC17 /* SentryLaunchProfiling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryLaunchProfiling.h; path = Sources/Sentry/include/SentryLaunchProfiling.h; sourceTree = SOURCE_ROOT; };
84E13B832CBF1D91003B52EC /* SentryUserFeedbackWidgetButtonMegaphoneIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUserFeedbackWidgetButtonMegaphoneIconView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2046,6 +2048,10 @@
D8FFE50B2703DAAE00607131 /* SwizzlingCallTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwizzlingCallTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
843FB3142D0BDA3900558F18 /* Feedback */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Feedback; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXFrameworksBuildPhase section */
63AA75971EB8AEF500D153DE /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
Expand Down Expand Up @@ -2977,6 +2983,7 @@
7B944FA924697E9700A10721 /* Integrations */ = {
isa = PBXGroup;
children = (
843FB3142D0BDA3900558F18 /* Feedback */,
7BF6505D292B77D100BBA5A8 /* MetricKit */,
D808FB85281AB2EF009A2A33 /* UIEvents */,
D8AB40D92806EBDC00E5E9F7 /* Screenshot */,
Expand Down Expand Up @@ -3542,6 +3549,7 @@
children = (
849B8F9E2C70091A00148E1F /* Configuration */,
849B8F962C6E906900148E1F /* SentryUserFeedbackIntegrationDriver.swift */,
84DBC62B2CE82F0E000C4904 /* SentryFeedback.swift */,
84CFA4CB2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h */,
84CFA4CC2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m */,
84CFA4C92C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift */,
Expand Down Expand Up @@ -4373,6 +4381,9 @@
D84DAD5C2B1742C1003CF120 /* PBXTargetDependency */,
D85153032CA2B5F60070F669 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
843FB3142D0BDA3900558F18 /* Feedback */,
);
name = SentryTests;
packageProductDependencies = (
);
Expand Down Expand Up @@ -4589,6 +4600,7 @@
7B6438AB26A70F24000D0F65 /* UIViewController+Sentry.m in Sources */,
84302A812B5767A50027A629 /* SentryLaunchProfiling.m in Sources */,
63AA76A31EB9CBAA00D153DE /* SentryDsn.m in Sources */,
84DBC62C2CE82F12000C4904 /* SentryFeedback.swift in Sources */,
63B818FA1EC34639002FDF4C /* SentryDebugMeta.m in Sources */,
7B98D7D325FB65AE00C5A389 /* SentryWatchdogTerminationTracker.m in Sources */,
8E564AE8267AF22600FE117D /* SentryNetworkTrackingIntegration.m in Sources */,
Expand Down
4 changes: 0 additions & 4 deletions SentryTestUtils/TestTransportAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ import _SentryPrivate
import Foundation

public class TestTransportAdapter: SentryTransportAdapter {
public override func send(_ event: Event, session: SentrySession, attachments: [Attachment]) {
self.send(event, with: session, traceContext: nil, attachments: attachments)
}

public var sentEventsWithSessionTraceState = Invocations<(event: Event, session: SentrySession, traceContext: TraceContext?, attachments: [Attachment])>()
public override func send(_ event: Event, with session: SentrySession, traceContext: TraceContext?, attachments: [Attachment]) {
sentEventsWithSessionTraceState.record((event, session, traceContext, attachments))
Expand Down
1 change: 1 addition & 0 deletions Sources/Sentry/Public/SentrySDK.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

@class SentryBreadcrumb;
@class SentryEvent;
@class SentryFeedback;
@class SentryId;
@class SentryMetricsAPI;
@class SentryOptions;
Expand Down
67 changes: 51 additions & 16 deletions Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -458,13 +458,7 @@ - (SentryId *)sendEvent:(SentryEvent *)event

SentryTraceContext *traceContext = [self getTraceStateWithEvent:event withScope:scope];

NSArray *attachments = scope.attachments;
if (self.attachmentProcessors.count) {
for (id<SentryClientAttachmentProcessor> attachmentProcessor in self.attachmentProcessors) {
attachments = [attachmentProcessor processAttachments:attachments
forEvent:preparedEvent];
}
}
NSArray *attachments = [self attachmentsForEvent:preparedEvent scope:scope];

[self.transportAdapter sendEvent:preparedEvent
traceContext:traceContext
Expand All @@ -474,18 +468,23 @@ - (SentryId *)sendEvent:(SentryEvent *)event
return preparedEvent.eventId;
}

- (NSArray *)attachmentsForEvent:(SentryEvent *)event scope:(SentryScope *)scope
armcknight marked this conversation as resolved.
Show resolved Hide resolved
{
NSArray *attachments = scope.attachments;
if (self.attachmentProcessors.count) {
for (id<SentryClientAttachmentProcessor> attachmentProcessor in self.attachmentProcessors) {
attachments = [attachmentProcessor processAttachments:attachments forEvent:event];
}
}
return attachments;
}

- (SentryId *)sendEvent:(SentryEvent *)event
withSession:(SentrySession *)session
withScope:(SentryScope *)scope
{
if (nil != event) {
NSArray *attachments = scope.attachments;
if (self.attachmentProcessors.count) {
for (id<SentryClientAttachmentProcessor> attachmentProcessor in self
.attachmentProcessors) {
attachments = [attachmentProcessor processAttachments:attachments forEvent:event];
}
}
NSArray *attachments = [self attachmentsForEvent:event scope:scope];

if (event.isCrashEvent && event.context[@"replay"] &&
[event.context[@"replay"] isKindOfClass:NSDictionary.class]) {
Expand Down Expand Up @@ -586,6 +585,39 @@ - (void)captureUserFeedback:(SentryUserFeedback *)userFeedback
[self.transportAdapter sendUserFeedback:userFeedback];
}

- (void)captureFeedback:(SentryFeedback *)feedback withScope:(SentryScope *)scope
armcknight marked this conversation as resolved.
Show resolved Hide resolved
{
SentryEvent *feedbackEvent = [[SentryEvent alloc] init];
feedbackEvent.eventId = feedback.eventId;
feedbackEvent.type = SentryEnvelopeItemTypeFeedback;

NSDictionary *serializedFeedback = [feedback serialize];

NSUInteger optionalItems = (scope.span == nil ? 0 : 1) + (scope.replayId == nil ? 0 : 1);
NSMutableDictionary *context = [NSMutableDictionary dictionaryWithCapacity:1 + optionalItems];
context[@"feedback"] = serializedFeedback;

if (scope.replayId != nil) {
NSMutableDictionary *replayContext = [NSMutableDictionary dictionaryWithCapacity:1];
replayContext[@"replay_id"] = scope.replayId;
context[@"replay"] = replayContext;
}

feedbackEvent.context = context;

SentryEvent *preparedEvent = [self prepareEvent:feedbackEvent
withScope:scope
alwaysAttachStacktrace:NO];
SentryTraceContext *traceContext = [self getTraceStateWithEvent:preparedEvent withScope:scope];
NSArray *attachments = [[self attachmentsForEvent:preparedEvent scope:scope]
arrayByAddingObjectsFromArray:[feedback attachments]];

[self.transportAdapter sendEvent:preparedEvent
traceContext:traceContext
attachments:attachments
additionalEnvelopeItems:@[]];
}

- (void)storeEnvelope:(SentryEnvelope *)envelope
{
[self.fileManager storeEnvelope:envelope];
Expand Down Expand Up @@ -644,6 +676,8 @@ - (SentryEvent *_Nullable)prepareEvent:(SentryEvent *)event
= event.type == nil || ![event.type isEqualToString:SentryEnvelopeItemTypeTransaction];
BOOL eventIsNotReplay
= event.type == nil || ![event.type isEqualToString:SentryEnvelopeItemTypeReplayVideo];
BOOL eventIsNotUserFeedback
= event.type == nil || ![event.type isEqualToString:SentryEnvelopeItemTypeFeedback];

// Transactions and replays have their own sampleRate
if (eventIsNotATransaction && eventIsNotReplay && [self isSampled:self.options.sampleRate]) {
Expand Down Expand Up @@ -673,8 +707,9 @@ - (SentryEvent *_Nullable)prepareEvent:(SentryEvent *)event

[self setSdk:event];

// We don't want to attach debug meta and stacktraces for transactions and replays.
if (eventIsNotATransaction && eventIsNotReplay) {
// We don't want to attach debug meta and stacktraces for transactions, replays or user
// feedback.
if (eventIsNotATransaction && eventIsNotReplay && eventIsNotUserFeedback) {
BOOL shouldAttachStacktrace = alwaysAttachStacktrace || self.options.attachStacktrace
|| (nil != event.exceptions && [event.exceptions count] > 0);

Expand Down
20 changes: 14 additions & 6 deletions Sources/Sentry/SentryDataCategoryMapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
NSString *const kSentryDataCategoryNameReplay = @"replay";
NSString *const kSentryDataCategoryNameMetricBucket = @"metric_bucket";
NSString *const kSentryDataCategoryNameSpan = @"span";
NSString *const kSentryDataCategoryNameFeedback = @"feedback";
armcknight marked this conversation as resolved.
Show resolved Hide resolved
NSString *const kSentryDataCategoryNameUnknown = @"unknown";

NS_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -46,6 +47,9 @@
if ([itemType isEqualToString:SentryEnvelopeItemTypeReplayVideo]) {
return kSentryDataCategoryReplay;
}
if ([itemType isEqualToString:SentryEnvelopeItemTypeFeedback]) {
return kSentryDataCategoryFeedback;
}
// The envelope item type used for metrics is statsd whereas the client report category for
// discarded events is metric_bucket.
if ([itemType isEqualToString:SentryEnvelopeItemTypeStatsd]) {
Expand Down Expand Up @@ -104,20 +108,20 @@
if ([value isEqualToString:kSentryDataCategoryNameSpan]) {
return kSentryDataCategorySpan;
}
if ([value isEqualToString:kSentryDataCategoryNameFeedback]) {
return kSentryDataCategoryFeedback;
}

return kSentryDataCategoryUnknown;
}

NSString *
nameForSentryDataCategory(SentryDataCategory category)
{
if (category < kSentryDataCategoryAll && category > kSentryDataCategoryUnknown) {
return kSentryDataCategoryNameUnknown;
}

switch (category) {
case kSentryDataCategoryAll:
return kSentryDataCategoryNameAll;

case kSentryDataCategoryDefault:
return kSentryDataCategoryNameDefault;
case kSentryDataCategoryError:
Expand All @@ -136,12 +140,16 @@
return kSentryDataCategoryNameProfileChunk;
case kSentryDataCategoryMetricBucket:
return kSentryDataCategoryNameMetricBucket;
case kSentryDataCategoryUnknown:
return kSentryDataCategoryNameUnknown;
case kSentryDataCategoryReplay:
return kSentryDataCategoryNameReplay;
case kSentryDataCategorySpan:
return kSentryDataCategoryNameSpan;
case kSentryDataCategoryFeedback:
return kSentryDataCategoryNameFeedback;

default: // !!!: fall-through!
case kSentryDataCategoryUnknown:
return kSentryDataCategoryNameUnknown;
}
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/Sentry/SentryEnvelope.m
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ - (instancetype)initWithEvent:(SentryEvent *)event
// default. In any case in the envelope type it should be event. Except for transactions
NSString *envelopeType = [event.type isEqualToString:SentryEnvelopeItemTypeTransaction]
? SentryEnvelopeItemTypeTransaction
: [event.type isEqualToString:SentryEnvelopeItemTypeFeedback]
armcknight marked this conversation as resolved.
Show resolved Hide resolved
? SentryEnvelopeItemTypeFeedback
: SentryEnvelopeItemTypeEvent;

return [self initWithHeader:[[SentryEnvelopeItemHeader alloc] initWithType:envelopeType
Expand Down
8 changes: 8 additions & 0 deletions Sources/Sentry/SentryHub.m
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,14 @@ - (void)captureUserFeedback:(SentryUserFeedback *)userFeedback
}
}

- (void)captureFeedback:(SentryFeedback *)feedback
{
SentryClient *client = _client;
if (client != nil) {
[client captureFeedback:feedback withScope:self.scope];
}
}

- (void)addBreadcrumb:(SentryBreadcrumb *)crumb
{
SentryOptions *options = [[self client] options];
Expand Down
9 changes: 9 additions & 0 deletions Sources/Sentry/SentryQueueableRequestManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ - (instancetype)initWithSession:(NSURLSession *)session

- (BOOL)isReady
{
#if TEST || TESTCI
armcknight marked this conversation as resolved.
Show resolved Hide resolved
// force every envelope to be cached in UI tests so we can inspect what the SDK would've sent
// for a given operation
if ([NSProcessInfo.processInfo.environment[@"--io.sentry.sdk-environment"]
isEqualToString:@"ui-tests"]) {
return NO;
}
#endif // TEST || TESTCI

// We always have at least one operation in the queue when calling this
return self.queue.operationCount <= 1;
}
Expand Down
7 changes: 7 additions & 0 deletions Sources/Sentry/SentrySDK.m
Original file line number Diff line number Diff line change
Expand Up @@ -405,10 +405,17 @@ + (void)captureUserFeedback:(SentryUserFeedback *)userFeedback
[SentrySDK.currentHub captureUserFeedback:userFeedback];
}

+ (void)captureFeedback:(SentryFeedback *)feedback
{
[SentrySDK.currentHub captureFeedback:feedback];
}

#if TARGET_OS_IOS && SENTRY_HAS_UIKIT
+ (void)showUserFeedbackForm
{
// TODO: implement
}
#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT

+ (void)addBreadcrumb:(SentryBreadcrumb *)crumb
{
Expand Down
9 changes: 5 additions & 4 deletions Sources/Sentry/include/HybridPublic/SentryEnvelope.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@

#endif

@class SentryEvent;
@class SentrySession;
@class SentryId;
@class SentryUserFeedback;
@class SentryAttachment;
@class SentryEnvelopeItemHeader;
@class SentryEvent;
@class SentryFeedback;
@class SentryId;
@class SentrySession;
@class SentryTraceContext;
@class SentryUserFeedback;
armcknight marked this conversation as resolved.
Show resolved Hide resolved

NS_ASSUME_NONNULL_BEGIN

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
static NSString *const SentryEnvelopeItemTypeEvent = @"event";
static NSString *const SentryEnvelopeItemTypeSession = @"session";
static NSString *const SentryEnvelopeItemTypeUserFeedback = @"user_report";
static NSString *const SentryEnvelopeItemTypeFeedback = @"feedback";
armcknight marked this conversation as resolved.
Show resolved Hide resolved
static NSString *const SentryEnvelopeItemTypeTransaction = @"transaction";
static NSString *const SentryEnvelopeItemTypeAttachment = @"attachment";
static NSString *const SentryEnvelopeItemTypeClientReport = @"client_report";
Expand Down
Loading
Loading