diff --git a/examples/AnalyticsCollection/AnalyticsCollection.jucer b/examples/AnalyticsCollection/AnalyticsCollection.jucer index 165234312c..e2f41d949b 100644 --- a/examples/AnalyticsCollection/AnalyticsCollection.jucer +++ b/examples/AnalyticsCollection/AnalyticsCollection.jucer @@ -7,6 +7,8 @@ jucerVersion="5.2.0"> + diff --git a/examples/AnalyticsCollection/Builds/Android/app/CMakeLists.txt b/examples/AnalyticsCollection/Builds/Android/app/CMakeLists.txt index adae6fc47f..d0c7a8f8a8 100644 --- a/examples/AnalyticsCollection/Builds/Android/app/CMakeLists.txt +++ b/examples/AnalyticsCollection/Builds/Android/app/CMakeLists.txt @@ -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) diff --git a/examples/AnalyticsCollection/Builds/MacOSX/AnalyticsCollection.xcodeproj/project.pbxproj b/examples/AnalyticsCollection/Builds/MacOSX/AnalyticsCollection.xcodeproj/project.pbxproj index 8cb9804415..258f04bdf5 100644 --- a/examples/AnalyticsCollection/Builds/MacOSX/AnalyticsCollection.xcodeproj/project.pbxproj +++ b/examples/AnalyticsCollection/Builds/MacOSX/AnalyticsCollection.xcodeproj/project.pbxproj @@ -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 = ""; }; diff --git a/examples/AnalyticsCollection/Builds/VisualStudio2017/AnalyticsCollection_App.vcxproj b/examples/AnalyticsCollection/Builds/VisualStudio2017/AnalyticsCollection_App.vcxproj index 87938bca4d..2e0fe86bb9 100644 --- a/examples/AnalyticsCollection/Builds/VisualStudio2017/AnalyticsCollection_App.vcxproj +++ b/examples/AnalyticsCollection/Builds/VisualStudio2017/AnalyticsCollection_App.vcxproj @@ -1228,6 +1228,7 @@ + diff --git a/examples/AnalyticsCollection/Builds/VisualStudio2017/AnalyticsCollection_App.vcxproj.filters b/examples/AnalyticsCollection/Builds/VisualStudio2017/AnalyticsCollection_App.vcxproj.filters index 1f01761550..8caf9bcc54 100644 --- a/examples/AnalyticsCollection/Builds/VisualStudio2017/AnalyticsCollection_App.vcxproj.filters +++ b/examples/AnalyticsCollection/Builds/VisualStudio2017/AnalyticsCollection_App.vcxproj.filters @@ -1362,6 +1362,9 @@ + + AnalyticsCollection\Source + AnalyticsCollection\Source diff --git a/examples/AnalyticsCollection/Builds/iOS/AnalyticsCollection.xcodeproj/project.pbxproj b/examples/AnalyticsCollection/Builds/iOS/AnalyticsCollection.xcodeproj/project.pbxproj index f3451ac5af..33a8f9d690 100644 --- a/examples/AnalyticsCollection/Builds/iOS/AnalyticsCollection.xcodeproj/project.pbxproj +++ b/examples/AnalyticsCollection/Builds/iOS/AnalyticsCollection.xcodeproj/project.pbxproj @@ -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 = ""; }; diff --git a/examples/AnalyticsCollection/Source/DemoAnalyticsEventTypes.h b/examples/AnalyticsCollection/Source/DemoAnalyticsEventTypes.h new file mode 100644 index 0000000000..346f4a1c13 --- /dev/null +++ b/examples/AnalyticsCollection/Source/DemoAnalyticsEventTypes.h @@ -0,0 +1,10 @@ +#pragma once + +enum DemoAnalyticsEventTypes +{ + event, + sessionStart, + sessionEnd, + screenView, + exception +}; diff --git a/examples/AnalyticsCollection/Source/GoogleAnalyticsDestination.h b/examples/AnalyticsCollection/Source/GoogleAnalyticsDestination.h index 5b26f92447..5f4a07dc30 100644 --- a/examples/AnalyticsCollection/Source/GoogleAnalyticsDestination.h +++ b/examples/AnalyticsCollection/Source/GoogleAnalyticsDestination.h @@ -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") + switch (event.eventType) { - 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 - { - 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"), diff --git a/examples/AnalyticsCollection/Source/Main.cpp b/examples/AnalyticsCollection/Source/Main.cpp index c768854ef7..aef5426bcb 100644 --- a/examples/AnalyticsCollection/Source/Main.cpp +++ b/examples/AnalyticsCollection/Source/Main.cpp @@ -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.. diff --git a/examples/AnalyticsCollection/Source/MainComponent.h b/examples/AnalyticsCollection/Source/MainComponent.h index 6934b359e1..99842077b6 100644 --- a/examples/AnalyticsCollection/Source/MainComponent.h +++ b/examples/AnalyticsCollection/Source/MainComponent.h @@ -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 logEventButtonPress; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent) diff --git a/modules/juce_analytics/analytics/juce_Analytics.cpp b/modules/juce_analytics/analytics/juce_Analytics.cpp index faa5da95e8..90f0bf0267 100644 --- a/modules/juce_analytics/analytics/juce_Analytics.cpp +++ b/modules/juce_analytics/analytics/juce_Analytics.cpp @@ -28,6 +28,11 @@ void Analytics::addDestination (AnalyticsDestination* destination) destinations.add (destination); } +OwnedArray& 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, diff --git a/modules/juce_analytics/analytics/juce_Analytics.h b/modules/juce_analytics/analytics/juce_Analytics.h index 1e46cd0afa..df870bdf01 100644 --- a/modules/juce_analytics/analytics/juce_Analytics.h +++ b/modules/juce_analytics/analytics/juce_Analytics.h @@ -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& 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 */ diff --git a/modules/juce_analytics/analytics/juce_ButtonTracker.cpp b/modules/juce_analytics/analytics/juce_ButtonTracker.cpp index 3fc613df99..0aae1054e0 100644 --- a/modules/juce_analytics/analytics/juce_ButtonTracker.cpp +++ b/modules/juce_analytics/analytics/juce_ButtonTracker.cpp @@ -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); } } diff --git a/modules/juce_analytics/analytics/juce_ButtonTracker.h b/modules/juce_analytics/analytics/juce_ButtonTracker.h index 3a85b63684..ee9883f7ea 100644 --- a/modules/juce_analytics/analytics/juce_ButtonTracker.h +++ b/modules/juce_analytics/analytics/juce_ButtonTracker.h @@ -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) }; diff --git a/modules/juce_analytics/destinations/juce_AnalyticsDestination.h b/modules/juce_analytics/destinations/juce_AnalyticsDestination.h index 23c364db2e..9ede9a1579 100644 --- a/modules/juce_analytics/destinations/juce_AnalyticsDestination.h +++ b/modules/juce_analytics/destinations/juce_AnalyticsDestination.h @@ -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. diff --git a/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp b/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp index d0e85fac09..322925842d 100644 --- a/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp +++ b/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp @@ -297,7 +297,7 @@ struct ThreadedAnalyticsDestinationTests : public UnitTest std::deque 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 loggedEvents, unloggedEvents; diff --git a/modules/juce_gui_basics/misc/juce_JUCESplashScreen.cpp b/modules/juce_gui_basics/misc/juce_JUCESplashScreen.cpp index be1802b3d0..433dd6665c 100644 --- a/modules/juce_gui_basics/misc/juce_JUCESplashScreen.cpp +++ b/modules/juce_gui_basics/misc/juce_JUCESplashScreen.cpp @@ -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");