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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Add user feedback API for collecting and sending user feedback to Sentry ([#418](https://github.com/getsentry/sentry-godot/pull/418))

### Improvements

- Detect when we're inside message logging to prevent SDK print operations through the Godot logger which cause runtime errors. ([#414](https://github.com/getsentry/sentry-godot/pull/414))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import io.sentry.SentryEvent
import io.sentry.SentryLevel
import io.sentry.SentryOptions
import io.sentry.android.core.SentryAndroid
import io.sentry.protocol.Feedback
import io.sentry.protocol.Message
import io.sentry.protocol.SentryException
import io.sentry.protocol.SentryId
import io.sentry.protocol.SentryStackFrame
import io.sentry.protocol.SentryStackTrace
import io.sentry.protocol.User
Expand Down Expand Up @@ -275,6 +277,17 @@ class SentryAndroidGodotPlugin(godot: Godot) : GodotPlugin(godot) {
return id.toString()
}

@UsedByGodot
fun captureFeedback(message: String, contactEmail: String, name: String, associatedEventId: String) {
val feedback = Feedback(message)
feedback.contactEmail = contactEmail.ifEmpty { null }
feedback.name = name.ifEmpty { null }
if (associatedEventId.isNotEmpty()) {
feedback.setAssociatedEventId(SentryId(associatedEventId))
}
Sentry.captureFeedback(feedback)
}

@UsedByGodot
fun eventGetId(eventHandle: Int): String {
val id = getEvent(eventHandle)?.eventId ?: return ""
Expand Down
30 changes: 30 additions & 0 deletions doc_classes/SentryFeedback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="SentryFeedback" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
<brief_description>
Represents user feedback in Sentry.
</brief_description>
<description>
[SentryFeedback] allows you to collect and send user feedback to Sentry. Create an instance of this class, set the required [member message] and optional fields, then submit it using [method SentrySDK.capture_feedback].
To learn more, visit [url=https://docs.sentry.io/platforms/godot/user-feedback/]User Feedback[/url] documentation.
</description>
<tutorials>
</tutorials>
<members>
<member name="associated_event_id" type="String" setter="set_associated_event_id" getter="get_associated_event_id" default="&quot;&quot;">
The identifier of an error event in the same project. [i]Optional[/i].
Use this to explicitly link a related error in the feedback UI.
</member>
<member name="contact_email" type="String" setter="set_contact_email" getter="get_contact_email" default="&quot;&quot;">
The email of the user who submitted the feedback. [i]Optional[/i].
If excluded, Sentry attempts to fill this in with user context. Anonymous feedbacks (no name or email) are still accepted.
</member>
<member name="message" type="String" setter="set_message" getter="get_message" default="&quot;&quot;">
Comments of the user, describing what happened and/or sharing feedback. [i]Required[/i].
The max length is 4096 characters.
</member>
<member name="name" type="String" setter="set_name" getter="get_name" default="&quot;&quot;">
The name of the user who submitted the feedback. [i]Optional[/i].
If excluded, Sentry attempts to fill this in with user context. Anonymous feedbacks (no name or email) are still accepted.
</member>
</members>
</class>
8 changes: 8 additions & 0 deletions doc_classes/SentrySDK.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@
Captures [param event] and sends it to Sentry, returning the event ID. You can create an event with [method SentrySDK.create_event].
</description>
</method>
<method name="capture_feedback">
<return type="void" />
<param index="0" name="feedback" type="SentryFeedback" />
<description>
Captures user [param feedback] and sends it to Sentry. The feedback can optionally be associated with a specific error event if the [member SentryFeedback.associated_event_id] is set.
For more information, see [SentryFeedback] class and visit [url=https://docs.sentry.io/platforms/godot/user-feedback/]User Feedback documentation[/url].
</description>
</method>
<method name="capture_message">
<return type="String" />
<param index="0" name="message" type="String" />
Expand Down
22 changes: 22 additions & 0 deletions project/test/suites/test_feedback.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
extends GdUnitTestSuite
## Test Feedback class.


func test_feedback_properties() -> void:
var feedback := SentryFeedback.new()

assert_str(feedback.associated_event_id).is_empty()
feedback.associated_event_id = "082ce03eface41dd94b8c6b005382d5e"
assert_str(feedback.associated_event_id).is_equal("082ce03eface41dd94b8c6b005382d5e")

assert_str(feedback.name).is_empty()
feedback.name = "Bob"
assert_str(feedback.name).is_equal("Bob")

assert_str(feedback.contact_email).is_empty()
feedback.contact_email = "[email protected]"
assert_str(feedback.contact_email).is_equal("[email protected]")

assert_str(feedback.message).is_empty()
feedback.message = "something happened"
assert_str(feedback.message).is_equal("something happened")
1 change: 1 addition & 0 deletions project/test/suites/test_feedback.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://m3p77wja6wk0
2 changes: 2 additions & 0 deletions src/register_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "sentry/sentry_attachment.h"
#include "sentry/sentry_breadcrumb.h"
#include "sentry/sentry_event.h"
#include "sentry/sentry_feedback.h"
#include "sentry/sentry_options.h"
#include "sentry/sentry_sdk.h"
#include "sentry/sentry_user.h"
Expand Down Expand Up @@ -48,6 +49,7 @@ void register_runtime_classes() {
GDREGISTER_INTERNAL_CLASS(RuntimeConfig);
GDREGISTER_CLASS(SentryUser);
GDREGISTER_CLASS(SentryTimestamp);
GDREGISTER_CLASS(SentryFeedback);
GDREGISTER_CLASS(SentrySDK);
GDREGISTER_ABSTRACT_CLASS(SentryAttachment);
GDREGISTER_ABSTRACT_CLASS(SentryEvent);
Expand Down
11 changes: 11 additions & 0 deletions src/sentry/android/android_sdk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ String AndroidSDK::capture_event(const Ref<SentryEvent> &p_event) {
return android_event->get_id();
}

void AndroidSDK::capture_feedback(const Ref<SentryFeedback> &p_feedback) {
ERR_FAIL_NULL(android_plugin);
ERR_FAIL_COND_MSG(p_feedback.is_null(), "Sentry: Can't capture feedback - feedback object is null.");
ERR_FAIL_COND_MSG(p_feedback->get_message().is_empty(), "Sentry: Can't capture feedback - feedback message is empty.");
android_plugin->call(ANDROID_SN(captureFeedback),
p_feedback->get_message(),
p_feedback->get_contact_email(),
p_feedback->get_name(),
p_feedback->get_associated_event_id());
}

void AndroidSDK::add_attachment(const Ref<SentryAttachment> &p_attachment) {
ERR_FAIL_COND(p_attachment.is_null());

Expand Down
2 changes: 2 additions & 0 deletions src/sentry/android/android_sdk.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class AndroidSDK : public InternalSDK {
virtual Ref<SentryEvent> create_event() override;
virtual String capture_event(const Ref<SentryEvent> &p_event) override;

virtual void capture_feedback(const Ref<SentryFeedback> &p_feedback) override;

virtual void add_attachment(const Ref<SentryAttachment> &p_attachment) override;

virtual void init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) override;
Expand Down
1 change: 1 addition & 0 deletions src/sentry/android/android_string_names.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ AndroidStringNames::AndroidStringNames() {
createEvent = StringName("createEvent");
releaseEvent = StringName("releaseEvent");
captureEvent = StringName("captureEvent");
captureFeedback = StringName("captureFeedback");
addFileAttachment = StringName("addFileAttachment");
addBytesAttachment = StringName("addBytesAttachment");

Expand Down
1 change: 1 addition & 0 deletions src/sentry/android/android_string_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class AndroidStringNames {
StringName createEvent;
StringName releaseEvent;
StringName captureEvent;
StringName captureFeedback;
StringName addFileAttachment;
StringName addBytesAttachment;

Expand Down
1 change: 1 addition & 0 deletions src/sentry/cocoa/cocoa_includes.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ using SentryException = ::SentryException;
using SentryStacktrace = ::SentryStacktrace;
using SentryFrame = ::SentryFrame;
using SentryThread = ::SentryThread;
using SentryFeedback = ::SentryFeedback;

} // namespace objc

Expand Down
2 changes: 2 additions & 0 deletions src/sentry/cocoa/cocoa_sdk.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class CocoaSDK : public InternalSDK {
virtual Ref<SentryEvent> create_event() override;
virtual String capture_event(const Ref<SentryEvent> &p_event) override;

virtual void capture_feedback(const Ref<SentryFeedback> &p_feedback) override;

virtual void add_attachment(const Ref<SentryAttachment> &p_attachment) override;

virtual void init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) override;
Expand Down
19 changes: 19 additions & 0 deletions src/sentry/cocoa/cocoa_sdk.mm
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,25 @@
return event_id ? string_from_objc(event_id.sentryIdString) : String();
}

void CocoaSDK::capture_feedback(const Ref<SentryFeedback> &p_feedback) {
ERR_FAIL_COND_MSG(p_feedback.is_null(), "Sentry: Can't capture feedback - feedback object is null.");
ERR_FAIL_COND_MSG(p_feedback->get_message().is_empty(), "Sentry: Can't capture feedback - feedback message is empty.");

objc::SentryId *id = nil;

if (!p_feedback->get_associated_event_id().is_empty()) {
id = [[objc::SentryId alloc] initWithUUIDString:string_to_objc(p_feedback->get_associated_event_id())];
}

objc::SentryFeedback *cocoa_feedback = [[objc::SentryFeedback alloc] initWithMessage:string_to_objc(p_feedback->get_message())
name:string_to_objc_or_nil_if_empty(p_feedback->get_name())
email:string_to_objc_or_nil_if_empty(p_feedback->get_contact_email())
source:SentryFeedbackSourceCustom
associatedEventId:id
attachments:nil];
[objc::SentrySDK captureFeedback:cocoa_feedback];
}

void CocoaSDK::add_attachment(const Ref<SentryAttachment> &p_attachment) {
ERR_FAIL_COND_MSG(p_attachment.is_null(), "Sentry: Can't add null attachment.");

Expand Down
2 changes: 2 additions & 0 deletions src/sentry/disabled/disabled_sdk.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class DisabledSDK : public InternalSDK {
virtual Ref<SentryEvent> create_event() override { return memnew(DisabledEvent); }
virtual String capture_event(const Ref<SentryEvent> &p_event) override { return ""; }

virtual void capture_feedback(const Ref<SentryFeedback> &p_feedback) override {}

virtual void add_attachment(const Ref<SentryAttachment> &p_attachment) override {}

virtual void init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) override {}
Expand Down
3 changes: 3 additions & 0 deletions src/sentry/internal_sdk.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "sentry/sentry_attachment.h"
#include "sentry/sentry_breadcrumb.h"
#include "sentry/sentry_event.h"
#include "sentry/sentry_feedback.h"
#include "sentry/sentry_user.h"

#include <godot_cpp/variant/dictionary.hpp>
Expand Down Expand Up @@ -37,6 +38,8 @@ class InternalSDK {
virtual Ref<SentryEvent> create_event() = 0;
virtual String capture_event(const Ref<SentryEvent> &p_event) = 0;

virtual void capture_feedback(const Ref<SentryFeedback> &p_feedback) = 0;

virtual void add_attachment(const Ref<SentryAttachment> &p_attachment) = 0;

virtual void init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) = 0;
Expand Down
25 changes: 25 additions & 0 deletions src/sentry/native/native_sdk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,31 @@ String NativeSDK::capture_event(const Ref<SentryEvent> &p_event) {
return _uuid_as_string(uuid);
}

void NativeSDK::capture_feedback(const Ref<SentryFeedback> &p_feedback) {
ERR_FAIL_COND_MSG(p_feedback.is_null(), "Sentry: Can't capture feedback - feedback object is null.");
ERR_FAIL_COND_MSG(p_feedback->get_message().is_empty(), "Sentry: Can't capture feedback - feedback message is empty.");

sentry_value_t feedback = sentry_value_new_object();

sentry_value_set_by_key(feedback, "message",
sentry_value_new_string(p_feedback->get_message().utf8()));

if (!p_feedback->get_contact_email().is_empty()) {
sentry_value_set_by_key(feedback, "contact_email",
sentry_value_new_string(p_feedback->get_contact_email().utf8()));
}
if (!p_feedback->get_name().is_empty()) {
sentry_value_set_by_key(feedback, "name",
sentry_value_new_string(p_feedback->get_name().utf8()));
}
if (!p_feedback->get_associated_event_id().is_empty()) {
sentry_value_set_by_key(feedback, "associated_event_id",
sentry_value_new_string(p_feedback->get_associated_event_id().ascii()));
}

sentry_capture_feedback(feedback);
}

void NativeSDK::add_attachment(const Ref<SentryAttachment> &p_attachment) {
ERR_FAIL_COND_MSG(p_attachment.is_null(), "Sentry: Can't add null attachment.");
ERR_FAIL_NULL(ProjectSettings::get_singleton());
Expand Down
2 changes: 2 additions & 0 deletions src/sentry/native/native_sdk.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class NativeSDK : public InternalSDK {
virtual Ref<SentryEvent> create_event() override;
virtual String capture_event(const Ref<SentryEvent> &p_event) override;

virtual void capture_feedback(const Ref<SentryFeedback> &p_feedback) override;

virtual void add_attachment(const Ref<SentryAttachment> &p_attachment) override;

virtual void init(const PackedStringArray &p_global_attachments, const Callable &p_configuration_callback) override;
Expand Down
14 changes: 14 additions & 0 deletions src/sentry/sentry_feedback.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include "sentry_feedback.h"

#include "sentry/util/simple_bind.h"

namespace sentry {

void SentryFeedback::_bind_methods() {
BIND_PROPERTY(SentryFeedback, PropertyInfo(Variant::STRING, "name"), set_name, get_name);
BIND_PROPERTY(SentryFeedback, PropertyInfo(Variant::STRING, "contact_email"), set_contact_email, get_contact_email);
BIND_PROPERTY(SentryFeedback, PropertyInfo(Variant::STRING, "message"), set_message, get_message);
BIND_PROPERTY(SentryFeedback, PropertyInfo(Variant::STRING, "associated_event_id"), set_associated_event_id, get_associated_event_id);
}

} //namespace sentry
35 changes: 35 additions & 0 deletions src/sentry/sentry_feedback.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once

#include <godot_cpp/classes/ref_counted.hpp>

using namespace godot;

namespace sentry {

class SentryFeedback : public RefCounted {
GDCLASS(SentryFeedback, RefCounted);

private:
String name;
String contact_email;
String message;
String associated_event_id;

protected:
static void _bind_methods();

public:
String get_name() const { return name; }
void set_name(const String &p_name) { name = p_name; }

String get_contact_email() const { return contact_email; }
void set_contact_email(const String &p_contact_email) { contact_email = p_contact_email; }

String get_message() const { return message; }
void set_message(const String &p_message) { message = p_message; }

String get_associated_event_id() const { return associated_event_id; }
void set_associated_event_id(const String &p_associated_event_id) { associated_event_id = p_associated_event_id; }
};

} //namespace sentry
10 changes: 10 additions & 0 deletions src/sentry/sentry_sdk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ String SentrySDK::capture_event(const Ref<SentryEvent> &p_event) {
return internal_sdk->capture_event(p_event);
}

void SentrySDK::capture_feedback(const Ref<SentryFeedback> &p_feedback) {
ERR_FAIL_COND_MSG(p_feedback.is_null(), "Sentry: Can't capture feedback - feedback object is null.");
ERR_FAIL_COND_MSG(p_feedback->get_message().is_empty(), "Sentry: Can't capture feedback - feedback message is empty.");
if (p_feedback->get_message().length() > 4096) {
WARN_PRINT("Sentry: Feedback message is too long (max 4096 characters).");
}
return internal_sdk->capture_feedback(p_feedback);
}

void SentrySDK::add_attachment(const Ref<SentryAttachment> &p_attachment) {
ERR_FAIL_COND_MSG(p_attachment.is_null(), "Sentry: Can't add null attachment.");
internal_sdk->add_attachment(p_attachment);
Expand Down Expand Up @@ -362,6 +371,7 @@ void SentrySDK::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_user"), &SentrySDK::remove_user);
ClassDB::bind_method(D_METHOD("create_event"), &SentrySDK::create_event);
ClassDB::bind_method(D_METHOD("capture_event", "event"), &SentrySDK::capture_event);
ClassDB::bind_method(D_METHOD("capture_feedback", "feedback"), &SentrySDK::capture_feedback);
ClassDB::bind_method(D_METHOD("add_attachment", "attachment"), &SentrySDK::add_attachment);

// Hidden API methods -- used in testing.
Expand Down
2 changes: 2 additions & 0 deletions src/sentry/sentry_sdk.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class SentrySDK : public Object {
Ref<SentryEvent> create_event() const;
String capture_event(const Ref<SentryEvent> &p_event);

void capture_feedback(const Ref<SentryFeedback> &p_feedback);

void add_attachment(const Ref<SentryAttachment> &p_attachment);

// * Hidden API methods -- used in testing
Expand Down