| @@ -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"); | |||