internal Identify(string userId, IDictionary<string, object> traits, Options options) : base("identify", userId, options) { this.Traits = traits ?? new Traits(); }
internal Identify(string userId, Traits traits, Options options) : base("identify", userId, options) { this.Traits = traits ?? new Traits(); }
internal Identify(string userId, Traits traits, Options options) : base("identify", options) { UserId = userId; Traits = traits ?? new Traits(); }
internal Group(string userId, string groupId, IDictionary<string, object> traits, Options options) : base("group", userId, options) { this.GroupId = groupId; this.Traits = traits ?? new Traits(); }
internal Group(string userId, string groupId, Traits traits, Options options) : base("group", userId, options) { this.GroupId = groupId; this.Traits = traits ?? new Traits(); }
internal Track(string userId, string eventName, Properties properties, Options options) : base("track", userId, options) { this.EventName = eventName; this.Properties = properties ?? new Properties(); }
internal Group(string userId, string groupId, Traits traits, Options options) : base("group", options) { UserId = userId; GroupId = groupId; Traits = traits ?? new Traits(); }
internal Track(string userId, string eventName, Properties properties, Options options) : base("track", options) { UserId = userId; EventName = eventName; Properties = properties ?? new Properties(); }
internal Page(string userId, string name, string category, Properties properties, Options options) : base("page", userId, options) { this.Name = name; this.Category = category; this.Properties = properties ?? new Properties(); }
internal Screen(string userId, string name, string category, IDictionary<string, object> properties, Options options) : base("screen", userId, options) { this.Name = name; this.Category = category; this.Properties = properties ?? new Properties(); }
internal Screen(string userId, string name, string category, Properties properties, Options options) : base("screen", options) { UserId = userId; Name = name; Category = category; Properties = properties ?? new Properties(); }
internal BaseAction(string type, Options options) { options = options ?? new Options (); this.Type = type; this.MessageId = Guid.NewGuid ().ToString(); if (options.Timestamp.HasValue) this.Timestamp = options.Timestamp.ToString (); else this.Timestamp = DateTime.Now.ToString("o"); this.Context = options.Context; this.Integrations = options.Integrations; }
static AnalyticsManager() { //anonymise the machine name so it's not too stalkery anonymousUserID = GetHashString(Environment.MachineName); userProperties = new Segment.Model.Properties() { {"version",UpdateManager.CurrentVersion}, {"game",GameConfigurationManager.GameConfiguration.Name} }; options = new Options {Context = {{"direct", true}}}; }
internal BaseAction(string type, Options options) { options = options ?? new Options (); Type = type; MessageId = Guid.NewGuid ().ToString(); if (options.Timestamp.HasValue) Timestamp = options.Timestamp.ToString (); else Timestamp = DateTime.Now.ToString("o"); Context = options.Context; Integrations = options.Integrations; AnonymousId = options.AnonymousId; }
/// <summary> /// The `page` method let your record whenever a user sees a webpage on /// your website, and attach a `name`, `category` or `properties` to the webpage load. /// </summary> /// /// <param name="userId">The visitor's identifier after they log in, or you know /// who they are. By explicitly identifying a user, you tie all of their actions to their identity. /// This makes it possible for you to run things like segment-based email campaigns.</param> /// /// <param name="name">The name of the webpage, like "Signup", "Login"</param> /// /// <param name="category">The (optional) category of the mobile screen, like "Authentication", "Sports"</param> /// /// <param name="properties"> A dictionary with items that describe the page /// in more detail. This argument is optional, but highly recommended — /// you’ll find these properties extremely useful later.</param> /// /// <param name="options">Options allowing you to set timestamp, anonymousId, target integrations, /// and the context of th emessage.</param> /// public void Page(string userId, string name, string category, Properties properties, Options options) { EnsureId(userId, options); if (string.IsNullOrEmpty(name)) throw new InvalidOperationException("Please supply a valid name to #Page."); Enqueue(new Page(userId, name, category, properties, options)); }
/// <summary> /// The `screen` method let your record whenever a user sees a mobile screen on /// your mobile app, and attach a `name`, `category` or `properties` to the screen. /// </summary> /// /// <param name="userId">The visitor's identifier after they log in, or you know /// who they are. By /// explicitly identifying a user, you tie all of their actions to their identity. /// This makes it possible for you to run things like segment-based email campaigns.</param> /// /// <param name="name">The name of the mobile screen, like "Signup", "Login"</param> /// /// <param name="category">The (optional) category of the mobile screen, like "Authentication", "Sports"</param> /// /// <param name="properties"> A dictionary with items that describe the screen /// in more detail. This argument is optional, but highly recommended — /// you’ll find these properties extremely useful later.</param> /// /// <param name="options">Options allowing you to set timestamp, anonymousId, target integrations, /// and the context of th emessage.</param> /// public void Screen(string userId, string name, string category, Properties properties, Options options) { if (String.IsNullOrEmpty(userId)) throw new InvalidOperationException("Please supply a valid userId to #Screen."); if (String.IsNullOrEmpty(name)) throw new InvalidOperationException("Please supply a valid name to #Screen."); Enqueue(new Screen(userId, name, category, properties, options)); }
/// <summary> /// The `screen` method let your record whenever a user sees a mobile screen on /// your mobile app, and attach a `name`, `category` or `properties` to the screen. /// </summary> /// /// <param name="userId">The visitor's identifier after they log in, or you know /// who they are. By /// explicitly identifying a user, you tie all of their actions to their identity. /// This makes it possible for you to run things like segment-based email campaigns.</param> /// /// <param name="name">The name of the mobile screen, like "Signup", "Login"</param> /// /// <param name="properties"> A dictionary with items that describe the screen /// in more detail. This argument is optional, but highly recommended — /// you’ll find these properties extremely useful later.</param> /// /// <param name="options">Options allowing you to set timestamp, anonymousId, target integrations, /// and the context of th emessage.</param> /// public void Screen(string userId, string name, Properties properties, Options options) { Screen (userId, name, null, properties, options); }
/// <summary> /// The `screen` method let your record whenever a user sees a mobile screen on /// your mobile app, and attach a `name`, `category` or `properties` to the screen. /// </summary> /// /// <param name="userId">The visitor's identifier after they log in, or you know /// who they are. By /// explicitly identifying a user, you tie all of their actions to their identity. /// This makes it possible for you to run things like segment-based email campaigns.</param> /// /// <param name="name">The name of the mobile screen, like "Signup", "Login"</param> /// /// <param name="options">Options allowing you to set timestamp, anonymousId, target integrations, /// and the context of th emessage.</param> /// public void Screen(string userId, string name, Options options) { Screen (userId, name, null, null, options); }
/// <summary> /// The `page` method let your record whenever a user sees a webpage on /// your website, and attach a `name`, `category` or `properties` to the webpage load. /// </summary> /// /// <param name="userId">The visitor's identifier after they log in, or you know /// who they are. By explicitly identifying a user, you tie all of their actions to their identity. /// This makes it possible for you to run things like segment-based email campaigns.</param> /// /// <param name="name">The name of the webpage, like "Signup", "Login"</param> /// /// <param name="properties"> A dictionary with items that describe the page /// in more detail. This argument is optional, but highly recommended — /// you’ll find these properties extremely useful later.</param> /// /// <param name="options">Options allowing you to set timestamp, anonymousId, target integrations, /// and the context of th emessage.</param> /// public void Page(string userId, string name, Properties properties, Options options) { Page (userId, name, null, properties, options); }
/// <summary> /// The `page` method let your record whenever a user sees a webpage on /// your website, and attach a `name`, `category` or `properties` to the webpage load. /// </summary> /// /// <param name="userId">The visitor's identifier after they log in, or you know /// who they are. By explicitly identifying a user, you tie all of their actions to their identity. /// This makes it possible for you to run things like segment-based email campaigns.</param> /// /// <param name="name">The name of the webpage, like "Signup", "Login"</param> /// /// <param name="options">Options allowing you to set timestamp, anonymousId, target integrations, /// and the context of th emessage.</param> /// public void Page(string userId, string name, Options options) { Page (userId, name, null, null, options); }
/// <summary> /// Initialized a singleton; after calling this, use Analytics.Track() for each event. /// </summary> /// <param name="apiSecret">The segment.com apiSecret</param> /// <param name="userInfo">Information about the user that you have previous collected</param> /// <param name="propertiesThatGoWithEveryEvent">A set of key-value pairs to send with *every* event</param> /// <param name="allowTracking">If false, this will not do any communication with segment.io</param> public Analytics(string apiSecret, UserInfo userInfo, Dictionary<string, string> propertiesThatGoWithEveryEvent, bool allowTracking = true) { if (_singleton != null) { throw new ApplicationException("You can only construct a single Analytics object."); } _singleton = this; _propertiesThatGoWithEveryEvent = propertiesThatGoWithEveryEvent; _userInfo = userInfo; AllowTracking = allowTracking; //UrlThatReturnsExternalIpAddress is a static and should really be set before this is called, so don't mess with it if the client has given us a different url to us if (string.IsNullOrEmpty(UrlThatReturnsExternalIpAddress)) UrlThatReturnsExternalIpAddress = "http://icanhazip.com"; //went down: "http://ipecho.net/plain"; if (!AllowTracking) return; //bring in settings from any previous version if (AnalyticsSettings.Default.NeedUpgrade) { //see http://stackoverflow.com/questions/3498561/net-applicationsettingsbase-should-i-call-upgrade-every-time-i-load AnalyticsSettings.Default.Upgrade(); AnalyticsSettings.Default.NeedUpgrade = false; AnalyticsSettings.Default.Save(); } if (string.IsNullOrEmpty(AnalyticsSettings.Default.IdForAnalytics)) { // Apparently a first-time install. Any chance we can migrate settings from another channel of this app? // We really want to use the same ID if possible to keep our statistics valid. try { AttemptToGetUserIdSettingsFromDifferentChannel(); } catch (Exception) { // Oh, well, we tried. } } Segment.Analytics.Initialize(apiSecret); Segment.Analytics.Client.Failed += Client_Failed; Segment.Analytics.Client.Succeeded += Client_Succeeded; if (string.IsNullOrEmpty(AnalyticsSettings.Default.IdForAnalytics)) { AnalyticsSettings.Default.IdForAnalytics = Guid.NewGuid().ToString(); AnalyticsSettings.Default.Save(); } var context = new Context(); context.Add("language", _userInfo.UILanguageCode); _options = new Options(); _options.SetContext(context); UpdateSegmentIOInformationOnThisUser(); ReportIpAddressOfThisMachineAsync(); //this will take a while and may fail, so just do it when/if we can string versionNumberWithBuild = ""; try { versionNumberWithBuild = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString(); } catch (NullReferenceException) { try { // GetEntryAssembly is null for MAF plugins versionNumberWithBuild = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); } catch (NullReferenceException) { // This probably can't happen, but if it does, just roll with it. } } string versionNumber = versionNumberWithBuild.Split('.').Take(2).Aggregate((a, b) => a + "." + b); SetApplicationProperty("Version", versionNumber); SetApplicationProperty("FullVersion", versionNumberWithBuild); SetApplicationProperty("UserName", GetUserNameForEvent()); SetApplicationProperty("Browser", GetOperatingSystemLabel()); if (string.IsNullOrEmpty(AnalyticsSettings.Default.LastVersionLaunched)) { //"Created" is a special property that segment.io understands and coverts to equivalents in various analytics services //So it's not as descriptive for us as "FirstLaunchOnSystem", but it will give the best experience on the analytics sites. TrackWithApplicationProperties("Created"); } else if (AnalyticsSettings.Default.LastVersionLaunched != versionNumberWithBuild) { TrackWithApplicationProperties("Upgrade", new Properties { {"OldVersion", AnalyticsSettings.Default.LastVersionLaunched}, }); } // We want to record the launch event independent of whether we also recorded a special first launch // But that is done after we retrieve (or fail to retrieve) our external ip address. // See http://issues.bloomlibrary.org/youtrack/issue/BL-4011. AnalyticsSettings.Default.LastVersionLaunched = versionNumberWithBuild; AnalyticsSettings.Default.Save(); }
/// <summary> /// Whenever a user triggers an event on your site, you’ll want to track it /// so that you can analyze and segment by those events later. /// </summary> /// /// <param name="userId">The visitor's identifier after they log in, or you know /// who they are. By /// explicitly identifying a user, you tie all of their actions to their identity. /// This makes it possible for you to run things like segment-based email campaigns.</param> /// /// <param name="eventName">The event name you are tracking. It is recommended /// that it is in human readable form. For example, "Bought T-Shirt" /// or "Started an exercise"</param> /// /// <param name="properties"> A dictionary with items that describe the event /// in more detail. This argument is optional, but highly recommended — /// you’ll find these properties extremely useful later.</param> /// /// <param name="options">Options allowing you to set timestamp, anonymousId, target integrations, /// and the context of th emessage.</param> /// /// public void Track(string userId, string eventName, Properties properties, Options options) { if (String.IsNullOrEmpty(userId)) throw new InvalidOperationException("Please supply a valid userId to Track."); if (String.IsNullOrEmpty(eventName)) throw new InvalidOperationException("Please supply a valid event to Track."); Enqueue(new Track(userId, eventName, properties, options)); }
/// <summary> /// Whenever a user triggers an event on your site, you’ll want to track it /// so that you can analyze and segment by those events later. /// </summary> /// /// <param name="userId">The visitor's identifier after they log in, or you know /// who they are. By /// explicitly identifying a user, you tie all of their actions to their identity. /// This makes it possible for you to run things like segment-based email campaigns.</param> /// /// <param name="eventName">The event name you are tracking. It is recommended /// that it is in human readable form. For example, "Bought T-Shirt" /// or "Started an exercise"</param> /// /// <param name="options">Options allowing you to set timestamp, anonymousId, target integrations, /// and the context of th emessage.</param> /// /// public void Track(string userId, string eventName, Options options) { Track(userId, eventName, null, options); }
/// <summary> /// The `group` method lets you associate a user with a group. Be it a company, /// organization, account, project, team or whatever other crazy name you came up /// with for the same concept! It also lets you record custom traits about the /// group, like industry or number of employees. /// </summary> /// /// <param name="userId">The visitor's database identifier after they log in, or you know /// who they are. By explicitly grouping a user, you tie all of their actions to their group.</param> /// /// <param name="groupId">The group's database identifier after they log in, or you know /// who they are.</param> /// /// <param name="traits">A dictionary with group keys like "name", “subscriptionPlan”. /// You can segment your users by any trait you record. Pass in values in key-value format. /// String key, then its value { String, Integer, Boolean, Double, or Date are acceptable types for a value. } </param> /// /// <param name="options">Options allowing you to set timestamp, anonymousId, target integrations, /// and the context of th emessage.</param> /// public void Group(string userId, string groupId, Traits traits, Options options) { if (String.IsNullOrEmpty(userId)) throw new InvalidOperationException("Please supply a valid userId to call #Group."); if (String.IsNullOrEmpty(groupId)) throw new InvalidOperationException("Please supply a valid groupId to call #Group."); Enqueue(new Group(userId, groupId, traits, options)); }
/// <summary> /// The `group` method lets you associate a user with a group. Be it a company, /// organization, account, project, team or whatever other crazy name you came up /// with for the same concept! It also lets you record custom traits about the /// group, like industry or number of employees. /// </summary> /// /// <param name="userId">The visitor's database identifier after they log in, or you know /// who they are. By explicitly grouping a user, you tie all of their actions to their group.</param> /// /// <param name="groupId">The group's database identifier after they log in, or you know /// who they are.</param> /// /// <param name="options">Options allowing you to set timestamp, anonymousId, target integrations, /// and the context of th emessage.</param> /// public void Group(string userId, string groupId, Options options) { Group (userId, groupId, null, options); }
/// <summary> /// Identifying a visitor ties all of their actions to an ID you /// recognize and records visitor traits you can segment by. /// </summary> /// /// <param name="userId">The visitor's identifier after they log in, or you know /// who they are. By /// explicitly identifying a user, you tie all of their actions to their identity.</param> /// /// <param name="traits">A dictionary with keys like "email", "name", “subscriptionPlan” or /// "friendCount”. You can segment your users by any trait you record. /// Pass in values in key-value format. String key, then its value /// { String, Integer, Boolean, Double, or Date are acceptable types for a value. } </param> /// /// <param name="options">Options allowing you to set timestamp, anonymousId, target integrations, /// and the context of th emessage.</param> /// public void Identify(string userId, Traits traits, Options options) { if (String.IsNullOrEmpty(userId)) throw new InvalidOperationException("Please supply a valid userId to Identify."); Enqueue(new Identify(userId, traits, options)); }
private static void EnsureId(string userId, Options options) { if (userId == null) throw new InvalidOperationException(nameof(userId)); if (options == null) throw new InvalidOperationException(nameof(options)); if (string.IsNullOrEmpty(userId) && string.IsNullOrEmpty(options.AnonymousId)) throw new InvalidOperationException("Please supply a valid id (either userId or anonymousId."); }
/// <summary> /// Aliases an anonymous user into an identified user. /// </summary> /// /// <param name="previousId">The anonymous user's id before they are logged in.</param> /// /// <param name="userId">the identified user's id after they're logged in.</param> /// /// <param name="options">Options allowing you to set timestamp, anonymousId, target integrations, /// and the context of th emessage.</param> /// public void Alias(string previousId, string userId, Options options) { if (String.IsNullOrEmpty(previousId)) throw new InvalidOperationException("Please supply a valid 'previousId' to Alias."); if (String.IsNullOrEmpty(userId)) throw new InvalidOperationException("Please supply a valid 'to' to Alias."); Enqueue(new Alias(previousId, userId, options)); }
/// <summary> /// Initialized a singleton; after calling this, use Analytics.Track() for each event. /// </summary> /// <param name="apiSecret">The segment.com apiSecret</param> /// <param name="userInfo">Information about the user that you have previous collected</param> /// <param name="propertiesThatGoWithEveryEvent">A set of key-value pairs to send with *every* event</param> /// <param name="allowTracking">If false, this will not do any communication with segment.io</param> public Analytics(string apiSecret, UserInfo userInfo, Dictionary<string, string> propertiesThatGoWithEveryEvent, bool allowTracking = true) { if (_singleton != null) { throw new ApplicationException("You can only construct a single Analytics object."); } _singleton = this; _propertiesThatGoWithEveryEvent = propertiesThatGoWithEveryEvent; _userInfo = userInfo; AllowTracking = allowTracking; //UrlThatReturnsExternalIpAddress is a static and should really be set before this is called, so don't mess with it if the clien has given us a different url to us if (string.IsNullOrEmpty(UrlThatReturnsExternalIpAddress)) UrlThatReturnsExternalIpAddress = "http://icanhazip.com";//went down: "http://ipecho.net/plain"; if (!AllowTracking) return; //bring in settings from any previous version if (AnalyticsSettings.Default.NeedUpgrade) { //see http://stackoverflow.com/questions/3498561/net-applicationsettingsbase-should-i-call-upgrade-every-time-i-load AnalyticsSettings.Default.Upgrade(); AnalyticsSettings.Default.NeedUpgrade = false; AnalyticsSettings.Default.Save(); } const string UserConfigFileName = "user.config"; if (string.IsNullOrEmpty(AnalyticsSettings.Default.IdForAnalytics)) { // Apparently a first-time install. Any chance we can migrate settings from another channel of this app? // We really want to use the same ID if possible to keep our statistics valid. // We need to get the company name and exe name of the main application, without introducing a dependency on // Windows.Forms, so we can't use the Windows.Forms.Application methods. var entryAssembly = Assembly.GetEntryAssembly(); // the main exe assembly var productExe = Path.GetFileNameWithoutExtension(entryAssembly.Location); AssemblyCompanyAttribute companyAttribute = AssemblyCompanyAttribute.GetCustomAttribute(entryAssembly, typeof(AssemblyCompanyAttribute)) as AssemblyCompanyAttribute; if (companyAttribute != null && !string.IsNullOrEmpty(productExe)) { string companyName = companyAttribute.Company; var settingsLocation = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), companyName); // Coincidentally, 5 is a good length for Bloom...better heuristic later? // For example, we could // - look for the last capital letter, and truncate to there. BloomAlpha->Bloom; HearThisAlpha->HearThis; *HearThis->Hear; TEX->?TE; TEXAlpha->TEX; BloomBetaOne->*BloomBeta // - look for the first non-initial capital letter, and truncate from there on. BloomAlpha->Bloom, HearThisAlpha->*Hear; HearThis->*Hear; TEX->*T' TEXAlpha->TEX; BloomBetaOne->Bloom // - look for a non-initial capital letter following at least one LC letter. Similar except TEX->TEX, TEXAlpha->*TEXAlpha. // In general, truncating too much is better than too little; too much just makes us slow, while too little may make us miss useful results. // It's true that truncating too much (like TEX->TE) may cause us to fetch an analytics ID from the wrong program. But even this is harmless, AFAIK. var index = Math.Min(5, productExe.Length); var prefix = productExe.Substring(0, index); var pattern = prefix + "*"; var possibleParentFolders = Directory.GetDirectories(settingsLocation, pattern); var possibleFolders = new List<string>(); foreach (var folder in possibleParentFolders) { possibleFolders.AddRange(Directory.GetDirectories(folder).Where(f => File.Exists(Path.Combine(f, UserConfigFileName)))); } possibleFolders.Sort((first, second) => { if (first == second) return 0; var firstConfigPath = Path.Combine(first, UserConfigFileName); var secondConfigPath = Path.Combine(second, UserConfigFileName); // Reversing the arguments like this means that second comes before first if it has a LARGER mod time. // That is, we end up with the most recently modified user.config first. return new FileInfo(secondConfigPath).LastWriteTimeUtc.CompareTo(new FileInfo(firstConfigPath).LastWriteTimeUtc); }); foreach (var folder in possibleFolders) { try { var doc = XDocument.Load(Path.Combine(folder, UserConfigFileName)); var idSetting = doc.XPathSelectElement( "configuration/userSettings/DesktopAnalytics.AnalyticsSettings/setting[@name='IdForAnalytics']"); if (idSetting == null) continue; string analyticsId = idSetting.Value; if (string.IsNullOrEmpty(analyticsId)) continue; AnalyticsSettings.Default.IdForAnalytics = analyticsId; AnalyticsSettings.Default.FirstName = ExtractSetting(AnalyticsSettings.Default.FirstName, doc, "FirstName"); AnalyticsSettings.Default.LastName = ExtractSetting(AnalyticsSettings.Default.LastName, doc, "LastName"); AnalyticsSettings.Default.LastVersionLaunched = ExtractSetting(AnalyticsSettings.Default.LastVersionLaunched, doc, "LastVersionLaunched"); AnalyticsSettings.Default.Email = ExtractSetting(AnalyticsSettings.Default.Email, doc, "Email"); AnalyticsSettings.Default.Save(); break; } catch (Exception) { // If anything goes wrong we just won't try to get our ID from this source. } } } } Segment.Analytics.Initialize(apiSecret); Segment.Analytics.Client.Failed += Client_Failed; Segment.Analytics.Client.Succeeded += Client_Succeeded; if (string.IsNullOrEmpty(AnalyticsSettings.Default.IdForAnalytics)) { AnalyticsSettings.Default.IdForAnalytics = Guid.NewGuid().ToString(); AnalyticsSettings.Default.Save(); } var context = new Context(); context.Add("language", _userInfo.UILanguageCode); _options = new Options(); _options.SetContext(context); UpdateSegmentIOInformationOnThisUser(); ReportIpAddressOfThisMachineAsync(); //this will take a while and may fail, so just do it when/if we can string versionNumberWithBuild = ""; try { versionNumberWithBuild = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString(); } catch (NullReferenceException) { try { // GetEntryAssembly is null for MAF plugins versionNumberWithBuild = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); } catch (NullReferenceException) { // This probably can't happen, but if it does, just roll with it. } } string versionNumber = versionNumberWithBuild.Split('.').Take(2).Aggregate((a, b) => a + "." + b); SetApplicationProperty("Version", versionNumber); SetApplicationProperty("FullVersion", versionNumberWithBuild); SetApplicationProperty("UserName", GetUserNameForEvent()); SetApplicationProperty("Browser", GetOperatingSystemLabel()); if (string.IsNullOrEmpty(AnalyticsSettings.Default.LastVersionLaunched)) { //"Created" is a special property that segment.io understands and coverts to equivalents in various analytics services //So it's not as descriptive for us as "FirstLaunchOnSystem", but it will give the best experience on the analytics sites. TrackWithApplicationProperties("Created"); } else if (AnalyticsSettings.Default.LastVersionLaunched != versionNumberWithBuild) { TrackWithApplicationProperties("Upgrade", new Properties { {"OldVersion", AnalyticsSettings.Default.LastVersionLaunched}, }); } //we want to record the launch event independent of whether we also recorded a special first launch // But that is done after we retrieve (or fail to retrieve) our external ip address. // See http://issues.bloomlibrary.org/youtrack/issue/BL-4011. AnalyticsSettings.Default.LastVersionLaunched = versionNumberWithBuild; AnalyticsSettings.Default.Save(); }
internal Alias(string previousId, string userId, Options options) : base("alias", userId, options) { this.PreviousId = previousId; }