-
Notifications
You must be signed in to change notification settings - Fork 123
Analytics update #1729
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
Analytics update #1729
Changes from 3 commits
6d3d353
94abdb3
b829441
fa9363e
9b377ac
e29cce5
a75e482
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,379 @@ | ||
// Copyright 2025 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#include "analytics/src/windows/analytics_windows.h" | ||
#include "firebase/app.h" | ||
#include "firebase/analytics.h" | ||
#include "firebase/variant.h" | ||
#include "firebase/future.h" | ||
#include "firebase/log.h" // <-- New include | ||
jonsimantov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#include <vector> | ||
#include <string> | ||
#include <map> | ||
|
||
// Error code for Analytics features not supported on this platform. | ||
const int kAnalyticsErrorNotSupportedOnPlatform = 1; // Or a more specific range | ||
|
||
namespace firebase { | ||
namespace analytics { | ||
|
||
// Initializes the Analytics desktop API. | ||
// This function must be called before any other Analytics methods. | ||
void Initialize(const App& app) { | ||
// The 'app' parameter is not directly used by the underlying Google Analytics C API | ||
// for Windows for global initialization. It's included for API consistency | ||
// with other Firebase platforms. | ||
// (void)app; // Mark as unused if applicable by style guides. | ||
|
||
// The underlying Google Analytics C API for Windows does not have an explicit | ||
// global initialization function. | ||
// This function is provided for API consistency with other Firebase platforms | ||
// and for any future global initialization needs for the desktop wrapper. | ||
jonsimantov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// Terminates the Analytics desktop API. | ||
// Call this function when Analytics is no longer needed to free up resources. | ||
void Terminate() { | ||
// The underlying Google Analytics C API for Windows does not have an explicit | ||
// global termination or shutdown function. Resources like event parameter maps | ||
// are managed at the point of their use (e.g., destroyed after logging). | ||
// This function is provided for API consistency with other Firebase platforms | ||
// and for any future global cleanup needs for the desktop wrapper. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unneeded comment, please remove the whole paragraph. |
||
} | ||
|
||
static void ConvertParametersToGAParams( | ||
const Parameter* parameters, // firebase::analytics::Parameter | ||
jonsimantov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
size_t number_of_parameters, | ||
GoogleAnalytics_EventParameters* c_event_params) { | ||
if (!parameters || number_of_parameters == 0 || !c_event_params) { | ||
return; | ||
} | ||
|
||
for (size_t i = 0; i < number_of_parameters; ++i) { | ||
const Parameter& param = parameters[i]; | ||
if (param.name == nullptr || param.name[0] == '\0') { | ||
LogError("Analytics: Parameter name cannot be null or empty."); | ||
continue; | ||
} | ||
|
||
if (param.value.is_int64()) { | ||
GoogleAnalytics_EventParameters_InsertInt(c_event_params, param.name, | ||
param.value.int64_value()); | ||
} else if (param.value.is_double()) { | ||
GoogleAnalytics_EventParameters_InsertDouble(c_event_params, param.name, | ||
param.value.double_value()); | ||
} else if (param.value.is_string()) { | ||
GoogleAnalytics_EventParameters_InsertString( | ||
c_event_params, param.name, param.value.string_value()); | ||
} else if (param.value.is_vector()) { | ||
// Vector types for top-level event parameters are not directly supported for conversion | ||
// to GoogleAnalytics_EventParameters on Desktop. | ||
// The previous implementation attempted to interpret a vector of maps as an "Item Array", | ||
jonsimantov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// but the standard way to log an array of Items is via a single parameter (e.g., "items") | ||
// whose value is a vector of firebase::analytics::Item objects (which are maps). | ||
// For direct parameters, vector is an unsupported type. | ||
LogError("Analytics: Parameter '%s' has type Vector, which is unsupported for event parameters on Desktop. Skipping.", param.name); | ||
continue; // Skip this parameter | ||
} else if (param.value.is_map()) { | ||
// This block handles parameters that are maps. | ||
// Each key-value pair in the map is converted into a GoogleAnalytics_Item, | ||
// and all such items are bundled into a GoogleAnalytics_ItemVector, | ||
// which is then inserted into the event parameters. | ||
// The original map's key becomes the "name" property of the GA_Item, | ||
// and the map's value becomes one of "int_value", "double_value", or "string_value". | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This last sentence is inaccurate now, as the itemvector/map keys work differently. Either remove this comment or correct it. |
||
const std::map<std::string, firebase::Variant>& user_map = | ||
param.value.map_value(); | ||
if (user_map.empty()) { | ||
LogWarning("Analytics: Parameter '%s' is an empty map. Skipping.", param.name); | ||
continue; // Skip this parameter | ||
} | ||
|
||
GoogleAnalytics_ItemVector* c_item_vector = | ||
GoogleAnalytics_ItemVector_Create(); | ||
if (!c_item_vector) { | ||
LogError("Analytics: Failed to create ItemVector for map parameter '%s'.", param.name); | ||
continue; // Skip this parameter | ||
} | ||
|
||
bool item_vector_populated = false; | ||
for (const auto& entry : user_map) { | ||
const std::string& key_from_map = entry.first; | ||
const firebase::Variant& value_from_map = entry.second; | ||
|
||
GoogleAnalytics_Item* c_item = GoogleAnalytics_Item_Create(); | ||
if (!c_item) { | ||
LogError("Analytics: Failed to create Item for key '%s' in map parameter '%s'.", key_from_map.c_str(), param.name); | ||
continue; // Skip this key-value pair, try next one in map | ||
} | ||
|
||
// Store the original map's key as the "name" of this item property | ||
GoogleAnalytics_Item_InsertString(c_item, "name", key_from_map.c_str()); | ||
jonsimantov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
bool value_property_set = false; | ||
if (value_from_map.is_int64()) { | ||
GoogleAnalytics_Item_InsertInt(c_item, "int_value", value_from_map.int64_value()); | ||
value_property_set = true; | ||
} else if (value_from_map.is_double()) { | ||
GoogleAnalytics_Item_InsertDouble(c_item, "double_value", value_from_map.double_value()); | ||
value_property_set = true; | ||
} else if (value_from_map.is_string()) { | ||
GoogleAnalytics_Item_InsertString(c_item, "string_value", value_from_map.string_value()); | ||
value_property_set = true; | ||
} else { | ||
LogWarning("Analytics: Value for key '%s' in map parameter '%s' has an unsupported Variant type. This key-value pair will be skipped.", key_from_map.c_str(), param.name); | ||
// As "name" was set, but no value, this item is incomplete. | ||
} | ||
|
||
if (value_property_set) { | ||
GoogleAnalytics_ItemVector_InsertItem(c_item_vector, c_item); | ||
// c_item is now owned by c_item_vector | ||
item_vector_populated = true; | ||
} else { | ||
// If value wasn't set (e.g. unsupported type), or c_item creation failed. | ||
// (c_item creation failure is handled by 'continue' above, so this 'else' | ||
// is mainly for when value_property_set is false due to unsupported type) | ||
GoogleAnalytics_Item_Destroy(c_item); | ||
} | ||
} | ||
|
||
if (item_vector_populated) { | ||
GoogleAnalytics_EventParameters_InsertItemVector( | ||
c_event_params, param.name, c_item_vector); | ||
// c_item_vector is now owned by c_event_params | ||
} else { | ||
// If no items were successfully created and added (e.g., all values in map were unsupported types) | ||
GoogleAnalytics_ItemVector_Destroy(c_item_vector); | ||
jonsimantov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
LogWarning("Analytics: Map parameter '%s' resulted in an empty ItemVector; no valid key-value pairs found or all values had unsupported types. This map parameter was skipped.", param.name); | ||
} | ||
} else { | ||
LogWarning("Analytics: Unsupported variant type for parameter '%s'.", param.name); | ||
} | ||
} | ||
} | ||
|
||
// Logs an event with the given name and parameters. | ||
void LogEvent(const char* name, | ||
const Parameter* parameters, // firebase::analytics::Parameter | ||
size_t number_of_parameters) { | ||
if (name == nullptr || name[0] == '\0') { | ||
LogError("Analytics: Event name cannot be null or empty."); | ||
return; | ||
} | ||
|
||
GoogleAnalytics_EventParameters* c_event_params = nullptr; | ||
if (parameters != nullptr && number_of_parameters > 0) { | ||
c_event_params = GoogleAnalytics_EventParameters_Create(); | ||
if (!c_event_params) { | ||
LogError("Analytics: Failed to create event parameters map for event '%s'.", name); | ||
return; | ||
} | ||
ConvertParametersToGAParams(parameters, number_of_parameters, c_event_params); | ||
} | ||
|
||
GoogleAnalytics_LogEvent(name, c_event_params); | ||
// c_event_params is consumed by GoogleAnalytics_LogEvent, so no explicit destroy if passed. | ||
// However, if we created it but didn't pass it (e.g. error), it should be destroyed. | ||
// The C API doc says: "Automatically destroyed when it is logged." | ||
// "The caller is responsible for destroying the event parameter map using the | ||
// GoogleAnalytics_EventParameters_Destroy() function, unless it has been | ||
// logged, in which case it will be destroyed automatically when it is logged." | ||
// So, if GoogleAnalytics_LogEvent is called with c_event_params, it's handled. | ||
// If there was an error before that, and c_event_params was allocated, it would leak. | ||
// For robustness, a unique_ptr or similar RAII wrapper would be better for c_event_params | ||
// if not for the C API's ownership transfer. | ||
// Given the current structure, if c_event_params is created, it's always passed or should be. | ||
// If `name` is invalid, we return early, c_event_params is not created. | ||
// If `c_event_params` creation fails, we return, nothing to destroy. | ||
// If a parameter is bad, we `continue`, `c_event_params` is still valid and eventually logged. | ||
jonsimantov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// Sets a user property to the given value. | ||
// | ||
// Up to 25 user property names are supported. Once set, user property values | ||
// persist throughout the app lifecycle and across sessions. | ||
// | ||
// @param[in] name The name of the user property to set. Should contain 1 to 24 | ||
// alphanumeric characters or underscores, and must start with an alphabetic | ||
// character. The "firebase_", "google_", and "ga_" prefixes are reserved and | ||
// should not be used for user property names. Must be UTF-8 encoded. | ||
// @param[in] value The value of the user property. Values can be up to 36 | ||
// characters long. Setting the value to `nullptr` or an empty string will | ||
// clear the user property. Must be UTF-8 encoded if not nullptr. | ||
void SetUserProperty(const char* name, const char* property) { | ||
if (name == nullptr || name[0] == '\0') { | ||
LogError("Analytics: User property name cannot be null or empty."); | ||
return; | ||
} | ||
// The C API GoogleAnalytics_SetUserProperty allows value to be nullptr to remove the property. | ||
// If value is an empty string, it might also be treated as clearing by some backends, | ||
// or it might be an invalid value. The C API doc says: | ||
// "Setting the value to `nullptr` remove the user property." | ||
// For consistency, we pass it as is. | ||
GoogleAnalytics_SetUserProperty(name, property); | ||
} | ||
|
||
// Sets the user ID property. | ||
// This feature must be used in accordance with Google's Privacy Policy. | ||
// | ||
// @param[in] user_id The user ID associated with the user of this app on this | ||
// device. The user ID must be non-empty if not nullptr, and no more than 256 | ||
// characters long, and UTF-8 encoded. Setting user_id to `nullptr` removes | ||
// the user ID. | ||
void SetUserId(const char* user_id) { | ||
// The C API GoogleAnalytics_SetUserId allows user_id to be nullptr to clear the user ID. | ||
// The C API documentation also mentions: "The user ID must be non-empty and | ||
// no more than 256 characters long". | ||
// We'll pass nullptr as is. If user_id is an empty string "", this might be | ||
// an issue for the underlying C API or backend if it expects non-empty. | ||
// However, the Firebase API typically allows passing "" to clear some fields, | ||
// or it's treated as an invalid value. For SetUserId, `nullptr` is the standard | ||
// clear mechanism. An empty string might be an invalid ID. | ||
// For now, we are not adding extra validation for empty string beyond what C API does. | ||
// Consider adding a check for empty string if Firebase spec requires it. | ||
// e.g., if (user_id != nullptr && user_id[0] == '\0') { /* log error */ return; } | ||
GoogleAnalytics_SetUserId(user_id); | ||
} | ||
|
||
// Sets whether analytics collection is enabled for this app on this device. | ||
// This setting is persisted across app sessions. By default it is enabled. | ||
// | ||
// @param[in] enabled A flag that enables or disables Analytics collection. | ||
void SetAnalyticsCollectionEnabled(bool enabled) { | ||
GoogleAnalytics_SetAnalyticsCollectionEnabled(enabled); | ||
} | ||
|
||
// Clears all analytics data for this app from the device and resets the app | ||
// instance ID. | ||
void ResetAnalyticsData() { | ||
GoogleAnalytics_ResetAnalyticsData(); | ||
} | ||
|
||
// --- Stub Implementations for Unsupported Features --- | ||
|
||
void SetConsent(const std::map<ConsentType, ConsentStatus>& consent_settings) { | ||
// Not supported by the Windows C API. | ||
(void)consent_settings; // Mark as unused | ||
LogWarning("Analytics: SetConsent() is not supported and has no effect on Desktop."); | ||
} | ||
|
||
void LogEvent(const char* name) { | ||
LogEvent(name, nullptr, 0); | ||
} | ||
|
||
void LogEvent(const char* name, const char* parameter_name, | ||
const char* parameter_value) { | ||
if (parameter_name == nullptr) { | ||
LogEvent(name, nullptr, 0); | ||
return; | ||
} | ||
Parameter param(parameter_name, parameter_value); | ||
LogEvent(name, ¶m, 1); | ||
} | ||
|
||
void LogEvent(const char* name, const char* parameter_name, | ||
const double parameter_value) { | ||
if (parameter_name == nullptr) { | ||
LogEvent(name, nullptr, 0); | ||
return; | ||
} | ||
Parameter param(parameter_name, parameter_value); | ||
LogEvent(name, ¶m, 1); | ||
} | ||
|
||
void LogEvent(const char* name, const char* parameter_name, | ||
const int64_t parameter_value) { | ||
if (parameter_name == nullptr) { | ||
LogEvent(name, nullptr, 0); | ||
return; | ||
} | ||
Parameter param(parameter_name, parameter_value); | ||
LogEvent(name, ¶m, 1); | ||
} | ||
|
||
void LogEvent(const char* name, const char* parameter_name, | ||
const int parameter_value) { | ||
if (parameter_name == nullptr) { | ||
LogEvent(name, nullptr, 0); | ||
return; | ||
} | ||
Parameter param(parameter_name, static_cast<int64_t>(parameter_value)); | ||
LogEvent(name, ¶m, 1); | ||
} | ||
|
||
void InitiateOnDeviceConversionMeasurementWithEmailAddress( | ||
const char* email_address) { | ||
(void)email_address; | ||
LogWarning("Analytics: InitiateOnDeviceConversionMeasurementWithEmailAddress() is not supported and has no effect on Desktop."); | ||
} | ||
|
||
void InitiateOnDeviceConversionMeasurementWithPhoneNumber( | ||
const char* phone_number) { | ||
(void)phone_number; | ||
LogWarning("Analytics: InitiateOnDeviceConversionMeasurementWithPhoneNumber() is not supported and has no effect on Desktop."); | ||
} | ||
|
||
void InitiateOnDeviceConversionMeasurementWithHashedEmailAddress( | ||
std::vector<unsigned char> hashed_email_address) { | ||
(void)hashed_email_address; | ||
LogWarning("Analytics: InitiateOnDeviceConversionMeasurementWithHashedEmailAddress() is not supported and has no effect on Desktop."); | ||
} | ||
|
||
void InitiateOnDeviceConversionMeasurementWithHashedPhoneNumber( | ||
std::vector<unsigned char> hashed_phone_number) { | ||
(void)hashed_phone_number; | ||
LogWarning("Analytics: InitiateOnDeviceConversionMeasurementWithHashedPhoneNumber() is not supported and has no effect on Desktop."); | ||
} | ||
|
||
void SetSessionTimeoutDuration(int64_t milliseconds) { | ||
(void)milliseconds; | ||
LogWarning("Analytics: SetSessionTimeoutDuration() is not supported and has no effect on Desktop."); | ||
} | ||
|
||
Future<std::string> GetAnalyticsInstanceId() { | ||
// Not supported by the Windows C API. | ||
// Return a Future that is already completed with an error. | ||
firebase::FutureHandle handle; // Dummy handle for error | ||
// TODO(jules): Ensure g_future_api_table is appropriate or replace with direct Future creation. | ||
auto future = MakeFuture<std::string>(&firebase::g_future_api_table, handle); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than using MakeFuture here, let's copy what's done in analytics_stub.cc and use FutureData. Note that this will require initializing FutureData in the Initialize() function. |
||
future.Complete(handle, kAnalyticsErrorNotSupportedOnPlatform, "GetAnalyticsInstanceId is not supported on Desktop."); | ||
return future; | ||
} | ||
|
||
Future<std::string> GetAnalyticsInstanceIdLastResult() { | ||
// This typically returns the last result of the async call. | ||
// Since GetAnalyticsInstanceId is not supported, this also returns a failed future. | ||
firebase::FutureHandle handle; | ||
auto future = MakeFuture<std::string>(&firebase::g_future_api_table, handle); | ||
future.Complete(handle, kAnalyticsErrorNotSupportedOnPlatform, "GetAnalyticsInstanceId (and thus LastResult) is not supported on Desktop."); | ||
jonsimantov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return future; | ||
} | ||
|
||
Future<int64_t> GetSessionId() { | ||
// Not supported by the Windows C API. | ||
firebase::FutureHandle handle; | ||
auto future = MakeFuture<int64_t>(&firebase::g_future_api_table, handle); | ||
future.Complete(handle, kAnalyticsErrorNotSupportedOnPlatform, "GetSessionId is not supported on Desktop."); | ||
return future; | ||
} | ||
|
||
Future<int64_t> GetSessionIdLastResult() { | ||
firebase::FutureHandle handle; | ||
auto future = MakeFuture<int64_t>(&firebase::g_future_api_table, handle); | ||
future.Complete(handle, kAnalyticsErrorNotSupportedOnPlatform, "GetSessionId (and thus LastResult) is not supported on Desktop."); | ||
return future; | ||
} | ||
|
||
} // namespace analytics | ||
} // namespace firebase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This entire file is in the wrong directory. It should be in analytics/src/analytics_desktop.cc.