Skip to content

Add SetDefaultEventParameters and ClearDefaultEventParameters to Analytics C++ SDK. #1719

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

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
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
66 changes: 66 additions & 0 deletions analytics/integration_test/src/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -341,4 +341,70 @@ TEST_F(FirebaseAnalyticsTest, TestSetConsent) {
did_test_setconsent_ = true;
}

TEST_F(FirebaseAnalyticsTest, TestSetDefaultEventParameters) {
LogInfo(
"Testing SetDefaultEventParameters with initial values, then updating "
"with Null.");

std::map<std::string, firebase::Variant> initial_defaults;
initial_defaults["initial_key"] = "initial_value";
initial_defaults["key_to_be_nulled"] = "text_before_null";
initial_defaults["numeric_default"] = 12345;

LogInfo("Setting initial default event parameters.");
firebase::analytics::SetDefaultEventParameters(initial_defaults);
// Log an event that would pick up these defaults.
firebase::analytics::LogEvent("event_with_initial_defaults");
LogInfo("Logged event_with_initial_defaults.");
ProcessEvents(
500); // Short pause for event logging, if it matters for backend.

std::map<std::string, firebase::Variant> updated_defaults;
updated_defaults["key_to_be_nulled"] = firebase::Variant::Null();
updated_defaults["another_key"] = "another_value";
// "initial_key" should persist if not overwritten.
// "numeric_default" should persist.

LogInfo(
"Updating default event parameters, setting key_to_be_nulled to Null.");
firebase::analytics::SetDefaultEventParameters(updated_defaults);
// Log an event that would pick up the updated defaults.
firebase::analytics::LogEvent("event_after_nulling_and_adding");
LogInfo("Logged event_after_nulling_and_adding.");
ProcessEvents(500);

// For this C++ SDK integration test, we primarily ensure API calls complete.
// Actual parameter presence on logged events would be verified by
// backend/native tests.
LogInfo(
"TestSetDefaultEventParameters completed. Calls were made "
"successfully.");
}

TEST_F(FirebaseAnalyticsTest, TestClearDefaultEventParameters) {
LogInfo("Testing ClearDefaultEventParameters.");

std::map<std::string, firebase::Variant> defaults_to_clear;
defaults_to_clear["default_one"] = "will_be_cleared";
defaults_to_clear["default_two"] = 9876;

LogInfo("Setting default parameters before clearing.");
firebase::analytics::SetDefaultEventParameters(defaults_to_clear);
// Log an event that would pick up these defaults.
firebase::analytics::LogEvent("event_before_global_clear");
LogInfo("Logged event_before_global_clear.");
ProcessEvents(500);

LogInfo("Calling ClearDefaultEventParameters.");
firebase::analytics::ClearDefaultEventParameters();
// Log an event that should not have the previous defaults.
firebase::analytics::LogEvent("event_after_global_clear");
LogInfo("Logged event_after_global_clear.");
ProcessEvents(500);

LogInfo(
"TestClearDefaultEventParameters completed. Call was made "
"successfully.");
}

} // namespace firebase_testapp_automated
104 changes: 103 additions & 1 deletion analytics/src/analytics_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ static const ::firebase::App* g_app = nullptr;
"()Lcom/google/android/gms/tasks/Task;"), \
X(GetSessionId, "getSessionId", \
"()Lcom/google/android/gms/tasks/Task;"), \
X(SetDefaultEventParameters, "setDefaultEventParameters", \
"(Landroid/os/Bundle;)V", util::kMethodTypeInstance), \
X(GetInstance, "getInstance", "(Landroid/content/Context;)" \
"Lcom/google/firebase/analytics/FirebaseAnalytics;", \
firebase::util::kMethodTypeStatic)
Expand Down Expand Up @@ -512,7 +514,8 @@ void LogEvent(const char* name, const Parameter* parameters,
LogError(
"LogEvent(%s): %s is not a valid parameter value type. "
"No event was logged.",
parameter.name, Variant::TypeName(parameter.value.type()));
parameter.name,
firebase::Variant::TypeName(parameter.value.type()));
}
}
});
Expand Down Expand Up @@ -609,6 +612,105 @@ void ResetAnalyticsData() {
util::CheckAndClearJniExceptions(env);
}

void SetDefaultEventParameters(
const std::map<std::string, firebase::Variant>& default_parameters) {
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
JNIEnv* env = g_app->GetJNIEnv();
if (!env) return;

jobject bundle =
env->NewObject(util::bundle::GetClass(),
util::bundle::GetMethodId(util::bundle::kConstructor));
if (util::CheckAndClearJniExceptions(env) || !bundle) {
LogError("Failed to create Bundle for SetDefaultEventParameters.");
if (bundle) env->DeleteLocalRef(bundle);
return;
}

for (const auto& pair : default_parameters) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure that Android also treats an empty map as a no-op, similar to iOS?

const Variant& value = pair.second;
const char* key_cstr = pair.first.c_str();

if (value.is_null()) {
jstring key_jstring = env->NewStringUTF(key_cstr);
if (util::CheckAndClearJniExceptions(env) || !key_jstring) {
LogError(
"SetDefaultEventParameters: Failed to create jstring for null "
"value key: %s",
key_cstr);
if (key_jstring) env->DeleteLocalRef(key_jstring);
continue;
}
env->CallVoidMethod(bundle,
util::bundle::GetMethodId(util::bundle::kPutString),
key_jstring, nullptr);
if (util::CheckAndClearJniExceptions(env)) {
LogError(
"SetDefaultEventParameters: Failed to put null string for key: %s",
key_cstr);
}
env->DeleteLocalRef(key_jstring);
} else if (value.is_string() || value.is_int64() || value.is_double() ||
value.is_bool()) {
// AddVariantToBundle handles these types and their JNI conversions.
// It also logs if an individual AddToBundle within it fails or if a type
// is unsupported by it.
if (!AddVariantToBundle(env, bundle, key_cstr, value)) {
// This specific log gives context that the failure happened during
// SetDefaultEventParameters for a type that was expected to be
// supported by AddVariantToBundle.
LogError(
"SetDefaultEventParameters: Failed to add parameter for key '%s' "
"with supported type '%s'. This might indicate a JNI issue during "
"conversion.",
key_cstr, Variant::TypeName(value.type()));
}
} else if (value.is_vector() || value.is_map()) {
LogError(
"SetDefaultEventParameters: Value for key '%s' has type '%s' which "
"is not supported for default event parameters. Only string, int64, "
"double, bool, and null are supported. Skipping.",
key_cstr, Variant::TypeName(value.type()));
} else {
// This case handles other fundamental Variant types that are not scalars
// and not vector/map.
LogError(
"SetDefaultEventParameters: Value for key '%s' has an unexpected and "
"unsupported type '%s'. Skipping.",
key_cstr, Variant::TypeName(value.type()));
}
}

env->CallVoidMethod(
g_analytics_class_instance,
analytics::GetMethodId(analytics::kSetDefaultEventParameters), bundle);
if (util::CheckAndClearJniExceptions(env)) {
LogError("Failed to call setDefaultEventParameters on Java instance.");
}

env->DeleteLocalRef(bundle);
}

void ClearDefaultEventParameters() {
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
JNIEnv* env = g_app->GetJNIEnv();
if (!env) return;

// Calling with nullptr bundle should clear the parameters.
env->CallVoidMethod(
g_analytics_class_instance,
analytics::GetMethodId(analytics::kSetDefaultEventParameters), nullptr);
if (util::CheckAndClearJniExceptions(env)) {
// This might happen if the method isn't available on older SDKs,
// or if some other JNI error occurs.
LogError(
"Failed to call setDefaultEventParameters(null) on Java instance. "
"This may indicate the method is not available on this Android SDK "
"version "
"or another JNI error occurred.");
}
}

Future<std::string> GetAnalyticsInstanceId() {
FIREBASE_ASSERT_RETURN(GetAnalyticsInstanceIdLastResult(),
internal::IsInitialized());
Expand Down
40 changes: 39 additions & 1 deletion analytics/src/analytics_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ void LogEvent(const char* name, const Parameter* parameters, size_t number_of_pa
// A Variant type that couldn't be handled was passed in.
LogError("LogEvent(%s): %s is not a valid parameter value type. "
"No event was logged.",
parameter.name, Variant::TypeName(parameter.value.type()));
parameter.name, firebase::Variant::TypeName(parameter.value.type()));
}
}
[FIRAnalytics logEventWithName:@(name) parameters:parameters_dict];
Expand Down Expand Up @@ -373,6 +373,44 @@ void SetSessionTimeoutDuration(int64_t milliseconds) {
setSessionTimeoutInterval:static_cast<NSTimeInterval>(milliseconds) / kMillisecondsPerSecond];
}

void SetDefaultEventParameters(const std::map<std::string, Variant>& default_parameters) {
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
NSMutableDictionary* ns_default_parameters =
[[NSMutableDictionary alloc] initWithCapacity:default_parameters.size()];
for (const auto& pair : default_parameters) {
NSString* key = SafeString(pair.first.c_str());
const Variant& value = pair.second;

if (value.is_null()) {
[ns_default_parameters setObject:[NSNull null] forKey:key];
} else if (value.is_int64()) {
[ns_default_parameters setObject:[NSNumber numberWithLongLong:value.int64_value()]
forKey:key];
} else if (value.is_double()) {
[ns_default_parameters setObject:[NSNumber numberWithDouble:value.double_value()] forKey:key];
} else if (value.is_string()) {
[ns_default_parameters setObject:SafeString(value.string_value()) forKey:key];
} else if (value.is_bool()) {
[ns_default_parameters setObject:[NSNumber numberWithBool:value.bool_value()] forKey:key];
} else if (value.is_vector() || value.is_map()) {
LogError("SetDefaultEventParameters: Value for key '%s' has type '%s' which is not supported "
"for default event parameters. Only string, int64, double, bool, and null are "
"supported. Skipping.",
pair.first.c_str(), Variant::TypeName(value.type()));
} else {
LogError("SetDefaultEventParameters: Value for key '%s' has an unexpected type '%s' which is "
"not supported. Skipping.",
pair.first.c_str(), Variant::TypeName(value.type()));
}
}
[FIRAnalytics setDefaultEventParameters:ns_default_parameters];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double-check FIRAnalytics setDefaultEventParameters -- if we pass in an empty dictionary, will it clear the parameters? This could happen if every single value we specified was an invalid type.

}

void ClearDefaultEventParameters() {
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
[FIRAnalytics setDefaultEventParameters:nil];
}

void ResetAnalyticsData() {
MutexLock lock(g_mutex);
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
Expand Down
11 changes: 11 additions & 0 deletions analytics/src/analytics_stub.cc
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ void SetSessionTimeoutDuration(int64_t /*milliseconds*/) {
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
}

void SetDefaultEventParameters(
const std::map<std::string, Variant>& /*default_parameters*/) {
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
// This is a stub implementation. No operation needed.
}

void ClearDefaultEventParameters() {
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
// This is a stub implementation. No operation needed.
}

void ResetAnalyticsData() {
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
g_fake_instance_id++;
Expand Down
17 changes: 17 additions & 0 deletions analytics/src/include/firebase/analytics.h
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,23 @@ void SetSessionTimeoutDuration(int64_t milliseconds);
/// instance id.
void ResetAnalyticsData();

/// @brief Sets the default event parameters.
///
/// These parameters will be automatically logged with all calls to `LogEvent`.
/// Default parameters are overridden by parameters supplied to the `LogEvent`
/// method.
///
/// When a value in the `default_parameters` map is
/// `firebase::Variant::Null()`, it signifies that the default parameter for
/// that specific key should be cleared.
///
/// @param[in] default_parameters A map of parameter names to Variant values.
void SetDefaultEventParameters(
const std::map<std::string, Variant>& default_parameters);

/// @brief Clears all default event parameters.
void ClearDefaultEventParameters();

/// Get the instance ID from the analytics service.
///
/// @note This is *not* the same ID as the ID returned by
Expand Down
2 changes: 2 additions & 0 deletions analytics/src_ios/fake/FIRAnalytics.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,6 @@

+ (void)resetAnalyticsData;

+ (void)setDefaultEventParameters:(nullable NSDictionary<NSString *, id> *)parameters;

@end
13 changes: 13 additions & 0 deletions analytics/src_ios/fake/FIRAnalytics.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
@implementation FIRAnalytics

+ (NSString *)stringForValue:(id)value {
if (value == [NSNull null]) {
return @"<NSNull>";
}
return [NSString stringWithFormat:@"%@", value];
}

Expand Down Expand Up @@ -94,4 +97,14 @@ + (void)resetAnalyticsData {
FakeReporter->AddReport("+[FIRAnalytics resetAnalyticsData]", {});
}

+ (void)setDefaultEventParameters:(nullable NSDictionary<NSString *, id> *)parameters {
if (parameters == nil) {
FakeReporter->AddReport("+[FIRAnalytics setDefaultEventParameters:]", {"nil"});
} else {
NSString *parameterString = [self stringForParameters:parameters];
FakeReporter->AddReport("+[FIRAnalytics setDefaultEventParameters:]",
{ [parameterString UTF8String] });
}
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,31 @@ public void setSessionTimeoutDuration(long milliseconds) {
FakeReporter.addReport(
"FirebaseAnalytics.setSessionTimeoutDuration", Long.toString(milliseconds));
}

public void setDefaultEventParameters(Bundle bundle) {
if (bundle == null) {
FakeReporter.addReport("FirebaseAnalytics.setDefaultEventParameters", "null");
} else {
StringBuilder paramsString = new StringBuilder();
// Sort keys for predictable ordering.
for (String key : new TreeSet<>(bundle.keySet())) {
paramsString.append(key);
paramsString.append("=");
Object value = bundle.get(key); // Get as Object first
if (value == null) {
// This case handles when bundle.putString(key, null) was called.
paramsString.append("<null_string_value>");
} else {
paramsString.append(value.toString());
}
paramsString.append(",");
}
// Remove trailing comma if paramsString is not empty
if (paramsString.length() > 0) {
paramsString.setLength(paramsString.length() - 1);
}
FakeReporter.addReport(
"FirebaseAnalytics.setDefaultEventParameters", paramsString.toString());
}
}
}
Loading
Loading