From 269db3f0cddcdeaab2017213d168ce078364df20 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Thu, 19 Jun 2025 16:12:41 +0200 Subject: [PATCH 1/7] Add SentryFeedback and capture_feedback() --- src/register_types.cpp | 2 ++ src/sentry/disabled_sdk.h | 2 ++ src/sentry/internal_sdk.h | 3 +++ src/sentry/native/native_sdk.cpp | 20 +++++++++++++++++++ src/sentry/native/native_sdk.h | 2 ++ src/sentry_feedback.cpp | 10 ++++++++++ src/sentry_feedback.h | 34 ++++++++++++++++++++++++++++++++ src/sentry_sdk.cpp | 7 +++++++ src/sentry_sdk.h | 2 ++ 9 files changed, 82 insertions(+) create mode 100644 src/sentry_feedback.cpp create mode 100644 src/sentry_feedback.h diff --git a/src/register_types.cpp b/src/register_types.cpp index 72f76c8c..d6c1b7f7 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -3,6 +3,7 @@ #include "sentry/util/print.h" #include "sentry_configuration.h" #include "sentry_event.h" +#include "sentry_feedback.h" #include "sentry_logger.h" #include "sentry_options.h" #include "sentry_sdk.h" @@ -51,6 +52,7 @@ void initialize_module(ModuleInitializationLevel p_level) { GDREGISTER_INTERNAL_CLASS(RuntimeConfig); GDREGISTER_CLASS(SentryConfiguration); GDREGISTER_CLASS(SentryUser); + GDREGISTER_CLASS(SentryFeedback); GDREGISTER_CLASS(SentrySDK); GDREGISTER_ABSTRACT_CLASS(SentryEvent); GDREGISTER_INTERNAL_CLASS(DisabledEvent); diff --git a/src/sentry/disabled_sdk.h b/src/sentry/disabled_sdk.h index 57e725a2..48f24ca2 100644 --- a/src/sentry/disabled_sdk.h +++ b/src/sentry/disabled_sdk.h @@ -26,6 +26,8 @@ class DisabledSDK : public InternalSDK { virtual Ref create_event() override { return memnew(DisabledEvent); } virtual String capture_event(const Ref &p_event) override { return ""; } + virtual void capture_feedback(const Ref &p_feedback) override {} + virtual void initialize() override {} }; diff --git a/src/sentry/internal_sdk.h b/src/sentry/internal_sdk.h index 1afed376..2c62471c 100644 --- a/src/sentry/internal_sdk.h +++ b/src/sentry/internal_sdk.h @@ -3,6 +3,7 @@ #include "sentry/level.h" #include "sentry_event.h" +#include "sentry_feedback.h" #include "sentry_user.h" #include @@ -34,6 +35,8 @@ class InternalSDK { virtual Ref create_event() = 0; virtual String capture_event(const Ref &p_event) = 0; + virtual void capture_feedback(const Ref &p_feedback) = 0; + virtual void initialize() = 0; virtual ~InternalSDK() = default; diff --git a/src/sentry/native/native_sdk.cpp b/src/sentry/native/native_sdk.cpp index 2ab0ca86..c0b5f145 100644 --- a/src/sentry/native/native_sdk.cpp +++ b/src/sentry/native/native_sdk.cpp @@ -340,6 +340,26 @@ String NativeSDK::capture_event(const Ref &p_event) { return _uuid_as_string(uuid); } +void NativeSDK::capture_feedback(const Ref &p_feedback) { + ERR_FAIL_COND(p_feedback.is_null()); + ERR_FAIL_COND(p_feedback->get_message().is_empty()); + + sentry_uuid_t event_uuid; + if (p_feedback->get_associated_event_id().is_empty()) { + event_uuid = sentry_uuid_nil(); + } else { + event_uuid = sentry_uuid_from_string(p_feedback->get_associated_event_id().ascii()); + } + + sentry_value_t uf = sentry_value_new_user_feedback( + &event_uuid, + p_feedback->get_name().utf8(), + p_feedback->get_contact_email().utf8(), + p_feedback->get_message().utf8()); + + sentry_capture_user_feedback(uf); +} + void NativeSDK::initialize() { ERR_FAIL_NULL(OS::get_singleton()); ERR_FAIL_NULL(ProjectSettings::get_singleton()); diff --git a/src/sentry/native/native_sdk.h b/src/sentry/native/native_sdk.h index 65cc8cf0..1326f2e0 100644 --- a/src/sentry/native/native_sdk.h +++ b/src/sentry/native/native_sdk.h @@ -33,6 +33,8 @@ class NativeSDK : public InternalSDK { virtual Ref create_event() override; virtual String capture_event(const Ref &p_event) override; + virtual void capture_feedback(const Ref &p_feedback) override; + virtual void initialize() override; virtual ~NativeSDK() override; diff --git a/src/sentry_feedback.cpp b/src/sentry_feedback.cpp new file mode 100644 index 00000000..6b36c462 --- /dev/null +++ b/src/sentry_feedback.cpp @@ -0,0 +1,10 @@ +#include "sentry_feedback.h" + +#include "sentry/simple_bind.h" + +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); +} diff --git a/src/sentry_feedback.h b/src/sentry_feedback.h new file mode 100644 index 00000000..8ab6bf3b --- /dev/null +++ b/src/sentry_feedback.h @@ -0,0 +1,34 @@ +#ifndef SENTRY_FEEDBACK_H +#define SENTRY_FEEDBACK_H + +#include + +using namespace godot; + +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; } +}; + +#endif // SENTRY_FEEDBACK_H diff --git a/src/sentry_sdk.cpp b/src/sentry_sdk.cpp index aaf9db0f..1619b4ee 100644 --- a/src/sentry_sdk.cpp +++ b/src/sentry_sdk.cpp @@ -73,6 +73,12 @@ String SentrySDK::capture_event(const Ref &p_event) { return internal_sdk->capture_event(p_event); } +void SentrySDK::capture_feedback(const Ref &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."); + return internal_sdk->capture_feedback(p_feedback); +} + void SentrySDK::set_tag(const String &p_key, const String &p_value) { ERR_FAIL_COND_MSG(p_key.is_empty(), "Sentry: Can't set tag with an empty key."); internal_sdk->set_tag(p_key, p_value); @@ -194,6 +200,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"), &SentrySDK::capture_feedback); // Hidden API methods -- used in testing. ClassDB::bind_method(D_METHOD("_set_before_send", "callable"), &SentrySDK::set_before_send); diff --git a/src/sentry_sdk.h b/src/sentry_sdk.h index 019dfe33..1b0ee1a7 100644 --- a/src/sentry_sdk.h +++ b/src/sentry_sdk.h @@ -69,6 +69,8 @@ class SentrySDK : public Object { Ref create_event() const; String capture_event(const Ref &p_event); + void capture_feedback(const Ref &p_feedback); + // * Hidden API methods -- used in testing void set_before_send(const Callable &p_callable) { SentryOptions::get_singleton()->set_before_send(p_callable); } From d056b190fa1634b484773a89ebd9cdf9db49f2db Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Thu, 19 Jun 2025 16:17:28 +0200 Subject: [PATCH 2/7] Add missing arg in capture_feedback binding --- src/sentry_sdk.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry_sdk.cpp b/src/sentry_sdk.cpp index 1619b4ee..5e609198 100644 --- a/src/sentry_sdk.cpp +++ b/src/sentry_sdk.cpp @@ -200,7 +200,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"), &SentrySDK::capture_feedback); + ClassDB::bind_method(D_METHOD("capture_feedback", "feedback"), &SentrySDK::capture_feedback); // Hidden API methods -- used in testing. ClassDB::bind_method(D_METHOD("_set_before_send", "callable"), &SentrySDK::set_before_send); From 76c0836f7b3e91724a3b8850428d7f0cb752475b Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Thu, 19 Jun 2025 16:40:56 +0200 Subject: [PATCH 3/7] Add test --- project/test/suites/test_feedback.gd | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 project/test/suites/test_feedback.gd diff --git a/project/test/suites/test_feedback.gd b/project/test/suites/test_feedback.gd new file mode 100644 index 00000000..42b53360 --- /dev/null +++ b/project/test/suites/test_feedback.gd @@ -0,0 +1,23 @@ +extends GdUnitTestSuite +## Test Feedback class. +@warning_ignore("unused_parameter") + + +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(feedback.name) + + assert_str(feedback.contact_email).is_empty() + feedback.contact_email = "bob@example.com" + assert_str(feedback.contact_email).is_equal("bob@example.com") + + assert_str(feedback.message).is_empty() + feedback.message = "something happened" + assert_str(feedback.message).is_equal("something happened") From ea3b6db4b47e9a3aa4d48b0ff4b649a9b7c4f422 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Thu, 19 Jun 2025 16:44:57 +0200 Subject: [PATCH 4/7] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6c16cb5..65f3d347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- User feedback API ([#204](https://github.com/getsentry/sentry-godot/pull/204)) + ### Dependencies - Bump gdUnit 4 from v5.0.0 to v5.0.4 ([#193](https://github.com/getsentry/sentry-godot/pull/193), [#197](https://github.com/getsentry/sentry-godot/pull/197)) From 99dd4f86c20cbbe0ffba760500920e1738966fba Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Thu, 19 Jun 2025 16:58:06 +0200 Subject: [PATCH 5/7] Update class reference --- doc_classes/SentryFeedback.xml | 28 ++++++++++++++++++++++++++++ doc_classes/SentrySDK.xml | 7 +++++++ 2 files changed, 35 insertions(+) create mode 100644 doc_classes/SentryFeedback.xml diff --git a/doc_classes/SentryFeedback.xml b/doc_classes/SentryFeedback.xml new file mode 100644 index 00000000..305f2c05 --- /dev/null +++ b/doc_classes/SentryFeedback.xml @@ -0,0 +1,28 @@ + + + + Represents user feedback in Sentry. + + + + + + + + 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. + + + 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. + + + Comments of the user, describing what happened and/or sharing feedback. [i]Required[/i]. + The max length is 4096 characters. + + + 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. + + + diff --git a/doc_classes/SentrySDK.xml b/doc_classes/SentrySDK.xml index de4196c7..d2146281 100644 --- a/doc_classes/SentrySDK.xml +++ b/doc_classes/SentrySDK.xml @@ -32,6 +32,13 @@ Captures [param event] and sends it to Sentry, returning the event ID. You can create an event with [method SentrySDK.create_event]. + + + + + Captures user [param feedback] and sends it to Sentry. The feedback can optionally be associated with a specific error event if the [SentryFeedback] object includes an event ID. + + From 9ec133d295c7c1fd85f7c6d0f6a0263edf377188 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Thu, 19 Jun 2025 17:14:54 +0200 Subject: [PATCH 6/7] Warn if message is too long --- src/sentry_sdk.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sentry_sdk.cpp b/src/sentry_sdk.cpp index 5e609198..e0c8e751 100644 --- a/src/sentry_sdk.cpp +++ b/src/sentry_sdk.cpp @@ -76,6 +76,9 @@ String SentrySDK::capture_event(const Ref &p_event) { void SentrySDK::capture_feedback(const Ref &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); } From 8bc173ed86813ea0312aa45d141630f2843456b7 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Thu, 19 Jun 2025 17:37:22 +0200 Subject: [PATCH 7/7] Correction --- doc_classes/SentrySDK.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc_classes/SentrySDK.xml b/doc_classes/SentrySDK.xml index d2146281..18d1ef65 100644 --- a/doc_classes/SentrySDK.xml +++ b/doc_classes/SentrySDK.xml @@ -36,7 +36,7 @@ - Captures user [param feedback] and sends it to Sentry. The feedback can optionally be associated with a specific error event if the [SentryFeedback] object includes an event ID. + 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.