@@ -7,6 +7,8 @@ | |||
jucerVersion="5.2.0"> | |||
<MAINGROUP id="aOi9Tf" name="AnalyticsCollection"> | |||
<GROUP id="{527B2E17-B1B1-B919-CBEA-231058E23D74}" name="Source"> | |||
<FILE id="kkmpUZ" name="DemoAnalyticsEventTypes.h" compile="0" resource="0" | |||
file="Source/DemoAnalyticsEventTypes.h"/> | |||
<FILE id="qTLkDX" name="GoogleAnalyticsDestination.h" compile="0" resource="0" | |||
file="Source/GoogleAnalyticsDestination.h"/> | |||
<FILE id="DHs1bY" name="MainComponent.h" compile="0" resource="0" file="Source/MainComponent.h"/> | |||
@@ -30,6 +30,7 @@ add_library( ${BINARY_NAME} | |||
SHARED | |||
"../../../Source/DemoAnalyticsEventTypes.h" | |||
"../../../Source/GoogleAnalyticsDestination.h" | |||
"../../../Source/MainComponent.h" | |||
"../../../Source/Main.cpp" | |||
@@ -788,6 +789,7 @@ add_library( ${BINARY_NAME} | |||
"../../../JuceLibraryCode/JuceHeader.h" | |||
) | |||
set_source_files_properties("../../../Source/DemoAnalyticsEventTypes.h" PROPERTIES HEADER_FILE_ONLY TRUE) | |||
set_source_files_properties("../../../Source/GoogleAnalyticsDestination.h" PROPERTIES HEADER_FILE_ONLY TRUE) | |||
set_source_files_properties("../../../Source/MainComponent.h" PROPERTIES HEADER_FILE_ONLY TRUE) | |||
set_source_files_properties("../../../../../modules/juce_analytics/analytics/juce_Analytics.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) | |||
@@ -38,12 +38,14 @@ | |||
91208A06115D573563996967 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = JuceHeader.h; path = ../../JuceLibraryCode/JuceHeader.h; sourceTree = "SOURCE_ROOT"; }; | |||
996BEF5ADCE2EC85EB9F637F = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_data_structures"; path = "../../../../modules/juce_data_structures"; sourceTree = "SOURCE_ROOT"; }; | |||
A0DDFB3559C431E96EC59392 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_events.mm"; path = "../../JuceLibraryCode/include_juce_events.mm"; sourceTree = "SOURCE_ROOT"; }; | |||
AAB5010326113C1358279789 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DemoAnalyticsEventTypes.h; path = ../../Source/DemoAnalyticsEventTypes.h; sourceTree = "SOURCE_ROOT"; }; | |||
AD2CFF58DA5E1C6EDF9CC399 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_data_structures.mm"; path = "../../JuceLibraryCode/include_juce_data_structures.mm"; sourceTree = "SOURCE_ROOT"; }; | |||
C858CF44E96D416E4B6B9266 = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; | |||
D352CDB4CA7E8B21FAA83B8C = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MainComponent.h; path = ../../Source/MainComponent.h; sourceTree = "SOURCE_ROOT"; }; | |||
F2CF007AA4C90AC7A5AD1604 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_analytics"; path = "../../../../modules/juce_analytics"; sourceTree = "SOURCE_ROOT"; }; | |||
FBCE051A0BA6C9FA3E64B47B = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; | |||
D269FA28B5D6012AEFE0BF20 = {isa = PBXGroup; children = ( | |||
AAB5010326113C1358279789, | |||
8B927F72BA8726A064560942, | |||
D352CDB4CA7E8B21FAA83B8C, | |||
6A86C9751E9DCFA62D4562DB, ); name = Source; sourceTree = "<group>"; }; | |||
@@ -1228,6 +1228,7 @@ | |||
<ClCompile Include="..\..\JuceLibraryCode\include_juce_gui_basics.cpp"/> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ClInclude Include="..\..\Source\DemoAnalyticsEventTypes.h"/> | |||
<ClInclude Include="..\..\Source\GoogleAnalyticsDestination.h"/> | |||
<ClInclude Include="..\..\Source\MainComponent.h"/> | |||
<ClInclude Include="..\..\..\..\modules\juce_analytics\analytics\juce_Analytics.h"/> | |||
@@ -1362,6 +1362,9 @@ | |||
</ClCompile> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ClInclude Include="..\..\Source\DemoAnalyticsEventTypes.h"> | |||
<Filter>AnalyticsCollection\Source</Filter> | |||
</ClInclude> | |||
<ClInclude Include="..\..\Source\GoogleAnalyticsDestination.h"> | |||
<Filter>AnalyticsCollection\Source</Filter> | |||
</ClInclude> | |||
@@ -42,6 +42,7 @@ | |||
996BEF5ADCE2EC85EB9F637F = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_data_structures"; path = "../../../../modules/juce_data_structures"; sourceTree = "SOURCE_ROOT"; }; | |||
A0DDFB3559C431E96EC59392 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_events.mm"; path = "../../JuceLibraryCode/include_juce_events.mm"; sourceTree = "SOURCE_ROOT"; }; | |||
A93F5541F6B3C067538499EF = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; | |||
AAB5010326113C1358279789 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DemoAnalyticsEventTypes.h; path = ../../Source/DemoAnalyticsEventTypes.h; sourceTree = "SOURCE_ROOT"; }; | |||
AD2CFF58DA5E1C6EDF9CC399 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_data_structures.mm"; path = "../../JuceLibraryCode/include_juce_data_structures.mm"; sourceTree = "SOURCE_ROOT"; }; | |||
BC02966C48A4F51E9A187E4A = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = AnalyticsCollection/Images.xcassets; sourceTree = "SOURCE_ROOT"; }; | |||
D352CDB4CA7E8B21FAA83B8C = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MainComponent.h; path = ../../Source/MainComponent.h; sourceTree = "SOURCE_ROOT"; }; | |||
@@ -50,6 +51,7 @@ | |||
F2CF007AA4C90AC7A5AD1604 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_analytics"; path = "../../../../modules/juce_analytics"; sourceTree = "SOURCE_ROOT"; }; | |||
FBCE051A0BA6C9FA3E64B47B = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; | |||
D269FA28B5D6012AEFE0BF20 = {isa = PBXGroup; children = ( | |||
AAB5010326113C1358279789, | |||
8B927F72BA8726A064560942, | |||
D352CDB4CA7E8B21FAA83B8C, | |||
6A86C9751E9DCFA62D4562DB, ); name = Source; sourceTree = "<group>"; }; | |||
@@ -0,0 +1,10 @@ | |||
#pragma once | |||
enum DemoAnalyticsEventTypes | |||
{ | |||
event, | |||
sessionStart, | |||
sessionEnd, | |||
screenView, | |||
exception | |||
}; |
@@ -1,5 +1,7 @@ | |||
#include "../JuceLibraryCode/JuceHeader.h" | |||
#include "DemoAnalyticsEventTypes.h" | |||
class GoogleAnalyticsDestination : public ThreadedAnalyticsDestination | |||
{ | |||
public: | |||
@@ -47,32 +49,56 @@ public: | |||
{ | |||
// Send events to Google Analytics. | |||
String appData ("v=1&tid=" + apiKey + "&t=event&"); | |||
String appData ("v=1&aip=1&tid=" + apiKey); | |||
StringArray postData; | |||
for (auto& event : events) | |||
{ | |||
StringPairArray data; | |||
if (event.name == "startup") | |||
{ | |||
data.set ("ec", "info"); | |||
data.set ("ea", "appStarted"); | |||
} | |||
else if (event.name == "shutdown") | |||
{ | |||
data.set ("ec", "info"); | |||
data.set ("ea", "appStopped"); | |||
} | |||
else if (event.name == "button_press") | |||
{ | |||
data.set ("ec", "button_press"); | |||
data.set ("ea", event.parameters["id"]); | |||
} | |||
else | |||
switch (event.eventType) | |||
{ | |||
continue; | |||
case (DemoAnalyticsEventTypes::event): | |||
{ | |||
data.set ("t", "event"); | |||
if (event.name == "startup") | |||
{ | |||
data.set ("ec", "info"); | |||
data.set ("ea", "appStarted"); | |||
} | |||
else if (event.name == "shutdown") | |||
{ | |||
data.set ("ec", "info"); | |||
data.set ("ea", "appStopped"); | |||
} | |||
else if (event.name == "button_press") | |||
{ | |||
data.set ("ec", "button_press"); | |||
data.set ("ea", event.parameters["id"]); | |||
} | |||
else if (event.name == "crash") | |||
{ | |||
data.set ("ec", "crash"); | |||
data.set ("ea", "crash"); | |||
} | |||
else | |||
{ | |||
jassertfalse; | |||
continue; | |||
} | |||
break; | |||
} | |||
default: | |||
{ | |||
// Unknown event type! In this demo app we're just using a | |||
// single event type, but in a real app you probably want to | |||
// handle multiple ones. | |||
jassertfalse; | |||
break; | |||
} | |||
} | |||
data.set ("cid", event.userID); | |||
@@ -82,7 +108,7 @@ public: | |||
for (auto& key : data.getAllKeys()) | |||
eventData.add (key + "=" + URL::addEscapeChars (data[key], true)); | |||
postData.add (appData + eventData.joinIntoString ("&")); | |||
postData.add (appData + "&" + eventData.joinIntoString ("&")); | |||
} | |||
auto url = URL ("https://www.google-analytics.com/batch") | |||
@@ -139,6 +165,7 @@ private: | |||
{ | |||
auto* xmlEvent = new XmlElement ("google_analytics_event"); | |||
xmlEvent->setAttribute ("name", event.name); | |||
xmlEvent->setAttribute ("type", event.eventType); | |||
xmlEvent->setAttribute ("timestamp", (int) event.timestamp); | |||
xmlEvent->setAttribute ("user_id", event.userID); | |||
@@ -194,6 +221,7 @@ private: | |||
restoredEventQueue.push_back ({ | |||
xmlEvent->getStringAttribute ("name"), | |||
xmlEvent->getIntAttribute ("type"), | |||
(uint32) xmlEvent->getIntAttribute ("timestamp"), | |||
parameters, | |||
xmlEvent->getStringAttribute ("user_id"), | |||
@@ -2,6 +2,7 @@ | |||
#include "GoogleAnalyticsDestination.h" | |||
#include "MainComponent.h" | |||
#include "DemoAnalyticsEventTypes.h" | |||
//============================================================================== | |||
class AnalyticsCollectionApplication : public JUCEApplication | |||
@@ -17,8 +18,8 @@ public: | |||
//============================================================================== | |||
void initialise (const String&) override | |||
{ | |||
// Add an analytics identifier for the user. Make sure you don't collect | |||
// identifiable information accidentally if you haven't asked for permission! | |||
// Add an analytics identifier for the user. Make sure you don't accidentally | |||
// collect identifiable information if you haven't asked for permission! | |||
Analytics::getInstance()->setUserId ("AnonUser1234"); | |||
// Add any other constant user information. | |||
@@ -29,14 +30,18 @@ public: | |||
// Add any analytics destinations we want to use to the Analytics singleton. | |||
Analytics::getInstance()->addDestination (new GoogleAnalyticsDestination()); | |||
Analytics::getInstance()->logEvent ("startup", {}); | |||
// The event type here should probably be DemoAnalyticsEventTypes::sessionStart | |||
// in a more advanced app. | |||
Analytics::getInstance()->logEvent ("startup", {}, DemoAnalyticsEventTypes::event); | |||
mainWindow = new MainWindow (getApplicationName()); | |||
} | |||
void shutdown() override | |||
{ | |||
Analytics::getInstance()->logEvent ("shutdown", {}); | |||
// The event type here should probably be DemoAnalyticsEventTypes::sessionEnd | |||
// in a more advanced app. | |||
Analytics::getInstance()->logEvent ("shutdown", {}, DemoAnalyticsEventTypes::event); | |||
// Add your application's shutdown code here.. | |||
@@ -2,13 +2,19 @@ | |||
#include "../JuceLibraryCode/JuceHeader.h" | |||
class MainContentComponent : public Component | |||
#include "DemoAnalyticsEventTypes.h" | |||
class MainContentComponent : public Component, | |||
private Button::Listener | |||
{ | |||
public: | |||
//============================================================================== | |||
MainContentComponent() | |||
{ | |||
crashButton.addListener (this); | |||
addAndMakeVisible (eventButton); | |||
addAndMakeVisible (crashButton); | |||
setSize (300, 200); | |||
@@ -17,7 +23,10 @@ public: | |||
logEventButtonPress = new ButtonTracker (eventButton, "button_press", logButtonPressParameters); | |||
} | |||
~MainContentComponent() {} | |||
~MainContentComponent() | |||
{ | |||
crashButton.removeListener (this); | |||
} | |||
void paint (Graphics& g) override | |||
{ | |||
@@ -26,12 +35,23 @@ public: | |||
void resized() override | |||
{ | |||
eventButton.centreWithSize (100, 50); | |||
eventButton.centreWithSize (100, 40); | |||
eventButton.setBounds (eventButton.getBounds().translated (0, 25)); | |||
crashButton.setBounds (eventButton.getBounds().translated (0, -50)); | |||
} | |||
private: | |||
//============================================================================== | |||
TextButton eventButton { "Press me!" }; | |||
void buttonClicked (Button*) override | |||
{ | |||
// In a more advanced application you would probably use a different event | |||
// type here. | |||
Analytics::getInstance()->logEvent ("crash", {}, DemoAnalyticsEventTypes::event); | |||
Analytics::getInstance()->getDestinations().clear(); | |||
JUCEApplication::getInstance()->shutdown(); | |||
} | |||
TextButton eventButton { "Press me!" }, crashButton { "Simulate crash!" }; | |||
ScopedPointer<ButtonTracker> logEventButtonPress; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent) | |||
@@ -28,6 +28,11 @@ void Analytics::addDestination (AnalyticsDestination* destination) | |||
destinations.add (destination); | |||
} | |||
OwnedArray<AnalyticsDestination>& Analytics::getDestinations() | |||
{ | |||
return destinations; | |||
} | |||
void Analytics::setUserId (const String& newUserId) | |||
{ | |||
userId = newUserId; | |||
@@ -39,13 +44,15 @@ void Analytics::setUserProperties (const StringPairArray& properties) | |||
} | |||
void Analytics::logEvent (const String& eventName, | |||
const StringPairArray& parameters) | |||
const StringPairArray& parameters, | |||
int eventType) | |||
{ | |||
if (! isSuspended) | |||
{ | |||
AnalyticsDestination::AnalyticsEvent event | |||
{ | |||
eventName, | |||
eventType, | |||
Time::getMillisecondCounter(), | |||
parameters, | |||
userId, | |||
@@ -47,6 +47,14 @@ public: | |||
*/ | |||
void addDestination (AnalyticsDestination* destination); | |||
/** Returns the array of AnalyticsDestinations managed by this class. | |||
If you have added any subclasses of ThreadedAnalyticsDestination to | |||
this class then you can remove them from this list to force them to | |||
flush any pending events. | |||
*/ | |||
OwnedArray<AnalyticsDestination>& getDestinations(); | |||
/** Sets a user ID that will be added to all AnalyticsEvents sent to | |||
AnalyticsDestinations. | |||
@@ -65,15 +73,17 @@ public: | |||
The AnalyticsEvent will be timestamped, and will have the userId and | |||
userProperties populated by values previously set by calls to | |||
setUserId and setUserProperties. The name and parameters will be | |||
setUserId and setUserProperties. The name, parameters and type will be | |||
populated by the arguments supplied to this function. | |||
@param eventName the event name | |||
@param parameters the event parameters | |||
@param eventType (optional) an integer to indicate the event | |||
type, which will be set to 0 if not supplied. | |||
*/ | |||
void logEvent (const String& eventName, const StringPairArray& parameters); | |||
void logEvent (const String& eventName, const StringPairArray& parameters, int eventType = 0); | |||
/** Suspends analytics submission to AnalyticsDestinations. | |||
/** Suspends analytics submissions to AnalyticsDestinations. | |||
@param shouldBeSuspended if event submission should be suspended | |||
*/ | |||
@@ -25,10 +25,12 @@ namespace juce | |||
ButtonTracker::ButtonTracker (Button& buttonToTrack, | |||
const String& triggeredEventName, | |||
const StringPairArray& triggeredEventParameters) | |||
const StringPairArray& triggeredEventParameters, | |||
int triggeredEventType) | |||
: button (buttonToTrack), | |||
eventName (triggeredEventName), | |||
eventParameters (triggeredEventParameters) | |||
eventParameters (triggeredEventParameters), | |||
eventType (triggeredEventType) | |||
{ | |||
button.addListener (this); | |||
} | |||
@@ -47,7 +49,7 @@ void ButtonTracker::buttonClicked (Button* b) | |||
if (button.getClickingTogglesState()) | |||
params.set ("ButtonState", button.getToggleState() ? "On" : "Off"); | |||
Analytics::getInstance()->logEvent (eventName, params); | |||
Analytics::getInstance()->logEvent (eventName, params, eventType); | |||
} | |||
} | |||
@@ -51,7 +51,8 @@ public: | |||
*/ | |||
ButtonTracker (Button& buttonToTrack, | |||
const String& triggeredEventName, | |||
const StringPairArray& triggeredEventParameters = {}); | |||
const StringPairArray& triggeredEventParameters = {}, | |||
int triggeredEventType = 0); | |||
/** Destructor. */ | |||
~ButtonTracker(); | |||
@@ -63,6 +64,7 @@ private: | |||
Button& button; | |||
const String eventName; | |||
const StringPairArray eventParameters; | |||
const int eventType; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonTracker) | |||
}; | |||
@@ -44,6 +44,12 @@ struct JUCE_API AnalyticsDestination | |||
/** The name of the event. */ | |||
String name; | |||
/** An optional integer representing the type of the event. You can use | |||
this to indicate if the event was a screenview, session start, | |||
exception, etc. | |||
*/ | |||
int eventType; | |||
/** | |||
The timestamp of the event. | |||
@@ -297,7 +297,7 @@ struct ThreadedAnalyticsDestinationTests : public UnitTest | |||
std::deque<AnalyticsDestination::AnalyticsEvent> testEvents; | |||
for (int i = 0; i < 7; ++i) | |||
testEvents.push_back ({ String (i), Time::getMillisecondCounter(), {}, "TestUser", {} }); | |||
testEvents.push_back ({ String (i), 0, Time::getMillisecondCounter(), {}, "TestUser", {} }); | |||
std::deque<AnalyticsDestination::AnalyticsEvent> loggedEvents, unloggedEvents; | |||
@@ -169,6 +169,7 @@ JUCESplashScreen::JUCESplashScreen (Component& parent) | |||
StringPairArray data; | |||
data.set ("v", "1"); | |||
data.set ("aip", "1"); | |||
data.set ("tid", "UA-19759318-3"); | |||
data.set ("cid", deviceIdentifier); | |||
data.set ("t", "event"); | |||