diff --git a/Samples/iOS-Swift/iOS-Swift/ExtraViewController.swift b/Samples/iOS-Swift/iOS-Swift/ExtraViewController.swift index 1acedd8d96..ba2d66466d 100644 --- a/Samples/iOS-Swift/iOS-Swift/ExtraViewController.swift +++ b/Samples/iOS-Swift/iOS-Swift/ExtraViewController.swift @@ -205,9 +205,9 @@ class ExtraViewController: UIViewController { @IBAction func captureUserFeedbackV2(_ sender: UIButton) { highlightButton(sender) - var attachments: [Data]? + var attachments: [Attachment]? if let url = BundleResourceProvider.screenshotURL, let data = try? Data(contentsOf: url) { - attachments = [data] + attachments = [Attachment(data: data, filename: "screenshot.png", contentType: "image/png")] } let errorEventID = SentrySDK.capture(error: NSError(domain: "test-error.user-feedback.iOS-Swift", code: 1)) let feedback = SentryFeedback(message: "It broke again on iOS-Swift. I don't know why, but this happens.", name: "John Me", email: "john@me.com", source: .custom, associatedEventId: errorEventID, attachments: attachments) diff --git a/Sources/Swift/Integrations/UserFeedback/SentryFeedback.swift b/Sources/Swift/Integrations/UserFeedback/SentryFeedback.swift index 01951d3988..c5eb3f3295 100644 --- a/Sources/Swift/Integrations/UserFeedback/SentryFeedback.swift +++ b/Sources/Swift/Integrations/UserFeedback/SentryFeedback.swift @@ -20,17 +20,17 @@ public final class SentryFeedback: NSObject { var message: String var source: SentryFeedbackSource @_spi(Private) public let eventId: SentryId - - /// Data objects for any attachments. Currently the web UI only supports showing one attached image, like for a screenshot. - private var attachments: [Data]? - + + /// Attachments for this feedback submission, like a screenshot. + private var attachments: [Attachment]? + /// The event id that this feedback is associated with, like a crash report. var associatedEventId: SentryId? - + /// - parameters: /// - associatedEventId The ID for an event you'd like associated with the feedback. - /// - attachments Data objects for any attachments. Currently the web UI only supports showing one attached image, like for a screenshot. - @objc public init(message: String, name: String?, email: String?, source: SentryFeedbackSource = .widget, associatedEventId: SentryId? = nil, attachments: [Data]? = nil) { + /// - attachments Attachment objects for any files to include with the feedback. + @objc public init(message: String, name: String?, email: String?, source: SentryFeedbackSource = .widget, associatedEventId: SentryId? = nil, attachments: [Attachment]? = nil) { self.eventId = SentryId() self.name = name self.email = email @@ -90,19 +90,27 @@ extension SentryFeedback { dict["email"] = email } if let attachments = attachments { - dict["attachments"] = attachments + dict["attachments"] = attachments.map { attachment -> [String: Any] in + var attDict: [String: Any] = ["filename": attachment.filename] + if let data = attachment.data { + attDict["data"] = data + } + if let path = attachment.path { + attDict["path"] = path + } + if let contentType = attachment.contentType { + attDict["contentType"] = contentType + } + return attDict + } } return dict } /** - * - note: Currently there is only a single attachment possible, for the screenshot, of which there can be only one. + * Returns all attachments for inclusion in the feedback envelope. */ @_spi(Private) public func attachmentsForEnvelope() -> [Attachment] { - var items = [Attachment]() - if let screenshot = attachments?.first { - items.append(Attachment(data: screenshot, filename: "screenshot.png", contentType: "application/png")) - } - return items + return attachments ?? [] } } diff --git a/Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackFormViewModel.swift b/Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackFormViewModel.swift index 061c3efc96..024afa9726 100644 --- a/Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackFormViewModel.swift +++ b/Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackFormViewModel.swift @@ -455,11 +455,11 @@ extension SentryUserFeedbackFormViewModel { } func feedbackObject() -> SentryFeedback { - var attachmentDatas: [Data]? + var attachments: [Attachment]? if let image = screenshotImageView.image, let data = image.pngData() { - attachmentDatas = [data] + attachments = [Attachment(data: data, filename: "screenshot.png", contentType: "image/png")] } - return SentryFeedback(message: messageTextView.text, name: fullNameTextField.text, email: emailTextField.text, attachments: attachmentDatas) + return SentryFeedback(message: messageTextView.text, name: fullNameTextField.text, email: emailTextField.text, attachments: attachments) } } diff --git a/Tests/SentryTests/Integrations/Feedback/SentryFeedbackTests.swift b/Tests/SentryTests/Integrations/Feedback/SentryFeedbackTests.swift index a2a2d306d2..e9ad77a5db 100644 --- a/Tests/SentryTests/Integrations/Feedback/SentryFeedbackTests.swift +++ b/Tests/SentryTests/Integrations/Feedback/SentryFeedbackTests.swift @@ -30,65 +30,84 @@ class SentryFeedbackTests: XCTestCase { } func testSerializeWithAllFields() throws { - let sut = SentryFeedback(message: "Test feedback message", name: "Test feedback provider", email: "test-feedback-provider@sentry.io", attachments: [Data()]) - + let attachment = Attachment(data: Data(), filename: "screenshot.png.png", contentType: "image/png") + let sut = SentryFeedback(message: "Test feedback message", name: "Test feedback provider", email: "test-feedback-provider@sentry.io", attachments: [attachment]) + let serialization = sut.serialize() XCTAssertEqual(try XCTUnwrap(serialization["message"] as? String), "Test feedback message") XCTAssertEqual(try XCTUnwrap(serialization["name"] as? String), "Test feedback provider") XCTAssertEqual(try XCTUnwrap(serialization["contact_email"] as? String), "test-feedback-provider@sentry.io") XCTAssertEqual(try XCTUnwrap(serialization["source"] as? String), "widget") - + let attachments = sut.attachmentsForEnvelope() XCTAssertEqual(attachments.count, 1) XCTAssertEqual(try XCTUnwrap(attachments.first).filename, "screenshot.png") - XCTAssertEqual(try XCTUnwrap(attachments.first).contentType, "application/png") + XCTAssertEqual(try XCTUnwrap(attachments.first).contentType, "image/png") } func testSerializeCustomFeedback() throws { - let sut = SentryFeedback(message: "Test feedback message", name: "Test feedback provider", email: "test-feedback-provider@sentry.io", source: .custom, attachments: [Data()]) - + let attachment = Attachment(data: Data(), filename: "screenshot.png", contentType: "image/png") + let sut = SentryFeedback(message: "Test feedback message", name: "Test feedback provider", email: "test-feedback-provider@sentry.io", source: .custom, attachments: [attachment]) + let serialization = sut.serialize() XCTAssertEqual(try XCTUnwrap(serialization["message"] as? String), "Test feedback message") XCTAssertEqual(try XCTUnwrap(serialization["name"] as? String), "Test feedback provider") XCTAssertEqual(try XCTUnwrap(serialization["contact_email"] as? String), "test-feedback-provider@sentry.io") XCTAssertEqual(try XCTUnwrap(serialization["source"] as? String), "custom") - + let attachments = sut.attachmentsForEnvelope() XCTAssertEqual(attachments.count, 1) XCTAssertEqual(try XCTUnwrap(attachments.first).filename, "screenshot.png") - XCTAssertEqual(try XCTUnwrap(attachments.first).contentType, "application/png") + XCTAssertEqual(try XCTUnwrap(attachments.first).contentType, "image/png") } func testSerializeWithAssociatedEventID() throws { let eventID = SentryId() - - let sut = SentryFeedback(message: "Test feedback message", name: "Test feedback provider", email: "test-feedback-provider@sentry.io", source: .custom, associatedEventId: eventID, attachments: [Data()]) - + let attachment = Attachment(data: Data(), filename: "screenshot.png", contentType: "image/png") + let sut = SentryFeedback(message: "Test feedback message", name: "Test feedback provider", email: "test-feedback-provider@sentry.io", source: .custom, associatedEventId: eventID, attachments: [attachment]) + let serialization = sut.serialize() XCTAssertEqual(try XCTUnwrap(serialization["message"] as? String), "Test feedback message") XCTAssertEqual(try XCTUnwrap(serialization["name"] as? String), "Test feedback provider") XCTAssertEqual(try XCTUnwrap(serialization["contact_email"] as? String), "test-feedback-provider@sentry.io") XCTAssertEqual(try XCTUnwrap(serialization["source"] as? String), "custom") XCTAssertEqual(try XCTUnwrap(serialization["associated_event_id"] as? String), eventID.sentryIdString) - + let attachments = sut.attachmentsForEnvelope() XCTAssertEqual(attachments.count, 1) XCTAssertEqual(try XCTUnwrap(attachments.first).filename, "screenshot.png") - XCTAssertEqual(try XCTUnwrap(attachments.first).contentType, "application/png") + XCTAssertEqual(try XCTUnwrap(attachments.first).contentType, "image/png") } func testSerializeWithNoOptionalFields() throws { let sut = SentryFeedback(message: "Test feedback message", name: nil, email: nil) - + let serialization = sut.serialize() XCTAssertEqual(try XCTUnwrap(serialization["message"] as? String), "Test feedback message") XCTAssertNil(serialization["name"]) XCTAssertNil(serialization["contact_email"]) XCTAssertEqual(try XCTUnwrap(serialization["source"] as? String), "widget") - + let attachments = sut.attachmentsForEnvelope() XCTAssertEqual(attachments.count, 0) } + + func testMultipleAttachments() throws { + let screenshot = Attachment(data: Data("screenshot".utf8), filename: "screenshot.png", contentType: "image/png") + let logFile = Attachment(data: Data("log content".utf8), filename: "app.log", contentType: "text/plain") + let videoFile = Attachment(data: Data("video".utf8), filename: "recording.mp4", contentType: "video/mp4") + + let sut = SentryFeedback(message: "Test feedback with multiple attachments", name: "Test User", email: "test@example.com", attachments: [screenshot, logFile, videoFile]) + + let attachments = sut.attachmentsForEnvelope() + XCTAssertEqual(attachments.count, 3) + XCTAssertEqual(attachments[0].filename, "screenshot.png") + XCTAssertEqual(attachments[0].contentType, "image/png") + XCTAssertEqual(attachments[1].filename, "app.log") + XCTAssertEqual(attachments[1].contentType, "text/plain") + XCTAssertEqual(attachments[2].filename, "recording.mp4") + XCTAssertEqual(attachments[2].contentType, "video/mp4") + } private let inputCombinations: [FeedbackTestCase] = [ // base case: don't require name or email, don't input a name or email, don't input a message or screenshot