Skip to content

Commit

Permalink
support enrichment closure and setAnonymousId (#110)
Browse files Browse the repository at this point in the history
* implement enrichment closure

* add unit tests

* fix unit test

* add new API SetAnonymousId

---------

Co-authored-by: Wenxi Zeng <[email protected]>
  • Loading branch information
wenxi-zeng and Wenxi Zeng authored Sep 27, 2024
1 parent 7354013 commit 964ddc5
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 36 deletions.
18 changes: 16 additions & 2 deletions Analytics-CSharp/Segment/Analytics/Analytics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,15 @@ public Analytics(Configuration configuration)
/// Process a raw event through the system. Useful when one needs to queue and replay events at a later time.
/// </summary>
/// <param name="incomingEvent">An event conforming to RawEvent to be processed in the timeline</param>
public void Process(RawEvent incomingEvent)
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
public void Process(RawEvent incomingEvent, Func<RawEvent, RawEvent> enrichment = default)
{
if (!Enable) return;

incomingEvent.ApplyRawEventData(_userInfo);
AnalyticsScope.Launch(AnalyticsDispatcher, () =>
{
Timeline.Process(incomingEvent);
Timeline.Process(incomingEvent, enrichment);
});
}

Expand All @@ -100,6 +101,19 @@ public void Process(RawEvent incomingEvent)
/// <returns>Anonymous Id</returns>
public virtual string AnonymousId() => _userInfo._anonymousId;

/// <summary>
/// Set the anonymousId.
/// </summary>
/// <param name="anonymousId">Anonymous Id</param>
public virtual void SetAnonymousId(string anonymousId)
{
_userInfo._anonymousId = anonymousId;
AnalyticsScope.Launch(AnalyticsDispatcher, async () =>
{
await Store.Dispatch<UserInfo.SetAnonymousIdAction, UserInfo>(new UserInfo.SetAnonymousIdAction(anonymousId));
});
}


/// <summary>
/// Retrieve the userId registered by a previous <see cref="Identify(string,JsonObject)"/> call
Expand Down
78 changes: 46 additions & 32 deletions Analytics-CSharp/Segment/Analytics/Events.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using global::System.Runtime.Serialization;
using Segment.Serialization;

Expand All @@ -12,15 +13,16 @@ public partial class Analytics
/// </summary>
/// <param name="name">Name of the action</param>
/// <param name="properties">Properties to describe the action</param>
public virtual void Track(string name, JsonObject properties = default)
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
public virtual void Track(string name, JsonObject properties = default, Func<RawEvent, RawEvent> enrichment = default)
{
if (properties == null)
{
properties = new JsonObject();
}

var trackEvent = new TrackEvent(name, properties);
Process(trackEvent);
Process(trackEvent, enrichment);
}

/// <summary>
Expand All @@ -30,17 +32,18 @@ public virtual void Track(string name, JsonObject properties = default)
/// </summary>
/// <param name="name">Name of the action</param>
/// <param name="properties">Properties to describe the action</param>
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
/// <typeparam name="T">Type that implements <see cref="ISerializable"/></typeparam>
public virtual void Track<T>(string name, T properties = default) where T : ISerializable
public virtual void Track<T>(string name, T properties = default, Func<RawEvent, RawEvent> enrichment = default) where T : ISerializable
{
if (properties == null)
{
Track(name);
Track(name, enrichment: enrichment);
}
else
{
string json = JsonUtility.ToJson(properties);
Track(name, JsonUtility.FromJson<JsonObject>(json));
Track(name, JsonUtility.FromJson<JsonObject>(json), enrichment);
}
}

Expand All @@ -59,7 +62,8 @@ public virtual void Track<T>(string name, T properties = default) where T : ISer
/// </summary>
/// <param name="userId">Unique identifier which you recognize a user by in your own database</param>
/// <param name="traits">Traits about the user</param>
public virtual void Identify(string userId, JsonObject traits = default)
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
public virtual void Identify(string userId, JsonObject traits = default, Func<RawEvent, RawEvent> enrichment = default)
{
if (traits == null)
{
Expand All @@ -76,7 +80,7 @@ public virtual void Identify(string userId, JsonObject traits = default)
});

var identifyEvent = new IdentifyEvent(userId, traits);
Process(identifyEvent);
Process(identifyEvent, enrichment);
}

/// <summary>
Expand All @@ -93,7 +97,8 @@ public virtual void Identify(string userId, JsonObject traits = default)
/// info.
/// </summary>
/// <param name="traits">Traits about the user</param>
public virtual void Identify(JsonObject traits)
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
public virtual void Identify(JsonObject traits, Func<RawEvent, RawEvent> enrichment = default)
{
if (traits == null)
{
Expand All @@ -109,7 +114,7 @@ public virtual void Identify(JsonObject traits)
});

var identifyEvent = new IdentifyEvent(_userInfo._userId, traits);
Process(identifyEvent);
Process(identifyEvent, enrichment);
}

/// <summary>
Expand All @@ -127,17 +132,18 @@ public virtual void Identify(JsonObject traits)
/// </summary>
/// <param name="userId">Unique identifier which you recognize a user by in your own database</param>
/// <param name="traits">Traits about the user</param>
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
/// <typeparam name="T">Type that implements <see cref="ISerializable"/></typeparam>
public virtual void Identify<T>(string userId, T traits = default) where T : ISerializable
public virtual void Identify<T>(string userId, T traits = default, Func<RawEvent, RawEvent> enrichment = default) where T : ISerializable
{
if (traits == null)
{
Identify(userId);
Identify(userId, enrichment: enrichment);
}
else
{
string json = JsonUtility.ToJson(traits);
Identify(userId, JsonUtility.FromJson<JsonObject>(json));
Identify(userId, JsonUtility.FromJson<JsonObject>(json), enrichment);
}
}

Expand All @@ -155,17 +161,18 @@ public virtual void Identify<T>(string userId, T traits = default) where T : ISe
/// info.
/// </summary>
/// <param name="traits">Traits about the user</param>
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
/// <typeparam name="T">Type that implements <see cref="ISerializable"/></typeparam>
public virtual void Identify<T>(T traits) where T : ISerializable
public virtual void Identify<T>(T traits, Func<RawEvent, RawEvent> enrichment = default) where T : ISerializable
{
if (traits == null)
{
Identify(new JsonObject());
Identify(new JsonObject(), enrichment);
}
else
{
string json = JsonUtility.ToJson(traits);
Identify(JsonUtility.FromJson<JsonObject>(json));
Identify(JsonUtility.FromJson<JsonObject>(json), enrichment);
}
}

Expand All @@ -177,14 +184,15 @@ public virtual void Identify<T>(T traits) where T : ISerializable
/// <param name="title">A name for the screen</param>
/// <param name="properties">Properties to add extra information to this call</param>
/// <param name="category">A category to describe the screen</param>
public virtual void Screen(string title, JsonObject properties = default, string category = "")
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
public virtual void Screen(string title, JsonObject properties = default, string category = "", Func<RawEvent, RawEvent> enrichment = default)
{
if (properties == null)
{
properties = new JsonObject();
}
var screenEvent = new ScreenEvent(category, title, properties);
Process(screenEvent);
Process(screenEvent, enrichment);
}

/// <summary>
Expand All @@ -195,17 +203,18 @@ public virtual void Screen(string title, JsonObject properties = default, string
/// <param name="title">A name for the screen</param>
/// <param name="properties">Properties to add extra information to this call</param>
/// <param name="category">A category to describe the screen</param>
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
/// <typeparam name="T">Type that implements <see cref="ISerializable"/></typeparam>
public virtual void Screen<T>(string title, T properties = default, string category = "") where T : ISerializable
public virtual void Screen<T>(string title, T properties = default, string category = "", Func<RawEvent, RawEvent> enrichment = default) where T : ISerializable
{
if (properties == null)
{
Screen(title, category: category);
Screen(title, category: category, enrichment: enrichment);
}
else
{
string json = JsonUtility.ToJson(properties);
Screen(title, JsonUtility.FromJson<JsonObject>(json), category);
Screen(title, JsonUtility.FromJson<JsonObject>(json), category, enrichment);
}
}

Expand All @@ -218,14 +227,15 @@ public virtual void Screen<T>(string title, T properties = default, string categ
/// <param name="title">A name for the page</param>
/// <param name="properties">Properties to add extra information to this call</param>
/// <param name="category">A category to describe the page</param>
public virtual void Page(string title, JsonObject properties = default, string category = "")
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
public virtual void Page(string title, JsonObject properties = default, string category = "", Func<RawEvent, RawEvent> enrichment = default)
{
if (properties == null)
{
properties = new JsonObject();
}
var pageEvent = new PageEvent(category, title, properties);
Process(pageEvent);
Process(pageEvent, enrichment);
}

/// <summary>
Expand All @@ -236,17 +246,18 @@ public virtual void Page(string title, JsonObject properties = default, string c
/// <param name="title">A name for the page</param>
/// <param name="properties">Properties to add extra information to this call</param>
/// <param name="category">A category to describe the page</param>
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
/// <typeparam name="T">Type that implements <see cref="ISerializable"/></typeparam>
public virtual void Page<T>(string title, T properties = default, string category = "") where T : ISerializable
public virtual void Page<T>(string title, T properties = default, string category = "", Func<RawEvent, RawEvent> enrichment = default) where T : ISerializable
{
if (properties == null)
{
Page(title, category: category);
Page(title, category: category, enrichment: enrichment);
}
else
{
string json = JsonUtility.ToJson(properties);
Page(title, JsonUtility.FromJson<JsonObject>(json), category);
Page(title, JsonUtility.FromJson<JsonObject>(json), category, enrichment);
}
}

Expand All @@ -259,14 +270,15 @@ public virtual void Page<T>(string title, T properties = default, string categor
/// </summary>
/// <param name="groupId">Unique identifier which you recognize a group by in your own database</param>
/// <param name="traits">Traits about the group</param>
public virtual void Group(string groupId, JsonObject traits = default)
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
public virtual void Group(string groupId, JsonObject traits = default, Func<RawEvent, RawEvent> enrichment = default)
{
if (traits == null)
{
traits = new JsonObject();
}
var groupEvent = new GroupEvent(groupId, traits);
Process(groupEvent);
Process(groupEvent, enrichment);
}

/// <summary>
Expand All @@ -278,17 +290,18 @@ public virtual void Group(string groupId, JsonObject traits = default)
/// </summary>
/// <param name="groupId">Unique identifier which you recognize a group by in your own database</param>
/// <param name="traits">Traits about the group</param>
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
/// <typeparam name="T">Type that implements <see cref="ISerializable"/></typeparam>
public virtual void Group<T>(string groupId, T traits = default) where T : ISerializable
public virtual void Group<T>(string groupId, T traits = default, Func<RawEvent, RawEvent> enrichment = default) where T : ISerializable
{
if (traits == null)
{
Group(groupId);
Group(groupId, enrichment: enrichment);
}
else
{
string json = JsonUtility.ToJson(traits);
Group(groupId, JsonUtility.FromJson<JsonObject>(json));
Group(groupId, JsonUtility.FromJson<JsonObject>(json), enrichment);
}
}

Expand All @@ -301,7 +314,8 @@ public virtual void Group<T>(string groupId, T traits = default) where T : ISeri
/// <param name="newId">The new ID you want to alias the existing ID to. The existing ID will be either
/// the previousId if you have called identify, or the anonymous ID.
/// </param>
public virtual void Alias(string newId)
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
public virtual void Alias(string newId, Func<RawEvent, RawEvent> enrichment = default)
{
var aliasEvent = new AliasEvent(newId, _userInfo._userId ?? _userInfo._anonymousId);

Expand All @@ -312,7 +326,7 @@ public virtual void Alias(string newId)
await Store.Dispatch<UserInfo.SetUserIdAction, UserInfo>(new UserInfo.SetUserIdAction(newId));
});

Process(aliasEvent);
Process(aliasEvent, enrichment);
}
}
}
7 changes: 6 additions & 1 deletion Analytics-CSharp/Segment/Analytics/Timeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,18 @@ public class Timeline
/// initiate the event's lifecycle
/// </summary>
/// <param name="incomingEvent">event to be processed</param>
/// <param name="enrichment">a closure that enables enrichment on the generated event</param>
/// <returns>event after processing</returns>
internal RawEvent Process(RawEvent incomingEvent)
internal RawEvent Process(RawEvent incomingEvent, Func<RawEvent, RawEvent> enrichment = default)
{
// Apply before and enrichment types first to start the timeline processing.
RawEvent beforeResult = ApplyPlugins(PluginType.Before, incomingEvent);
// Enrichment is like middleware, a chance to update the event across the board before going to destinations.
RawEvent enrichmentResult = ApplyPlugins(PluginType.Enrichment, beforeResult);
if (enrichment != null)
{
enrichmentResult = enrichment(enrichmentResult);
}

// Make sure not to update the events during this next cycle. Since each destination may want different
// data than other destinations we don't want them conflicting and changing what a real result should be
Expand Down
13 changes: 13 additions & 0 deletions Tests/AnalyticsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ public void TestAnonymousId()
Assert.NotNull(id);
}

[Fact]
public void TestSetAnonymousId()
{
string expected = "foo";
_storage.Setup(o => o.WritePrefs(StorageConstants.AnonymousId, expected)).Verifiable();
string anonIdOld = _analytics.AnonymousId();
_analytics.SetAnonymousId(expected);

string anonIdNew = _analytics.AnonymousId();
Assert.NotEqual(anonIdOld, anonIdNew);
Assert.Equal(expected, anonIdNew);
}

[Fact]
public void TestUserId()
{
Expand Down
Loading

0 comments on commit 964ddc5

Please sign in to comment.